Cycles
Design · c85

Chat Transcript

activecreated 2026-09-11· last activity 2026-09-11· mode: evolution (promotes c77's "chat wearing the spine" ruled candidate into a reusable component) + assemblage (turn navigator borrowed from ChatGPT)

`transcript/*` component namespace (q-nuxt-layer) for rendering an agent chat session — messages, collapsible tool-call bursts, and a ChatGPT-style hover-expanding turn navigator.

Why

c77 (Session View) ruled a chat-grammar candidate for rendering agent sessions — collapsible tool bursts, avatar-vs-glyph rail, policy-as-speaker — but left it as inline template code on a demo page. This cycle promotes that pattern into a real, reusable layer component for the page-session context, per "replicate first, optimize later" and "the demo is the test."

Objective

A transcript component namespace in q-nuxt-layer (TranscriptPanel, TranscriptNavigator, TranscriptTurn, TranscriptMessage, TranscriptToolCall) rendering the initial-prompt's synthetic chat-session shape, plus a Phase 2 demo page on the design site, documented on /components once Mark's manual design revisions landed.

Scope

  • q-nuxt-layer/components/transcript/ — Panel, Navigator, Turn, Message, ToolCall
  • app/data/transcript-fixtures.tsTranscript/TranscriptTurn/TranscriptMessage/ContentPart types + empty/minimal/typical/overflow fixtures
  • app/pages/pages/phase2-chat-transcript.vue — demo page with a state toggle across all four fixtures
  • app/pages/pages/index.vue — new "Phase 2" card
  • app/data/component-registry.ts — new transcript group, 5 registry entries
  • app/pages/components/_demos/Transcript*.vue — one demo file per component
  • app/pages/components/index.vue — Visual-view thumbnail for TranscriptPanel

Key Changes

  • 2026-09-11: Built the transcript namespace: TranscriptPanel (card shell, turn count, navigator + turn stack, scroll-to-turn), TranscriptNavigator (tick rail with a hover-distance-based flyout, truncation via line-clamp), TranscriptTurn/TranscriptMessage (one bubble per speaker message, right-aligned with UxAvatar for the user, left-aligned with an inline AgentPawn for the agent), TranscriptToolCall (collapsed row → expanded input/content/output monospace block, local expanded ref). Resolved the initial prompt's flagged items/items naming collision as parts (message content) vs messages (turn's speaker groups). Renamed the stale, uncommitted "c85. Phase-2 Pages" entry in cycles/CYCLES.md to this cycle. Verified all four demo states (empty/minimal/typical/overflow) and the tool-call expand + navigator hover interactions via Playwright against the running dev server.
  • 2026-09-11 (follow-up): Tightened fidelity to the reference sketch — entity icon inline at the bubble's bottom corner (was stacked below), monospace chat/tool-call text, no purple bubble background. Reworked the navigator: no dead space between tick rows (clicks now target whichever turn currently has the most cursor-proximity influence, not the exact row under the pointer), hover fan replaced a per-tick rotate effect with width-only growth (rotation read as noise, not signal), and centered the rail on the panel's own height via flex rather than a translateY transform (a transform on an ancestor traps a position:fixed descendant in that ancestor's box — broke the flyout). TranscriptPanel became self-contained: flex flex-col h-full + minHeight prop, internal overflow-y-auto turn list so the panel (not the page) owns its own scroll — scrollToTurn now scrolls that container directly instead of scrollIntoView(), which was also dragging the outer page (and the panel's own header) along with it. Fixed one real layer-gotcha violation found in cleanup: max-w-[560px] (arbitrary Tailwind value, unreliably scanned in layer components) → inline style="max-width: 560px".
  • 2026-09-11 (design revisions + documentation): Iterated the message bubble's tail through three techniques before landing: a thin SVG line (matched the hand sketch's stroke but couldn't read as attached), a CSS-border triangle glued to the bubble corner (a proper wedge, but reached for the avatar rather than sitting flush), then Mark's skewed-square border-trick (ported faithfully — colors read the same --qp-surface/ --qp-stroke custom properties Tailwind's bg-surface/border-stroke resolve to, since this repo drives theming through those vars rather than a dark: class). Fixed a real bug surfaced along the way: flex-row-reverse had user/agent swapped relative to what the bubble's corner-cuts assumed, so identity marks sat on the wrong side of their bubble. Polish pass: thicker/more padded bubbles, shrink-0 + a floor-clearing translateY on the identity marks so they don't compress or overlap the tail. Swapped TranscriptPanel's h-full for max-h-full so a short transcript shrink-wraps to content instead of always stretching to fill the ancestor's height — flex-1/min-h-0 still caps and scrolls once content exceeds it, no conflict between the two behaviors. Hid the navigator (and its reserved gutter) when a transcript has only one turn, since there's nothing to navigate between. Documented all five components on /components via the document-components skill: new transcript registry group, one demo file per component (TranscriptPanel's reuses the four /pages fixtures behind a state toggle), and a Visual-view thumbnail.
  • 2026-09-11 (sizing bug): The max-h-full shrink-to-fit change above had two real bugs, both found on the doc page (a taller viewport made them obvious). Internal scroll: the scroller's h-full silently grew to content height instead of clamping — a max-height-derived parent size isn't reliably "definite" enough for a nested percentage-height child — so overflow never triggered and content just got clipped by the panel's own overflow-hidden. First fix attempt (position: absolute on the scroller) traded that bug for a worse one: absolutely-positioned content doesn't count toward an ancestor's auto/content-based height, so short transcripts collapsed to just the header. Tried height: clamp(minHeight, max-content, 100%) next — invalid CSS (max-content isn't a valid argument inside clamp()/min()/max(), only lengths/percentages are), silently dropped, height fell back to unbounded auto. Landed on max-h-full (unchanged) paired with a normal in-flow flex-1 min-h-0 scroller instead of the old relative + two absolutely-positioned children layout — verified all four fixture states hit the right number (960px capped+scrolling for Typical/Overflow, 480px floor for Minimal/Empty) at a 1600px-tall viewport. Also added a max-width: 640px wrap on the doc-page demo instance only — TranscriptPanel itself stays unconstrained/fill-width by default.

Outcome

transcript/* shipped and documented — TranscriptPanel, TranscriptNavigator, TranscriptTurn, TranscriptMessage, TranscriptToolCall all live on /components/transcript, each with a working interactive demo and full props/emits coverage. The c77 chat-grammar candidate is now a real, reusable layer component rather than inline demo-page template code. Carries forward: the skewed-square speech-bubble tail is a nice generalizable pattern (colors read from the --qp-* custom properties, not hardcoded) worth remembering for any future chat-bubble UI in this design system.

Artifacts

  • initial-prompt.md
  • initial-prompt.md
  • plan.md

Qpoint Brand Style Guide