feat(composer): compose spoiler, annotation, and tooltip controls#1491
Conversation
|
CI note, corrected after rebase: Desktop Smoke E2E had repeatedly failed the same pre-existing scroll-history assertion by half a pixel (observed |
427be3e to
9dc2d08
Compare
Move the text spoiler toggle into the expanded formatting toolbar and make it text-only — it no longer mirrors onto pending media attachments. Media spoilers are now toggled per image/video via a new control in the attachment lightbox's top-right cluster, next to the close button and backed by the existing per-URL spoilered set. The lightbox toggle is placed left of where the upcoming image edit/draw control (PR #1488) will sit, with a note to hide it while edit mode is active once that feature lands. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
9dc2d08 to
f32d8fd
Compare
…image-canvas * origin/tho/spoiler-formatting-ingress: feat(composer): split text and media spoiler controls Stacks #1488 on top of #1491: annotation (MediaAttachmentItem) stays the media path, and #1491's per-attachment spoiler toggle now lives in that lightbox's view-mode action bar. Text spoiler remains text-only; the superseded AttachmentMediaLightbox is removed. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…tooltip-pointer-events Stacks #1272 on top of #1488 (whose branch now contains #1491): the tooltip dismiss-on-leave sweep is applied to the final combined composer UI. The toolbar spoiler button removed by #1491 stays removed, and the annotation MediaAttachmentItem's tooltips adopt disableHoverableContent. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
#1272) Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co>
wesbillman
left a comment
There was a problem hiding this comment.
Reviewed the full diff (18 files, +1406/−274) as the collapsed #1491 + #1488 + #1272 stack. Traced the upload/cancel accounting in useMediaUpload, the new Rust IPC command's validation path, and the lightbox key/remount trick. The architecture is sound and the sharp edges are handled well:
uploadEditedAttachment/revertAttachmentpreview-count andfinishUploadbookkeeping balances on success, cancel, and error paths (cancel andonUploadErroreach own their decrement — verified againstcancelUpload).- Keying
MediaAttachmentItembyoriginalUrl ?? attachment.urlis a neat trick: edit-save and revert both preserve the key, so the open dialog never remounts. Correct in both directions. fetch_media_bytesreuses the exact SSRF/origin/size-cap/MIME validation as the download commands — no new attack surface beyond the existing envelope.- Spoiler-membership migration across URL swaps (
useAttachmentEditing) is small and covered by the e2e spec. - The pruning effect on
pendingImetaand the same-callback batched slot-swap + originals re-key are consistent (React batches both setters), so revert state can't observe a torn intermediate.
Findings, none blocking:
-
Dead code:
SimpleImageLightbox's newactionsprop has no consumer. After the collapse,ComposerAttachmentsbuilds its ownDialogPrimitive-based lightbox, and the only remainingSimpleImageLightboxconsumer (ViewImageToolPreview) doesn't passactions. The PR body still describes this as an additive extension for the composer, but the final composed code never uses it — suggest revertingSimpleImageLightbox.tsxtomainunless a follow-up needs it. -
Perf nit:
fetch_media_bytesreturnsVec<u8>as a JSON number array over IPC. With the 50 MiB cap that's a worst-case ~150 MB JSON payload to deserialize intonumber[].tauri::ipc::Responsewould send raw bytes. Fine to ship as-is for typical image sizes; worth a follow-up if large images feel slow to enter edit mode. -
UX nit: Escape during save exits edit mode but doesn't abort the in-flight upload — the save still completes and swaps the attachment after the user apparently backed out. Low stakes (the swap is revertable), just noting it's a known race.
-
Revert affordance is lost across a draft/channel round-trip — the originals map is in-memory and pruned when the attachment leaves
pendingImeta. The code comment documents this as intentional; agreed it's the right scope, flagging only so it's a recorded decision. -
Several of the new comment blocks (e.g. the 8-line tooltip rationale in
MessageComposerToolbar, the multi-line lightbox-close rationale) restate design justification that lives in the PR body. House style here trends much leaner — consider trimming to one line each.
CI: all 26 checks green, including both Desktop E2E Integration shards which run the three new/updated specs. Approving — #1 is the only change I'd actually like to see before merge, and it's a deletion.
…mments Revert SimpleImageLightbox to main: the collapsed stack's composer lightbox owns its own controls, so the optional actions slot had no remaining consumer. Also trim the tooltip-dismissal and save-close rationale comments to house-style one-liners; the full reasoning lives in the PR body. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
|
Addressed Wes’s pre-merge cleanup feedback in
Keeping the other notes as follow-up material on a stacked branch, since they were called out as non-blocking. |
Radix react-dismissable-layer >=1.1.14 (renovate bump #1524 on main) registers a stable Escape listener, so onEscapeKeyDown captured a stale mode closure: Escape in canvas-edit mode closed the lightbox instead of returning to view mode. Read the current mode from a ref so the handler always sees the live value. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Follow-ups from the #1491 review: - fetch_media_bytes now replies with tauri::ipc::Response so image bytes cross IPC as a raw buffer instead of a JSON number array (~3x smaller to serialize at the 50 MiB cap). TS wrapper reads an ArrayBuffer and the e2e mock returns the same shape. - Escape no longer exits canvas edit mode while a save upload is in flight: the editor reports its saving state up, and the lightbox's Escape handler ignores the key mid-save so a dismissal can't look like a cancel while the attachment swap still lands. - Documented the in-memory-only revert map as an explicit non-goal for draft round-trips, per the review's recorded-decision ask. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Follow-ups from the #1491 review: - fetch_media_bytes now replies with tauri::ipc::Response so image bytes cross IPC as a raw buffer instead of a JSON number array (~3x smaller to serialize at the 50 MiB cap). TS wrapper reads an ArrayBuffer and the e2e mock returns the same shape. - Escape no longer exits canvas edit mode while a save upload is in flight: the editor reports its saving state up, and the lightbox's Escape handler ignores the key mid-save so a dismissal can't look like a cancel while the attachment swap still lands. - Documented the in-memory-only revert map as an explicit non-goal for draft round-trips, per the review's recorded-decision ask. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co>
…ivity * origin/main: fix(desktop): hydrate reactions for Inbox context messages (#1596) fix: cleanup old screenshots that my agents committed (#1598) chore(release): release Buzz Desktop version 0.3.46 (#1585) fix(desktop): preserve agent model/provider when persona snapshot fields are blank (#1583) feat(acp,desktop): identify and reap stale agent harness processes (#1582) feat(desktop): active-draft badge, send-from-drafts confirm dialog, thread-deleted state (#1581) fix(desktop): treat baked build env vars as satisfying required agent config (#1580) feat(desktop): add "Copy image" to image right-click context menu (#1579) fix(nest): use buzz-dev symlink name for dev builds (#1587) fix(composer): address image-editor follow-up nits on #1491 (#1565) fix(desktop): render black static boot screen (#1570) feat(agents): group activity tool bursts (#1571) feat(desktop): aggregated overview rail, commit detail page, and full breadcrumbs (#1573) fix(desktop): fetch profiles for reaction actors and thread-reply authors (#1550) refactor(desktop): unify EditAgentDialog styling with PersonaDialog (#1540) feat(desktop): add 10-minute message grouping window (#1578) feat(desktop): unify sidebar section actions into a per-section ⋮ menu (#1577) feat(desktop): emoji avatar picker for agents + reliable picker scroll (#1576) Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
* origin/main: docs(readme): add Getting started section routing install paths (#1606) fix(desktop): restrict shared-agent sync to dev data dirs (#1597) feat(desktop): restart-required badge from spawn-time config hash (#1602) feat(desktop): boot-time reconcile of managed agents to relay events (#1601) feat(desktop): canonical <PubKey> component — hover to view/copy full keys, owner "you" labels (#1589) fix(desktop): hydrate reactions for Inbox context messages (#1596) fix: cleanup old screenshots that my agents committed (#1598) chore(release): release Buzz Desktop version 0.3.46 (#1585) fix(desktop): preserve agent model/provider when persona snapshot fields are blank (#1583) feat(acp,desktop): identify and reap stale agent harness processes (#1582) feat(desktop): active-draft badge, send-from-drafts confirm dialog, thread-deleted state (#1581) fix(desktop): treat baked build env vars as satisfying required agent config (#1580) feat(desktop): add "Copy image" to image right-click context menu (#1579) fix(nest): use buzz-dev symlink name for dev builds (#1587) fix(composer): address image-editor follow-up nits on #1491 (#1565) fix(desktop): render black static boot screen (#1570) feat(agents): group activity tool bursts (#1571) feat(desktop): aggregated overview rail, commit detail page, and full breadcrumbs (#1573) fix(desktop): fetch profiles for reaction actors and thread-reply authors (#1550)
Category: improvement
User Impact: Users can mark text and individual image/video attachments as spoilers independently, annotate uploaded images, and get composer tooltips that dismiss correctly from the final combined composer UI.
Problem: The composer’s previous spoiler model coupled text spoilering to pending media, so marking text as spoiler could also hide every attached image/video. In parallel, uploaded-image annotation and tooltip-dismissal fixes touched the same composer attachment/lightbox surfaces, making the intended combined behavior easy to lose during conflict resolution.
Solution: This PR is now the collapsed composer stack for #1491 + #1488 + #1272. It keeps text spoiler as a text-only formatting action, moves media spoiler control into each attachment’s annotation-capable lightbox, preserves uploaded-image draw/save/revert behavior, and applies tooltip dismissal to the controls that survive in the final combined UI.
Final composed behavior
MediaAttachmentItempath with view/edit modes.SimpleImageLightboxis untouched (reverted tomain): the composer lightbox owns its own controls, so the shared component needed no extension.Stack / merge history
This branch intentionally carries the combined composer stack so the merge order is traceable in GitHub history instead of relying on code comments:
#1487 and #1490 stayed independent solo PRs against
main.Validation
biome check✅tsc✅build✅cargo check✅Screenshots
Lightbox view-mode action bar with spoiler / draw / close:
Composer thumbnail after per-attachment spoiler:
Notes