Clip AI chat rebuilt, a "/" command palette, and Figma-style comments
Changed
Comments polish + migration applied. Applied the 20260724000100_comments migration (plus the
two pending ai_soul / ai_usage_tokens migrations) to the hosted Supabase, so comments now work
live. Comment mode now shows a speech-bubble cursor (echoing the toolbar's comment glyph, tail
as the drop point) instead of a generic crosshair. The inline thread's compose/reply box is rebuilt
on the shadcn Textarea + Button primitives (auto-growing field, cleaner single surface) and no
longer draws a stray divider above a fresh draft. The two comment buttons on the canvas toolbar are
now split into their own divided category, separate from the drawing tools.
Added
All-comments panel — the comments Sheet (Phase 3, slice 3). A second toolbar button opens a
right-side shadcn Sheet listing every thread in the space, split into Open and Resolved,
each row showing the author's initial avatar, name, relative time, a two-line snippet, and reply
count. Clicking a row pans the canvas to that pin and opens its inline thread (via a shared focus
state). The button carries an unresolved-count badge. New components/comments/CommentsSheet.tsx;
Toolbar gains onOpenComments + commentUnresolved, and the page renders the Sheet from the
cloud space. Completes the Phase-3 comments feature (data layer → pins/threads → panel).
Figma-style comment pins + inline threads (Phase 3, slice 2). A comment tool in the canvas
toolbar (cloud spaces only) toggles comment mode; clicking the canvas drops a pin and opens an
inline thread composer at that spot. Pins live in flow coordinates (they pan/zoom with the board,
via a new CommentLayer rendered inside the ReactFlowProvider alongside the cursors/draw
overlays), show the author's colored initial, and fade when the thread is resolved. Clicking a pin
opens its thread — root + replies with avatars and relative timestamps, a resolve/reopen toggle,
delete (author-only), and a reply box. Both the composer and reply box support @-mention
autocomplete over the space's members (↑/↓/Enter, click). New components/comments/*
(CommentLayer, CommentThread, shared CommentsCtx type) + lib/use-space-comments.ts (loads
comments + members, subscribes to Realtime, refetch-on-write); Canvas gains a comments prop,
the page wires it from the cloud space, and the Toolbar gains the comment toggle. Local spaces
are unaffected (no member list → no comments). The all-comments Sheet panel is the next slice.
Comments data layer (Phase 3, slice 1 — foundation, inert). Groundwork for cloud-only,
Figma-style comments: a new public.comments table (client-uuid id, self-FK parent_id for
replies with cascade, author_id default auth.uid(), resolved flag, and a data jsonb for
body/anchor/mentions), member-read / self-insert / author-or-editor update+delete RLS via
the private.* helpers, added to supabase_realtime with replica identity full
(20260724000100_comments.sql). New lib/supabase/comments.ts mapper + CRUD
(loadComments/createComment/editComment/setResolved/deleteComment) and a
subscribeComments Realtime helper (refetch-on-change). Nothing consumes it yet — the canvas
pin overlay and the Sheet thread UI land in the next slices.
/ slash-command palette in the AI composer (Phase 2). Claude-style: typing / as the
first character opens an anchored, grouped command menu above the composer that filters as you
type, with ↑/↓ + Enter/Tab to run, Esc to dismiss, and mouse hover/click; a non-matching /text
still sends as a normal message. The + button now opens the same palette. Commands span three
groups — Board (/brief, /layout auto-layout, /new chat), Mode & model (/agent,
/chat, /model), and Add to board (/add sticky · text · frame · shape). Board/add
commands run through the existing onActions path (validated AIActions: auto_layout,
create_sticky/create_text/create_frame/create_shape placed at the viewport center).
New lib/ai/commands.ts registry (declarative run(ctx) per command, no React/icon deps —
unit-tested, tests/ai-commands.test.ts) + components/ai/SlashCommandMenu.tsx.
Clip AI chat rebuilt on shadcn chat primitives (Phase 1). Installed the shadcn
MessageScroller / Message / Bubble / Marker / Attachment / Avatar components and
rewired the compact AI popover thread onto them: the scroller now owns follow-while-streaming,
turn anchoring, and a floating jump-to-latest control (replacing the hand-rolled scroll
container). User turns render as right-aligned secondary bubbles, assistant replies as plain
ghost bubbles, and day dividers as Marker separators — all still carrying briefing badges,
thoughts, token counts, and the "Board updated" action meta. The composer gains a +
add-context menu (brief space / new chat) on the left and a round ↑ send button on the right,
matching the reference layout while keeping the model switcher, mic/dictation, mode, and
Enter-to-send intact.
The shadcn chat primitives ship for Tailwind v4; a few v4-only utilities they use
(wrap-break-word, ring-3, duration-400, scrollbar-*, shimmer) are backported to this
project's Tailwind v3 build via tailwind.config.ts + a @layer utilities block in
globals.css, and the generated components' v4 variant syntax (has-data-, *:data-slot,
size-*!) was rewritten to v3 form. Button gains icon-sm / icon-xs sizes the primitives expect.
Fixed
Chat flickered while completely idle — replaced the observer-driven scroller. The shadcn
MessageScroller (from @shadcn/react) drives scroll position with a ResizeObserver →
requestAnimationFrame → setSnapshot (context setState) measurement loop, plus a
MutationObserver and more rAF probes. Its viewport also toggled data-autoscrolling:scrollbar-none,
which changes the reserved scrollbar-gutter width — so hiding/showing the scrollbar resized the
viewport, the ResizeObserver re-fired, it re-measured and re-toggled: a self-sustaining
measure→repaint loop that re-rendered the whole thread every frame at idle (the "full redraw"
flicker). Replaced it with a plain scroll container: a useLayoutEffect pins to the newest turn
(pre-paint) and a small onScroll check drives the jump-to-latest button — no observers, no rAF
loop, stable gutter (scrollbar-gutter: stable, never hidden). The Message/Bubble/Marker
visuals are unchanged. Removed the now-unused components/ui/message-scroller.tsx and the
@shadcn/react dependency.
Chat polish — user bubbles were transparent, composer felt boxed-in, thread jumped.
Three follow-up fixes to the Phase-1 chat: (1) the shadcn Bubble applies its fill through a
*:data-[slot=bubble-content]:bg-… variant that Tailwind 3.4 silently drops — so user
(secondary) bubbles rendered with no background; rewrote those variants to the v3-valid
[&>[data-slot=bubble-content]]:bg-… child selector (verified the bg-secondary/bg-primary/
bg-muted rules now emit into the compiled CSS). (2) The composer no longer sits in a
bordered footer container — it floats over the thread (rounded pill with a soft shadow, a
top fade so the conversation dissolves behind it), matching the reference; the legal disclaimer
line was dropped from the compact view and the jump-to-latest button lifted above the pill.
(3) Removed the upstream content-visibility:auto + contain-intrinsic-size from
MessageScrollerItem — that long-thread virtualization estimated row heights and caused visible
scroll jumpiness in this short-thread popover.