feat(comments): show selection action after highlight, calmer styling - #79403
Conversation
|
React Doctor found 8 issues in 2 files · 8 warnings. 8 warnings
Reviewed by React Doctor for commit |
|
😎 Merged successfully - details. |
Generated-By: PostHog Code Task-Id: e93165cd-6893-45dd-8fa8-4e97299f5f06
559ccbb to
232c372
Compare
Placement: the floating comment action now anchors like Google Docs — just right of the selection's end caret, vertically centered on the end line; it flips below the line when the right edge has no room and above near the viewport bottom. The shared computeCommentActionPlacement() applies everywhere: the code editor previously anchored to the START of the end line (CodeMirrorEditor), text artifacts and canvases to the whole-range bounding box, and the HTML-artifact bridge centered above the range. Styling: one pill look shared by the in-app trigger and the sandboxed iframe CSS (border-radius 999px, app-gray card tokens, chat-bubble icon, 600-weight 12px label). Hover now has a real background shift plus shadow lift and cursor: pointer — previously the desktop Quill button kept cursor: default and its hover background was nearly identical to rest, so hovering looked dead. The canvas runtime stops hand-rolling its own button CSS and uses the shared theme/css/placement helpers like the artifact bridge. Generated-By: PostHog Code Task-Id: e93165cd-6893-45dd-8fa8-4e97299f5f06
A Range spanning block elements reports the wrapper boxes (paragraph, blockquote, list) alongside the text line boxes, and a wrapper can come last in DOM order. Taking the last box — or the bounding box — therefore anchored the action to the wrapper's right edge, which is why a multi-line selection put the Comment button far from where the selection ended. The shared commentActionAnchorRect() now keeps only leaf boxes (those enclosing no other box) and picks the visually lowest, right-most one: the end of the last selected line. All three range-based surfaces use it. The action is also its own component now, SelectionCommentActionButton, instead of a Quill button. It renders the same stylesheet the sandboxed iframes inject, so markdown artifacts, HTML artifacts, canvases and the code editor share one look and cannot drift. Hover changes the background only — the shadow no longer jumps — and the palette is explicit per theme rather than inherited from ambient tokens. Generated-By: PostHog Code Task-Id: e93165cd-6893-45dd-8fa8-4e97299f5f06
The gate started on pointerdown and reported synchronously on pointerup, which let the action appear mid-selection. Reworked around the pattern the established editor toolbars use (Floating UI's text-selection example, TipTap's BubbleMenu, and the common selectstart/pointerup idiom): - selectstart also starts the gate, so drags whose pointerdown never reaches the document still hide the action. - The report waits two animation frames after pointerup. Browsers commit the selection after the pointerup handler runs, so reading it synchronously returned the mid-gesture range — which both showed the action early and anchored it to the wrong place. - pointercancel now cancels instead of settling. Trackpads fire it mid-drag, which popped the action up while the user was still selecting. - Keyboard selections are held until keyup rather than reported on every Shift+Arrow tick; a plain letter no longer reads as select-all. - Secondary mouse buttons no longer start a gesture. Scrolling the artifact container now re-anchors the action to the live selection instead of leaving it at stale viewport coordinates. Verified with a real mouse drag in headless Chromium: the action is absent at six sampled points across the drag, appears on release 8px right of the selection end, with its vertical center within a pixel of the end line's. Generated-By: PostHog Code Task-Id: e93165cd-6893-45dd-8fa8-4e97299f5f06
A published canvas runs the runtime baked into its artifact by the cloud builder, not the desktop sandbox document, so it never picked up the settle gate or the end-line anchor: the action tracked the cursor mid-drag and anchored to the whole-range bounding box. Port both into build.mjs, cancel a pending report when a new gesture starts (on both runtimes), and drive the emitted artifact runtime through a simulated drag in the builder tests, replacing the string pins that had frozen the old selectionchange-only behavior. Already-built canvases keep the old runtime until they are rebuilt. Generated-By: PostHog Code Task-Id: 17b5c25a-9f90-4267-a486-15c4debd3074
puemos
left a comment
There was a problem hiding this comment.
Merge Confidence: 3.5/5 - Confident
"Looks good - minor concerns only"
Assessment
- ✓ Genuine consolidation: four ad-hoc
selectionchangeimplementations collapse into one sharedselectionCommentAction.ts, with the comment action's look shared between the app and sandboxed iframes via a single class + palette - ✓ Strong test investment for a UI change — 15 unit tests over the gate/anchor/placement, a jsdom test that runs the real injected bridge script, and a Node harness driving the actual built canvas runtime through a simulated drag
- ✓ Security posture preserved: the new
thememessage sits inside the existing source/marker/channel guard and allowlists to light|dark; injected constants go through JSON.stringify; CSP and nonce handling untouched - ⚠ Cmd+A leaves
installSelectionSettleGatestuck inselectingwhen the modifier is released before the letter —onKeyUpre-evaluatesisSelectionKeyagainst the keyup event, so the gate swallows allselectionchangeuntil the next pointerdown/pointerup. The comment action silently never appears for a select-all (selectionCommentAction.ts:258) - ⚠ The test that would have caught it stops one event short:
treats a plain letter as typingasserts the Cmd+A keydown but never dispatches the keyup (selectionCommentAction.test.ts:190) - ⚠
onKeyDownstarts a gesture on every arrow-key repeat including plain caret movement, andonGestureStartis wired to an unconditional cross-iframetext-selection-clearedpost in sandboxRuntime.ts and the canvas runtime — ~30 messages/sec while an arrow key is held - ⚠
build.mjshand-copies the gate and anchor functions (the builder can't import the desktop workspace) and the copy already diverges — it drops the cleanup return. The only guard is a comment plus the Node harness, whoseElement/MouseEventstubs make everyinstanceofcheck unreachable, so the right-click and action-UI guards are untested there - ⚠
computeCommentActionPlacementnow positions the 180px expanded composer as well as the 28px action, centering it on the selection line instead of below it — a visible behavior change with no test on that branch - ⚠
anchor.leftnow means the end-caret x and is passed into the placement function'srightfield; correct but reads as a transposition at five independent call sites
Review: Unify the selection comment action: shared settle gate, end-line anchoring, and one themed button across all surfaces
Replaces four independent selectionchange implementations (markdown annotations, HTML artifact bridge, canvas sandbox, published canvas runtime) with a shared selectionCommentAction.ts: a gesture settle gate so the action stops chasing the cursor mid-drag, a leaf-box anchor so it lands at the end of the last selected line instead of a wrapper's bounding box, and one button class + palette so the in-app and iframed actions can't drift. EditorSelection.anchor grows a bottom field and left is redefined as the end-caret x; a new theme message lets a running artifact iframe re-theme without reloading.
The consolidation is well-executed and unusually well-tested for a UI change — including a Node harness that drives the actually-built canvas runtime through a simulated drag. The main risk is that one shared selecting boolean now gates the action on every surface at once, so its unreachable transitions degrade all of them silently. Specifically, Cmd+A with the modifier released first leaves the gate stuck and the action never appears; the test suite stops one event short of catching it. Secondary concerns: arrow-key repeat drives a cross-iframe message per keydown, the canvas builder's hand-copied fork of the gate already diverges from the TS source with only a comment guarding sync, and the placement helper is now reused for the 180px composer without a test on that branch.
Fix select-all settling, repeated clear messages, and composer placement. Keep the published canvas runtime in sync and exercise its pointer guards. Generated-By: PostHog Code Task-Id: c8373a88-43af-4fbd-958a-b6385a2d6849
|
React Doctor follow-up: fixed the |
|
/trunk merge |
Add braces to the shared selection helpers and their canvas builder copy so the root Oxlint curly rule passes in the Trunk merge queue. Generated-By: PostHog Code Task-Id: c8373a88-43af-4fbd-958a-b6385a2d6849
|
/trunk merge |
1 similar comment
|
/trunk merge |
Problem
Anyone highlighting text in a desktop task's comments gets a Comment action that chases the mouse cursor mid-drag, appears nowhere near where the selection actually ends, gives no visible hover feedback, and looks louder than the text it sits on.
Five specifics:
cursor: default, and its hover background differed from rest by only a few percent. The in-iframe light theme's hover was#f5f5f4on white — invisible.Changes
installSelectionSettleGate(newselectionCommentAction.ts), shows the action only once the selection settles, following the pattern the established editor toolbars use (Floating UI's text-selection example, TipTap's BubbleMenu, the commonselectstart/pointerupidiom):selectstart/pointerdown/selection keydown hide it,selectionchangeis ignored mid-gesture, andpointerup/selection keyup report two animation frames later — browsers commit the selection after the pointerup handler runs, so reading it synchronously returns the mid-gesture range.pointercancel(which trackpads fire mid-drag) and window blur cancel. Secondary mouse buttons and plain typing don't start a gesture..toString(), so the behavior has one implementation.commentActionAnchorRect: a Range spanning block elements also reports its wrapper boxes (paragraph, blockquote, list), so neither the bounding box nor the last box marks where the selection ended. Keeping only leaf boxes and taking the lowest, right-most one gives the end of the last selected line.computeCommentActionPlacement: the action sits just right of the selection's end caret, vertically centered on the end line (Google Docs style). It flips below the end line when the right edge has no room, keeping its right edge at the caret, and flips above near the viewport bottom. Every anchor producer (CodeMirrorEditor, artifact text annotations, the artifact HTML bridge, the canvas runtime) now reports the selection's end-line rect.SelectionCommentActionButton, rather than a Quill button. It renders the same stylesheet the sandboxed iframes inject, so the code editor, text artifacts, HTML artifacts and canvases cannot drift apart, and its palette is explicit per theme instead of inherited from ambient tokens.themebridge message, so a theme change never reloads the running preview.products/canvas/packages/canvas_builder/build.mjs) installs the same gate and the same end-line anchor, so a published canvas behaves like every other surface.Note
Canvases built before this ships keep the old runtime until they are rebuilt. The runtime is baked into each artifact at build time, so republishing a canvas is what picks up the fix.
How did you test this code?
selectionCommentAction.test.ts: the gate ignores mid-drag selections, reports on pointer release and on keyboard-only selection, ignores presses inside the action UI, cancels on window blur, and settles onpointercancel;computeCommentActionPlacementcovers right-of-end, flip-below, flip-above, and viewport-margin clamping. Guards the "button chases the cursor" and "button lands nowhere near the selection" regressions.artifactHtmlCommentBridge.test.ts: executes the generated bridge script in a real JSDOM document; the Comment button stays hidden through a simulated drag, appears on release at the expected end-line coordinates, and athememessage re-themes a running document. Guards template-string fumbles in the injected script itself.SelectionCommentOverlay.test.tsx: the labeled Comment action opens no tooltip, while the icon-only action still does.test_cloud_builder.pydrives the runtime the builder actually emits through a simulated drag in Node: nothing is reported while the pointer is down, one report lands on release, and its rect is the last selected line's box rather than the whole-range box. Run against the pre-fix runtime, both assertions fail. Guards the published-canvas path, which no desktop test can reach.selectionchangepresent andmouseupabsent. The drag test replaces them.Automatic notifications
Docs update
None: desktop-app UI polish behind the
posthog-code-commentsfeature flag.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
The gate, placement, and theme helpers are centralized in
selectionCommentAction.tsso sandboxed iframe scripts can inject them via.toString(). The published-canvas gap surfaced in review of the first round:BuiltCanvasembeds a signed artifact whose runtime comes from the cloud builder, so the desktop fixes never applied there. Porting plus a behavioral test was chosen over a full extraction, which would have to cross into the canvas build image. Hover invisibility was verified against the actual CSS (Quill'scursor: default+ near-identical--fill-hoverdelta; iframe hover#f5f5f4on white) and against pixel diffs of rest-vs-hover renders.