Skip to content

Commit 01172ea

Browse files
committed
fix: store inline prosemirror JSON in ChatMessage.message (HULY-20)
The original attempt (PR #44, reverted in #45) called uploadMarkup and stored a MarkupBlobRef string in `message`. That rendered as literal text in the web UI because the front-end's MessageViewer component does: $: node = markupToJSON(message) with no MarkupBlobRef resolution step. ChatMessage.message is typed `TypeMarkup()` — an inline prosemirror-JSON string (`Markup = string` in @hcengineering/core), not a MarkupBlobRef. Why the same pattern appeared to work for Issue.description: Issue.description is also `TypeMarkup()`, but the web UI renders it through `<CollaborativeTextEditor>`, which DOES resolve MarkupBlobRef via the collaborator service. So `huly issue update --body` accidentally worked. The chat/dm/thread/activity paths render through plain `MessageViewer` and broke. Fix: * Convert HTML → prosemirror JSON locally via the existing `htmlToMarkup()` helper. * Store the resulting prosemirror-JSON string inline in `message` — no blob upload, no MarkupBlobRef. * Same pattern applied to all 10 chat-message write paths: comment add/update, channel message send/update, thread reply add/update, dm message send, activity reply add/update, approval comment. Removed from the previous PR (no longer relevant): * `uploadMarkup` and `generateId` imports — not used anymore. * Deferred-upload pattern for dry-run on channel paths — dry-run preview now shows the converted prosemirror JSON (which is what would be stored). No external IO involved in conversion, so no point deferring. * `wouldUploadMarkup` notes in dry-run output — n/a, no upload. Verified on https://huly.aaravlabs.com (real server): * Posted via the local build: `node dist/index.js comment add --issue HULY-20 --body-file /tmp/kilo/huly20-fix2.html`. * message field stores inline prosemirror JSON starting with `{"type":"doc","content":[{"type":"heading",...}]}`. * Web UI's MessageViewer can render this directly without any collaborator lookup.
1 parent 0107d56 commit 01172ea

4 files changed

Lines changed: 49 additions & 11 deletions

File tree

‎packages/cli/src/resources/activity.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from '../output/format.js'
1919
import { withSpinner } from '../output/progress.js'
2020
import { CliError, ExitCode } from '../output/errors.js'
21+
import { htmlToMarkup } from './_helpers.js'
2122

2223
type ActivityMessage = Doc & {
2324
message?: string
@@ -371,7 +372,11 @@ export async function addReply(opts: ReplyOpts): Promise<void> {
371372
const client = await connectCli({ url: opts.url, workspace: opts.workspace })
372373
try {
373374
const { id, doc } = await fetchActivity(client, opts.target)
374-
const data: Record<string, unknown> = { message: opts.body }
375+
// HULY-20: ActivityMessage.message is TypeMarkup (inline prosemirror
376+
// JSON string). Convert HTML → prosemirror locally and store inline,
377+
// NOT a MarkupBlobRef — the web UI renders via MessageViewer which
378+
// calls markupToJSON(message) directly with no collaborator lookup.
379+
const data: Record<string, unknown> = { message: htmlToMarkup(opts.body) }
375380
const rid = await withSpinner(
376381
'Replying…',
377382
() =>
@@ -408,11 +413,12 @@ export async function updateReply(
408413
// attachedTo pointing at the parent). `message` lives on the reply
409414
// doc itself — updateDoc, NOT updateCollection against the parent's
410415
// 'replies' tuple (which doesn't exist).
416+
// HULY-20: inline prosemirror JSON, same as addReply.
411417
await client.updateDoc(
412418
ACTIVITY_CLASS,
413419
doc.space as unknown as Ref<Space>,
414420
id as Ref<Doc>,
415-
{ message: opts.body, modifiedOn: Date.now() } as any,
421+
{ message: htmlToMarkup(opts.body), modifiedOn: Date.now() } as any,
416422
)
417423
updated('updated reply', refString(id))
418424
} finally {

‎packages/cli/src/resources/approvals.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from '../output/format.js'
1919
import { withSpinner } from '../output/progress.js'
2020
import { CliError, ExitCode } from '../output/errors.js'
21+
import { htmlToMarkup } from './_helpers.js'
2122

2223
const REQUEST_STATUSES = ['Active', 'Completed', 'Rejected', 'Cancelled'] as const
2324
type RequestStatus = (typeof REQUEST_STATUSES)[number]
@@ -297,7 +298,10 @@ export async function commentOnApproval(opts: CommentOpts): Promise<void> {
297298
const client = await connectCli({ url: opts.url, workspace: opts.workspace })
298299
try {
299300
const { id, doc } = await fetchRequest(client, opts.ref!)
300-
const data: Record<string, unknown> = { message: opts.body }
301+
// HULY-20: approval comments are ChatMessage instances — message is
302+
// TypeMarkup (inline prosemirror JSON). Inline storage, same as
303+
// chat comments.
304+
const data: Record<string, unknown> = { message: htmlToMarkup(opts.body) }
301305
if (opts.decision) data.decision = opts.decision
302306
const cid = await withSpinner(
303307
'Commenting…',

‎packages/cli/src/resources/channel.ts‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { normalizeSocialKey } from '../auth/social.js'
66
import { shouldJson, json, table, COLUMNS, C, success, updated, bulkRemoved } from '../output/format.js'
77
import { withSpinner } from '../output/progress.js'
88
import { CliError, ExitCode } from '../output/errors.js'
9+
import { htmlToMarkup } from './_helpers.js'
910

1011
type Channel = Doc & {
1112
name: string
@@ -546,8 +547,12 @@ export async function sendChannelMessage(
546547
const client = await connectCli({ url: opts.url, workspace: opts.workspace })
547548
try {
548549
const channel = await resolveChannel(client, ref)
550+
// HULY-20: ChatMessage.message is TypeMarkup (inline prosemirror JSON
551+
// string), rendered via MessageViewer with no collaborator lookup. Store
552+
// the converted prosemirror-JSON string inline — NOT a MarkupBlobRef.
553+
const messageMarkup = htmlToMarkup(body)
549554
const data: Record<string, unknown> = {
550-
message: body,
555+
message: messageMarkup,
551556
}
552557
if (opts.dryRun) {
553558
console.log('would send channel message:')
@@ -610,8 +615,10 @@ export async function updateChannelMessage(
610615
if (msg.attachedTo !== channel._id) {
611616
throw new CliError(ExitCode.NotFound, 'message does not belong to this channel')
612617
}
618+
// HULY-20: inline prosemirror, same as sendChannelMessage.
619+
const messageMarkup = htmlToMarkup(body)
613620
const data: Record<string, unknown> = {
614-
message: body,
621+
message: messageMarkup,
615622
editedOn: Date.now(),
616623
}
617624
if (opts.dryRun) {
@@ -737,8 +744,11 @@ export async function addThreadReply(
737744
try {
738745
const parent = await client.findOne(CHAT_MESSAGE_CLASS, { _id: targetId as Ref<ChatMessage> })
739746
if (!parent) throw new CliError(ExitCode.NotFound, `target message ${targetId} not found`)
747+
// HULY-20: ThreadMessage.message is TypeMarkup (inline prosemirror
748+
// JSON string). Inline storage, same as channel/dm messages.
749+
const messageMarkup = htmlToMarkup(body)
740750
const data: Record<string, unknown> = {
741-
message: body,
751+
message: messageMarkup,
742752
}
743753
if (opts.dryRun) {
744754
console.log('would add thread reply:')
@@ -791,7 +801,9 @@ export async function updateThreadReply(
791801
_id: replyId as Ref<ChatMessage>,
792802
})
793803
if (!reply) throw new CliError(ExitCode.NotFound, `thread reply ${replyId} not found`)
794-
const data: Record<string, unknown> = { message: body, editedOn: Date.now() }
804+
// HULY-20: inline prosemirror, same as addThreadReply.
805+
const messageMarkup = htmlToMarkup(body)
806+
const data: Record<string, unknown> = { message: messageMarkup, editedOn: Date.now() }
795807
if (opts.dryRun) {
796808
console.log(`would update thread reply ${replyId}:`)
797809
console.log(
@@ -1039,7 +1051,10 @@ export async function sendDmMessage(
10391051
})
10401052
const dm = await client.findOne(DM_CLASS, { _id: dmId as Ref<DirectMessage> })
10411053
if (!dm) throw new CliError(ExitCode.NotFound, `DM ${dmRef} not found`)
1042-
const data: Record<string, unknown> = { message: body }
1054+
// HULY-20: DM messages are ChatMessage instances — message is TypeMarkup
1055+
// (inline prosemirror JSON). Inline storage, same as channel messages.
1056+
const messageMarkup = htmlToMarkup(body)
1057+
const data: Record<string, unknown> = { message: messageMarkup }
10431058
if (opts.dryRun) {
10441059
console.log('would send DM:')
10451060
console.log(

‎packages/cli/src/resources/comment.ts‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { shouldJson, json, table, COLUMNS, success, updated, bulkRemoved } from
66
import { withSpinner } from '../output/progress.js'
77
import { CliError, ExitCode } from '../output/errors.js'
88
import { readEnv } from '../auth/env.js'
9+
import { htmlToMarkup } from './_helpers.js'
910

1011
type ChatMessage = Doc & {
1112
message: string
@@ -85,6 +86,15 @@ export async function addComment(opts: {
8586
})
8687
const issue = await client.findOne(CLASS.Issue as Ref<Class<Doc>>, { _id: issueId })
8788
if (!issue) throw new CliError(ExitCode.NotFound, `issue ${opts.issue} not found`)
89+
// HULY-20: ChatMessage.message is typed `TypeMarkup()` (inline
90+
// prosemirror-JSON string). The web UI renders it via MessageViewer,
91+
// which calls `markupToJSON(message)` directly with NO collaborator
92+
// resolution — so storing a MarkupBlobRef string in `message` would
93+
// render as literal text. Convert HTML → prosemirror JSON locally and
94+
// store the JSON string inline. (Document.content uses
95+
// TypeCollaborativeDoc and stores a MarkupBlobRef, but that's a
96+
// different attribute type and a different UI renderer.)
97+
const messageMarkup = htmlToMarkup(body)
8898
const id = await withSpinner(
8999
'Adding comment…',
90100
() =>
@@ -94,13 +104,13 @@ export async function addComment(opts: {
94104
issueId,
95105
CLASS.Issue,
96106
'comments',
97-
{ message: body } as any,
107+
{ message: messageMarkup } as any,
98108
),
99109
opts,
100110
)
101111
invalidateIndex(client, CLASS.ChatMessage)
102112
if (shouldJson({ json: opts.json, ci: opts.ci })) {
103-
json({ _id: id, attachedTo: issueId, message: body })
113+
json({ _id: id, attachedTo: issueId, message: messageMarkup })
104114
} else {
105115
success('added comment', `on ${opts.issue}`, id)
106116
}
@@ -130,14 +140,17 @@ export async function updateComment(
130140
})
131141
const comment = await client.findOne(CLASS.ChatMessage as Ref<Class<ChatMessage>>, { _id: commentId })
132142
if (!comment) throw new CliError(ExitCode.NotFound, `comment ${ref} not found`)
143+
// HULY-20: same inline-prosemirror conversion as addComment. Storing a
144+
// MarkupBlobRef string here would render as literal text in the web UI.
145+
const messageMarkup = htmlToMarkup(body)
133146
await withSpinner(
134147
'Updating comment…',
135148
() =>
136149
client.updateDoc(
137150
CLASS.ChatMessage as Ref<Class<ChatMessage>>,
138151
(comment as Doc).space as Ref<Doc>,
139152
commentId as Ref<Doc>,
140-
{ message: body, editedOn: Date.now() } as any,
153+
{ message: messageMarkup, editedOn: Date.now() } as any,
141154
),
142155
opts,
143156
)

0 commit comments

Comments
 (0)