Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/cli/src/resources/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '../output/format.js'
import { withSpinner } from '../output/progress.js'
import { CliError, ExitCode } from '../output/errors.js'
import { htmlToMarkup } from './_helpers.js'

type ActivityMessage = Doc & {
message?: string
Expand Down Expand Up @@ -371,7 +372,11 @@ export async function addReply(opts: ReplyOpts): Promise<void> {
const client = await connectCli({ url: opts.url, workspace: opts.workspace })
try {
const { id, doc } = await fetchActivity(client, opts.target)
const data: Record<string, unknown> = { message: opts.body }
// HULY-20: ActivityMessage.message is TypeMarkup (inline prosemirror
// JSON string). Convert HTML → prosemirror locally and store inline,
// NOT a MarkupBlobRef — the web UI renders via MessageViewer which
// calls markupToJSON(message) directly with no collaborator lookup.
const data: Record<string, unknown> = { message: htmlToMarkup(opts.body) }
const rid = await withSpinner(
'Replying…',
() =>
Expand Down Expand Up @@ -408,11 +413,12 @@ export async function updateReply(
// attachedTo pointing at the parent). `message` lives on the reply
// doc itself — updateDoc, NOT updateCollection against the parent's
// 'replies' tuple (which doesn't exist).
// HULY-20: inline prosemirror JSON, same as addReply.
await client.updateDoc(
ACTIVITY_CLASS,
doc.space as unknown as Ref<Space>,
id as Ref<Doc>,
{ message: opts.body, modifiedOn: Date.now() } as any,
{ message: htmlToMarkup(opts.body), modifiedOn: Date.now() } as any,
)
updated('updated reply', refString(id))
} finally {
Expand Down
6 changes: 5 additions & 1 deletion packages/cli/src/resources/approvals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '../output/format.js'
import { withSpinner } from '../output/progress.js'
import { CliError, ExitCode } from '../output/errors.js'
import { htmlToMarkup } from './_helpers.js'

const REQUEST_STATUSES = ['Active', 'Completed', 'Rejected', 'Cancelled'] as const
type RequestStatus = (typeof REQUEST_STATUSES)[number]
Expand Down Expand Up @@ -297,7 +298,10 @@ export async function commentOnApproval(opts: CommentOpts): Promise<void> {
const client = await connectCli({ url: opts.url, workspace: opts.workspace })
try {
const { id, doc } = await fetchRequest(client, opts.ref!)
const data: Record<string, unknown> = { message: opts.body }
// HULY-20: approval comments are ChatMessage instances — message is
// TypeMarkup (inline prosemirror JSON). Inline storage, same as
// chat comments.
const data: Record<string, unknown> = { message: htmlToMarkup(opts.body) }
if (opts.decision) data.decision = opts.decision
const cid = await withSpinner(
'Commenting…',
Expand Down
31 changes: 25 additions & 6 deletions packages/cli/src/resources/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { normalizeSocialKey } from '../auth/social.js'
import { shouldJson, json, table, COLUMNS, C, success, updated, bulkRemoved } from '../output/format.js'
import { withSpinner } from '../output/progress.js'
import { CliError, ExitCode } from '../output/errors.js'
import { htmlToMarkup } from './_helpers.js'

type Channel = Doc & {
name: string
Expand Down Expand Up @@ -546,8 +547,12 @@ export async function sendChannelMessage(
const client = await connectCli({ url: opts.url, workspace: opts.workspace })
try {
const channel = await resolveChannel(client, ref)
// HULY-20: ChatMessage.message is TypeMarkup (inline prosemirror JSON
// string), rendered via MessageViewer with no collaborator lookup. Store
// the converted prosemirror-JSON string inline — NOT a MarkupBlobRef.
const messageMarkup = htmlToMarkup(body)
const data: Record<string, unknown> = {
message: body,
message: messageMarkup,
}
if (opts.dryRun) {
console.log('would send channel message:')
Expand Down Expand Up @@ -610,8 +615,10 @@ export async function updateChannelMessage(
if (msg.attachedTo !== channel._id) {
throw new CliError(ExitCode.NotFound, 'message does not belong to this channel')
}
// HULY-20: inline prosemirror, same as sendChannelMessage.
const messageMarkup = htmlToMarkup(body)
const data: Record<string, unknown> = {
message: body,
message: messageMarkup,
editedOn: Date.now(),
}
if (opts.dryRun) {
Expand Down Expand Up @@ -737,8 +744,11 @@ export async function addThreadReply(
try {
const parent = await client.findOne(CHAT_MESSAGE_CLASS, { _id: targetId as Ref<ChatMessage> })
if (!parent) throw new CliError(ExitCode.NotFound, `target message ${targetId} not found`)
// HULY-20: ThreadMessage.message is TypeMarkup (inline prosemirror
// JSON string). Inline storage, same as channel/dm messages.
const messageMarkup = htmlToMarkup(body)
const data: Record<string, unknown> = {
message: body,
message: messageMarkup,
}
if (opts.dryRun) {
console.log('would add thread reply:')
Expand Down Expand Up @@ -791,7 +801,9 @@ export async function updateThreadReply(
_id: replyId as Ref<ChatMessage>,
})
if (!reply) throw new CliError(ExitCode.NotFound, `thread reply ${replyId} not found`)
const data: Record<string, unknown> = { message: body, editedOn: Date.now() }
// HULY-20: inline prosemirror, same as addThreadReply.
const messageMarkup = htmlToMarkup(body)
const data: Record<string, unknown> = { message: messageMarkup, editedOn: Date.now() }
if (opts.dryRun) {
console.log(`would update thread reply ${replyId}:`)
console.log(
Expand Down Expand Up @@ -1004,6 +1016,11 @@ export async function sendDmMessage(
},
): Promise<void> {
const body = await readMessageBody(opts)
// HULY-20: ChatMessage.message is TypeMarkup (inline prosemirror JSON).
// Compute the converted markup up-front so both dry-run previews (the
// --person one below and the resolved-DM one in the try-block) reflect
// the value that would actually be stored, not the raw HTML input.
const messageMarkup = htmlToMarkup(body)
// --person <email>: resolve or auto-create DM, then send.
if (opts.person !== undefined && opts.person !== '') {
// CLI-04: forward --dry-run so createDm doesn't actually mutate state
Expand All @@ -1021,7 +1038,7 @@ export async function sendDmMessage(
{
wouldCreateDm: { person: opts.person },
wouldSendTo: dmRef,
message: body,
message: messageMarkup,
},
null,
2,
Expand All @@ -1039,7 +1056,9 @@ export async function sendDmMessage(
})
const dm = await client.findOne(DM_CLASS, { _id: dmId as Ref<DirectMessage> })
if (!dm) throw new CliError(ExitCode.NotFound, `DM ${dmRef} not found`)
const data: Record<string, unknown> = { message: body }
// HULY-20: messageMarkup was computed above (before the --person branch)
// so both dry-run previews show the same value that would be stored.
const data: Record<string, unknown> = { message: messageMarkup }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: sendDmMessage has two dry-run preview paths; the first one (lines 1029-1043, triggered when --person is provided) was not updated alongside the second path.

The first dry-run preview at line 1036 still emits the raw HTML body (message: body,), even though the actual stored value (line 1057) is the converted markup. The second dry-run path (lines 1058-1073) now correctly shows the converted markup via data: { message: messageMarkup }. Before this PR both paths were consistent (both showed raw HTML); the change to the second path makes them inconsistent.

This contradicts the PR description's claim that "dry-run preview now shows the converted prosemirror JSON (which is what would be stored)". A user running huly dm message send --person <x> --dry-run --body "<p>hello</p>" will see raw HTML in the preview, but the actual stored value will be the converted markup. This is misleading for dry-run previews.

Consider either (a) moving the messageMarkup = htmlToMarkup(body) computation above the if (opts.person !== undefined && opts.person !== '') block so both dry-run paths can use it, or (b) updating the first dry-run path's JSON to show message: htmlToMarkup(body) directly.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

if (opts.dryRun) {
console.log('would send DM:')
console.log(
Expand Down
19 changes: 16 additions & 3 deletions packages/cli/src/resources/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { shouldJson, json, table, COLUMNS, success, updated, bulkRemoved } from
import { withSpinner } from '../output/progress.js'
import { CliError, ExitCode } from '../output/errors.js'
import { readEnv } from '../auth/env.js'
import { htmlToMarkup } from './_helpers.js'

type ChatMessage = Doc & {
message: string
Expand Down Expand Up @@ -85,6 +86,15 @@ export async function addComment(opts: {
})
const issue = await client.findOne(CLASS.Issue as Ref<Class<Doc>>, { _id: issueId })
if (!issue) throw new CliError(ExitCode.NotFound, `issue ${opts.issue} not found`)
// HULY-20: ChatMessage.message is typed `TypeMarkup()` (inline
// prosemirror-JSON string). The web UI renders it via MessageViewer,
// which calls `markupToJSON(message)` directly with NO collaborator
// resolution — so storing a MarkupBlobRef string in `message` would
// render as literal text. Convert HTML → prosemirror JSON locally and
// store the JSON string inline. (Document.content uses
// TypeCollaborativeDoc and stores a MarkupBlobRef, but that's a
// different attribute type and a different UI renderer.)
const messageMarkup = htmlToMarkup(body)
const id = await withSpinner(
'Adding comment…',
() =>
Expand All @@ -94,13 +104,13 @@ export async function addComment(opts: {
issueId,
CLASS.Issue,
'comments',
{ message: body } as any,
{ message: messageMarkup } as any,
),
opts,
)
invalidateIndex(client, CLASS.ChatMessage)
if (shouldJson({ json: opts.json, ci: opts.ci })) {
json({ _id: id, attachedTo: issueId, message: body })
json({ _id: id, attachedTo: issueId, message: messageMarkup })
} else {
success('added comment', `on ${opts.issue}`, id)
}
Expand Down Expand Up @@ -130,14 +140,17 @@ export async function updateComment(
})
const comment = await client.findOne(CLASS.ChatMessage as Ref<Class<ChatMessage>>, { _id: commentId })
if (!comment) throw new CliError(ExitCode.NotFound, `comment ${ref} not found`)
// HULY-20: same inline-prosemirror conversion as addComment. Storing a
// MarkupBlobRef string here would render as literal text in the web UI.
const messageMarkup = htmlToMarkup(body)
await withSpinner(
'Updating comment…',
() =>
client.updateDoc(
CLASS.ChatMessage as Ref<Class<ChatMessage>>,
(comment as Doc).space as Ref<Doc>,
commentId as Ref<Doc>,
{ message: body, editedOn: Date.now() } as any,
{ message: messageMarkup, editedOn: Date.now() } as any,
),
opts,
)
Expand Down
Loading