Skip to content

feat(browser): annotation v2 S3 — browser comments as composer attachments, Cmd+Enter send - #454

Merged
bubbleptr merged 2 commits into
feat/browser-annotation-v2-comment-storefrom
feat/browser-annotation-v2-composer
Oct 10, 2026
Merged

bubbleptr merged 2 commits into
feat/browser-annotation-v2-comment-storefrom
feat/browser-annotation-v2-composer

Conversation

@bubbleptr

@bubbleptr bubbleptr commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

Stacked on #452. PRD: .scratch/browser-annotation-v2/PRD.md decision 4 (S3 revisions noted inline).

  • Composer chips: the Session's browser comments appear in the attachment drawer as a "Browser comments" row of Token chips (#n first line, removable; stale ones yellow with an explanation). Removing a chip deletes the comment in main, so the page marker disappears too. An empty draft with comments can be sent.
  • Send: submitDraft snapshots the pending comments, fetches their crops, and appends formatBrowserComments after the draft. Comment images go last, in comment order. On success only the snapshot ids are consumed: comments saved during the send stay, and a failed send consumes nothing. Covers send / queue / steer-from-queue.
  • Formatter (@pace/core): replaces formatBrowserAnnotationPrompt. Comments stay in Session order with a Page: header whenever the URL changes; multiline comments are kept (main's clamp no longer folds newlines in comment); [screenshot] / rect / stale markers. Template is pinned by full-string tests.
  • Cmd/Ctrl+Enter in the page: the overlay saves, then posts submit-requested after the save report; main waits for in-flight crop commits and emits a submit-requested browser event that the composer handles itself, so it works even with the Browser panel unmounted. The panel only shows a notice when no composer is mounted (ref-counted presence registry). The composer ignores the request while busy or when there is nothing to send. Enter still saves and keeps collecting.
  • Send barrier: every send goes through browser_settle_comments, which waits for pending crops and returns the store's revision-stamped list, so a comment saved just before Send is included with its screenshot. The drawer only applies list replies and events newer than the last revision it applied.
  • Removed v1 path: the To composer button, browser_capture_annotation, prepare-capture / capture-ready, BrowserAnnotationCapture, and injectIntoComposer. feat(browser): annotation v2 S1 — edit-first comment overlay #451's palette-only command, badge clipping and rebind behavior are kept.
  • /design drawer entry plus docs/design/{chat,workspace}.md and docs/self-built-ui.md updated.

Closes #448

Test plan

  • bun run typecheck, bun run lint, bun run build
  • bun run test: all pass except the 2 pre-existing astryx-text-ref.test.tsx failures (also on main)
  • bun run test:e2e e2e/smoke/browser-surface.spec.ts: 4/4. A real end-to-end send: save 2 comments → chips → Cmd/Ctrl+Enter in the page → transcript shows the comment block and both browser-comment-N.png crops; chips and count go to 0

@bubbleptr
bubbleptr added this pull request to stack #453 October 10, 2026 11:37
@bubbleptr bubbleptr changed the title feat(browser): send browser comments as structured composer attachments feat(browser): annotation v2 S3 — browser comments as composer attachments, Cmd+Enter send Oct 10, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +284 to +285
const pending = browserComments.latest();
const sentCommentIds = pending.map((comment) => comment.id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Fresh comments missed by composer send

If Send runs while a new comment's crop is pending, browserComments.latest() omits that comment. The store publishes comments-changed only after capture, so the message goes out without the comment and leaves it pending.

Learn more

A newly saved browser comment enters the main-process store before its screenshot is captured. The store does not publish the comment to the composer until saveComment finishes the capture. The page shortcut waits for pending crops through requestSubmit, but clicking the composer's Send button does not. As a result, a send during a crop snapshots the previous comment list and succeeds without the newly saved comment.

Example: Save comment c2 while c1 is already visible in the drawer, then click Send before c2's screenshot resolves. The prompt contains only c1; c2 appears in the drawer after the message is sent.

Recommended fix: Give the composer's submit path a main-process barrier that awaits pending saves before reading the authoritative comment list and images. Ensure the snapshot and consume IDs come from the same settled store state.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 3b49109. New main command browser_settle_comments waits for in-flight save commits (crop → images → comments-changed), then returns the store's list. submitDraft sends that settled list (button and page Cmd+Enter alike) and consumes exactly those ids; if the settle fails, nothing is sent or consumed. Host and composer regression tests added.

Comment on lines +122 to +127
if (event.type === "submit-requested") {
// Cmd/Ctrl+Enter in the page. A composer mounted for this Session
// answers by submitting; with none, the comments stay on the page and
// the user hears that nothing was sent.
if (event.sessionId !== sessionId || !alive.current) return;
if (!requestComposerSubmit(sessionId)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Page send lost after dock switch

If the Browser panel unmounts while a crop runs, submit-requested has no listener when the crop completes. The composer remains mounted, but the page's send request disappears and its comments stay pending.

Learn more

The Browser panel subscribes to browser events only while it is mounted. SessionSurfaceContent renders only the active surface, so switching away from Browser unmounts this listener. requestSubmit waits for a pending crop before emitting its request. The request can therefore arrive after the user switches surfaces, even though the Session composer remains available.

Example: Save a comment using Cmd+Enter, then switch to Changes before its screenshot finishes. Main emits the request after capture, but no Browser panel forwards it; no message is sent.

Recommended fix: Subscribe to Session-scoped submit events from a component that remains mounted alongside the composer, rather than from the active Browser surface. Keep a suitable notice path when the composer cannot accept a request.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 3b49109. The composer now listens for the browser submit-requested event itself, so the send lands even if the Browser panel is unmounted. The panel only keeps the "no composer" notice, which it shows when a ref-counted presence registry reports no composer mounted for the Session.

Comment on lines +49 to +53
if (!alive || sawEvent || !fetched) {
return;
}
latestRef.current = fetched;
setComments(fetched);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Late comment list discarded despite newer contents

If browser_list_comments reads after a comments-changed event but replies later, sawEvent discards the newer list. The drawer and send snapshot retain outdated comments until another store change.

Learn more

The initial comment list is a separate IPC request from the event subscription. The sawEvent flag records only arrival order in the renderer, not when the main-process list was read. A list response that was computed after an event can arrive later, but this condition drops it and leaves the earlier event's comments in both UI state and latestRef.

Example: The subscription receives event E with [c1]; main then reads [c1,c2] for the list command, and that command's reply arrives after E. The drawer and submit still see only [c1].

Recommended fix: Attach monotonically increasing store revisions to events and list replies, and apply the result with the highest revision. Alternatively serialize the initial read and subscription through a single main-process snapshot/event handshake.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 3b49109. The store keeps a commentRevision bumped on every change; comments-changed, browser_list_comments and browser_settle_comments all carry it, and useBrowserComments applies only answers newer than the last one applied, replacing the arrival-order flag. Tests cover both orderings.

Saved comments now show as chips in the composer drawer and go out with the
next message as a formatted block plus one crop per comment, then leave the
store exactly once. Cmd/Ctrl+Enter in the page saves and sends; Enter keeps
collecting. The v1 To composer button, its full-viewport capture path and
the draft injection are removed.

Closes #448
@bubbleptr
bubbleptr force-pushed the feat/browser-annotation-v2-composer branch from d75b8fd to 3b49109 Compare October 10, 2026 12:17
@bubbleptr
bubbleptr merged commit e43ae65 into main Oct 10, 2026
1 check passed
@bubbleptr
bubbleptr deleted the feat/browser-annotation-v2-composer branch October 10, 2026 12:26
@bubbleptr bubbleptr mentioned this pull request Oct 10, 2026
3 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(browser): 标注 v2 S3 Browser comments 结构化进入 composer 与 Cmd+Enter 直发

1 participant