feat(pages): flag delete requests on their pin, tighten comment chrome
Quick feedback posted from the selection toolbar was indistinguishable from any other comment once it landed. The body is free text, and it is stored in the reader's locale, so nothing downstream could tell a "Delete this" preset from someone typing the same words — and in French it posts "Supprimer ceci", so body-matching was never going to hold.
Threads now carry an intent enum, set by the toolbar's trash and thumbs-up buttons and null for everything else. An open delete request renders its pin red instead of blue, falling back to the usual resolved grey once someone closes it. The meaning lives in the column, not the prose, so it survives translation, editing, and agent-authored comments.
The ten QUICK_PRESETS deliberately carry no intent — they are prose feedback, not actions.
Also in here
A pass over the comment chrome, driven by review of how it looked in the app:
- Fast menu colour. Ten accent-bar hues that encoded nothing — three presets shared orange, three shared sky — plus full-colour emoji doing the same job beside them. Both replaced by muted lucide icons following the
dropdown-menurecipe used elsewhere. - "All comments" was near-invisible over a page.
Button's outline variant shipsdark:bg-input/30, a different tailwind-merge modifier group than an appendedbg-popover, so it survived the merge and won on specificity — and--inputis itselfoklch(1 0 0 / 15%)in dark, leaving the button at roughly 4% opacity. The hover state had the same bug withbg-input/50, so the button faded further as the pointer arrived. - Pins sat on the words they marked. The bubble is
rounded-full rounded-bl-sm— a tail at bottom-left — but was positioned centred on the anchor, so the tail pointed at nothing. The tail now rests on the anchor point. - Comment-mode icon was a message-plus for what is an element picker; now the devtools pointer-in-a-frame idiom.
- Density. A fixed
h-7on the author row reserved 28px for a 20px line purely to match the old avatar. Roughly a third of the vertical space per thread comes back.
Review fixes folded in
A pre-merge review caught three things worth calling out:
pinPointOfstill clamped for a centred pin after the geometry moved. An element flush with the top of the frame could put the pin box aty = -12, and the overlay isoverflow-hidden, so the top half was clipped away. Clamping is now per-axis, with a regression test.popoverLayouthad the same stale assumption — the popover overlapped the pin by 4px when it flipped above, and sat 12px off-centre. It now uses the real box edges.- A quick action returned its promise into a
voidcallback whilecreateThreadrethrows, so a failed post escaped as an unhandled rejection. Caught now; the store already toasts.
Not addressed — pre-existing, from the toolbar commit
Found by the same review, all predating this change. Happy to take them in a follow-up:
FastMenuis hard-positionedtop-fullwith no flip or clamp, unlike the toolbar'stoolbarPlacement. A selection low in the frame pushes most of the ~330px menu off-screen; only the 1–0 shortcuts still reach it.selection.rectis captured once at pick time and never re-reads the runtime'srects/scrollmessages, so the highlight and toolbar stay put when the iframe scrolls.- When a quick post fails,
composingis null (the selection path cleared the draft), so the recovery branch never fires and the pick is lost with no way back.
Testing
57 tests pass in packages/ui (5 new, covering the pin intent colours and the clipping regression). packages/ui, packages/db, packages/trpc, and apps/web typecheck clean; biome clean; check:i18n compiles --strict across all 17 locales — no user-facing copy changed.
Before merging
The migration is generated but not applied. This workspace runs local Postgres via docker-compose rather than Neon, so there was no dev branch to apply against:
CREATE TYPE "public"."page_comment_intent" AS ENUM('delete', 'approve');
ALTER TABLE "page_comment_threads" ADD COLUMN "intent" "page_comment_intent";
Nullable, no default, no backfill — catalog-only, no table rewrite. There is also no integration test proving intent round-trips through create/list; page-comment.test.ts only covers pure functions, so that needs a new .integration.ts against an applied migration.
One design note: a red pin marks a request, not a scheduled deletion — nothing removes the block until an agent acts on the thread.
https://claude.ai/code/session_014wAnLASdoKc5jYSbbS4pYA
Summary by cubic
Threads now carry an intent enum so delete requests from the selection toolbar stay distinguishable from ordinary comments — bodies are free text in the reader's locale, so text-matching never held. Open delete requests render their pin red instead of blue, falling back to grey once resolved.
Fixes and migration
CommentProvideris keyed by page ID so a pending selection can't post an anchor from the previous page after navigation.- A failed quick post restores the pick instead of losing it, and the error is logged before being discarded.
- The quick-feedback menu announces itself to screen readers via
aria-expandedandaria-haspopup. - A translation pass aligns delete/remove and feedback terms across locales; the merge with main regenerated catalogs so shared preset strings keep one translation each.
- Migration
0111_page_comment_thread_intentis generated but not applied.
Comment chrome
- Fast menu's accent-bar hues and emoji replaced with muted lucide icons.
- The all-comments button no longer renders near-invisible in dark mode; pins rest on their anchor; the comment-mode toggle uses the pointer-in-a-frame icon.
- Pin and popover geometry clamp per-axis with a regression test; composer and row spacing tightened.
Summary by CodeRabbit
-
New Features
- Added a text-selection toolbar for quickly commenting, approving, deleting, or dismissing selected content.
- Added quick feedback presets with keyboard shortcuts.
- Comments now support approval and deletion intents, displayed with distinct visual indicators.
-
Bug Fixes
- Improved comment pin and toolbar positioning near page edges and small selections.
- Preserved selected content when quick-action comment creation fails.
-
Style
- Refined comment panel spacing, sizing, icons, colors, and button styling for a more compact interface.