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
13 changes: 11 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 { uploadMarkup, generateId } from './_helpers.js'

type ActivityMessage = Doc & {
message?: string
Expand Down Expand Up @@ -371,7 +372,12 @@ 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: upload the markup first so `message` stores a MarkupBlobRef
// (prosemirror-JSON-backed), not raw HTML. The id is generated up-front
// so the markup blob's collabId and the reply doc share the same id.
const newReplyId = generateId()
const messageRef = await uploadMarkup(client, ACTIVITY_CLASS, newReplyId, 'message', opts.body, 'markup')
const data: Record<string, unknown> = { message: messageRef }
const rid = await withSpinner(
'Replying…',
() =>
Expand All @@ -382,6 +388,7 @@ export async function addReply(opts: ReplyOpts): Promise<void> {
ACTIVITY_CLASS,
'replies',
data as any,
newReplyId,
),
opts,
)
Expand All @@ -408,11 +415,13 @@ 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: upload markup first so the update lands a MarkupBlobRef.
const messageRef = await uploadMarkup(client, ACTIVITY_CLASS, id, 'message', opts.body, 'markup')
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: messageRef, modifiedOn: Date.now() } as any,
)
updated('updated reply', refString(id))
} finally {
Expand Down
16 changes: 15 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 { uploadMarkup, generateId } 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,19 @@ 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: upload markup so `message` stores a MarkupBlobRef, not raw
// HTML. Generate id up-front so the markup blob's collabId matches
// the ChatMessage doc's id. No --dry-run flag on this surface today.
const newCommentId = generateId()
const messageRef = await uploadMarkup(
client,
'chunter:class:ChatMessage' as Ref<Class<Doc>>,
newCommentId,
'message',
opts.body,
'markup',
)
const data: Record<string, unknown> = { message: messageRef }
if (opts.decision) data.decision = opts.decision
const cid = await withSpinner(
'Commenting…',
Expand All @@ -309,6 +322,7 @@ export async function commentOnApproval(opts: CommentOpts): Promise<void> {
REQUEST_CLASS,
'comments',
data as any,
newCommentId,
),
opts,
)
Expand Down
90 changes: 85 additions & 5 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 { uploadMarkup, generateId } from './_helpers.js'

type Channel = Doc & {
name: string
Expand Down Expand Up @@ -546,8 +547,21 @@ export async function sendChannelMessage(
const client = await connectCli({ url: opts.url, workspace: opts.workspace })
try {
const channel = await resolveChannel(client, ref)
// HULY-20: pre-upload the markup so `message` stores a MarkupBlobRef,
// not raw HTML. Generate the id up-front so the markup blob's collabId
// matches the ChatMessage doc's id (the platform keys the blob by
// `${class}:${id}:${attribute}`).
const newMessageId = generateId()
// Defer the actual upload until after the dry-run guard below — same
// pattern as the HULY-8 updateIssue fix. Dry-run must not write to
// MinIO. The dry-run preview shows `message: ''` (the placeholder) +
// a `wouldUploadMarkup` note so users see what would happen.
let messageRef = ''
if (!opts.dryRun) {
messageRef = await uploadMarkup(client, CHAT_MESSAGE_CLASS, newMessageId, 'message', body, 'markup')
}
const data: Record<string, unknown> = {
message: body,
message: messageRef,
}
if (opts.dryRun) {
console.log('would send channel message:')
Expand All @@ -560,6 +574,12 @@ export async function sendChannelMessage(
attachedToClass: CHANNEL_CLASS,
collection: 'messages',
data,
wouldUploadMarkup: {
objectClass: CHAT_MESSAGE_CLASS,
objectId: newMessageId,
objectAttr: 'message',
bodyBytes: body.length,
},
},
null,
2,
Expand All @@ -575,6 +595,7 @@ export async function sendChannelMessage(
CHANNEL_CLASS,
'messages',
data as any,
newMessageId,
),
)
if (shouldJson({ json: opts.json, ci: opts.ci })) {
Expand Down Expand Up @@ -610,8 +631,23 @@ export async function updateChannelMessage(
if (msg.attachedTo !== channel._id) {
throw new CliError(ExitCode.NotFound, 'message does not belong to this channel')
}
// HULY-20: upload the markup so the update lands a MarkupBlobRef in
// `message`, not raw HTML. The existing messageId is reused so the
// markup blob's collabId stays consistent across updates. Deferred
// until after the dry-run guard below (same pattern as HULY-8).
let messageRef = ''
if (!opts.dryRun) {
messageRef = await uploadMarkup(
client,
CHAT_MESSAGE_CLASS,
messageId as Ref<Doc>,
'message',
body,
'markup',
)
}
const data: Record<string, unknown> = {
message: body,
message: messageRef,
editedOn: Date.now(),
}
if (opts.dryRun) {
Expand Down Expand Up @@ -737,8 +773,24 @@ 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: upload markup first so `message` stores a MarkupBlobRef.
// The reply lives in chunter:class:ThreadMessage (subclass of
// ChatMessage), and the markup blob is keyed by that subclass id.
// Deferred until after the dry-run guard (same pattern as HULY-8).
const newReplyId = generateId()
let messageRef = ''
if (!opts.dryRun) {
messageRef = await uploadMarkup(
client,
'chunter:class:ThreadMessage' as Ref<Class<Doc>>,
newReplyId,
'message',
body,
'markup',
)
}
const data: Record<string, unknown> = {
message: body,
message: messageRef,
}
if (opts.dryRun) {
console.log('would add thread reply:')
Expand All @@ -759,6 +811,7 @@ export async function addThreadReply(
CHAT_MESSAGE_CLASS,
'replies',
data as any,
newReplyId,
),
)
if (shouldJson({ json: opts.json, ci: opts.ci })) {
Expand Down Expand Up @@ -791,7 +844,20 @@ 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: upload markup so the update lands a MarkupBlobRef. Deferred
// until after the dry-run guard (same pattern as HULY-8).
let messageRef = ''
if (!opts.dryRun) {
messageRef = await uploadMarkup(
client,
'chunter:class:ThreadMessage' as Ref<Class<Doc>>,
replyId as Ref<Doc>,
'message',
body,
'markup',
)
}
const data: Record<string, unknown> = { message: messageRef, editedOn: Date.now() }
if (opts.dryRun) {
console.log(`would update thread reply ${replyId}:`)
console.log(
Expand Down Expand Up @@ -1039,7 +1105,14 @@ 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: upload markup so `message` stores a MarkupBlobRef, not raw
// HTML. Deferred until after the dry-run guard (same pattern as HULY-8).
const newMessageId = generateId()
let messageRef = ''
if (!opts.dryRun) {
messageRef = await uploadMarkup(client, CHAT_MESSAGE_CLASS, newMessageId, 'message', body, 'markup')
}
const data: Record<string, unknown> = { message: messageRef }
if (opts.dryRun) {
console.log('would send DM:')
console.log(
Expand All @@ -1051,6 +1124,12 @@ export async function sendDmMessage(
attachedToClass: DM_CLASS,
collection: 'messages',
data,
wouldUploadMarkup: {
objectClass: CHAT_MESSAGE_CLASS,
objectId: newMessageId,
objectAttr: 'message',
bodyBytes: body.length,
},
},
null,
2,
Expand All @@ -1066,6 +1145,7 @@ export async function sendDmMessage(
DM_CLASS,
'messages',
data as any,
newMessageId,
),
)
if (shouldJson({ json: opts.json, ci: opts.ci })) {
Expand Down
31 changes: 28 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 { uploadMarkup, generateId } from './_helpers.js'

type ChatMessage = Doc & {
message: string
Expand Down Expand Up @@ -85,6 +86,19 @@ 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: pre-upload the markup so the `message` field stores a
// MarkupBlobRef (pointing to a prosemirror-JSON blob in MinIO) instead of
// a raw HTML string. Generating the id up-front lets us use the same
// id for both the markup blob's collabId and the ChatMessage doc.
const newMessageId = generateId()
const messageRef = await uploadMarkup(
client,
CLASS.ChatMessage as Ref<Class<Doc>>,
newMessageId,
'message',
body,
'markup',
)
const id = await withSpinner(
'Adding comment…',
() =>
Expand All @@ -94,13 +108,14 @@ export async function addComment(opts: {
issueId,
CLASS.Issue,
'comments',
{ message: body } as any,
{ message: messageRef } as any,
newMessageId,
),
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: messageRef })
} else {
success('added comment', `on ${opts.issue}`, id)
}
Expand Down Expand Up @@ -130,14 +145,24 @@ 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 fix as addComment — upload the markup first so the
// update lands a MarkupBlobRef in `message`, not raw HTML.
const messageRef = await uploadMarkup(
client,
CLASS.ChatMessage as Ref<Class<Doc>>,
commentId,
'message',
body,
'markup',
)
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: messageRef, editedOn: Date.now() } as any,
),
opts,
)
Expand Down
Loading