Skip to content

feat(ui): migrate tool live-output stream onto @maka/ui chat primitives (#332 PR3) - #348

Merged
Astro-Han merged 3 commits into
mainfrom
claude/chat-primitives-pr3
Jun 28, 2026
Merged

feat(ui): migrate tool live-output stream onto @maka/ui chat primitives (#332 PR3)#348
Astro-Han merged 3 commits into
mainfrom
claude/chat-primitives-pr3

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Moves the tool live-output stream panel (ToolOutputStream) onto the @maka/ui chat substrate, retiring the bespoke .maka-tool-output-stream-* shell CSS with zero visual change. This is PR3 of the #332 conversation-flow governance pass, following PR1 (#334, bubble/row shell) and PR2 (#337, turn markers).

The panel's static shell (container, header, counts row + its pills, scrolling body, chunk/tag spans) literalizes onto a new internal streamVariants cva exactly as PR2 did for markers. The pulsing "live" dot is the one part that can't ride those rails — an animation can't be a leaf-literal and getComputedStyle reads a phase-dependent value — so it moves onto a new governed LiveIndicator primitive backed by a single canonical @keyframes maka-pulse, pinned by a keyframe-values contract instead of the diff harness.

Why

Closes part of #332 (PR3). The conversation-flow display is being migrated onto one @maka/ui substrate + a test net, one island at a time, with zero visual change. PR3 covers the tool live-output stream. It also lays the groundwork for retiring the repo's duplicated pulse keyframes: the duplicate reasoning / composer / onboarding live dots can adopt LiveIndicator in a follow-up motion pass, retiring their own *-pulse keyframes onto maka-pulse.

Scope

Changed:

  • packages/ui/src/primitives/chat.tsx — add streamVariants (internal, off-barrel like markerVariants) for the panel shell, and an internal LiveIndicator primitive for the live dot. Every shell value is a LITERAL arbitrary utility that compiles 1:1 to the retired declaration.
  • packages/ui/src/index.tsLiveIndicator and streamVariants both stay OFF the package barrel (single consumer today, applied by relative import). LiveIndicator is promoted to a public export only when a second real consumer migrates onto it, not speculatively before then.
  • packages/ui/src/components.tsx — wire ToolOutputStream to the primitives. Live auto-scroll behavior is preserved verbatim.
  • apps/desktop/src/renderer/maka-tokens.css — add the canonical @keyframes maka-pulse (rest opacity 0.55, scale 1→1.1) in the shared motion home.
  • apps/desktop/src/renderer/styles/tool-stream.css — delete the .maka-tool-output-stream-* block + its per-feature @keyframes.
  • package.json — add npm run check:chat-visual, a discoverable entry point for the computed-style diff (the repo has no CI; this keeps the zero-visual proof reproducible by anyone).
  • Tests + the computed-style diff script (see Verification).

Not included:

  • Cross-feature pulse consolidation (reasoning-panel / composer / onboarding adopting LiveIndicator and deleting their duplicate keyframes) — a separate motion pass; that is where their visuals would change, so it stays out of this zero-visual PR. That pass is also when LiveIndicator would be promoted to a public export.
  • The live auto-scroll behavior is untouched; MessageScroller / scroll-area governance remains the separately-evaluated feat(ui): govern chat conversation-flow display onto @maka/ui Base UI primitives #332 concern.
  • Preview surfaces (terminal / diff / web-search) — PR4.

Verification

  • npm run -w @maka/ui test — 12/12 (adds a streamVariants literal/scale-drift guard + the LiveIndicator pulse-pin test).
  • npm run -w @maka/desktop test — 1604/1604 (adds chat-stream-cascade-contract.test.ts: retired stream selectors + per-feature pulse keyframe absent, canonical maka-pulse frames pinned, and the live-dot literals pinned — the one part the computed-style diff can't cover).
  • npm run typecheck — clean across all workspaces.
  • npm run check:chat-visual -- <pre-PR2 baseline.css> <head.css> (wraps scripts/check-chat-marker-computed-style.mjs, extended to cover the stream shell): against a pre-PR2 renderer CSS baseline (e033a8c4~1, which still carries both the bespoke marker and stream CSS), every row is 45/45 identical — TOTAL DIFFS: 0 across 36 element/state rows + 2 ::before middots (markers + all 14 stream rows incl. the data-live accent ring). Non-vacuous: the baseline and head bundles genuinely differ in source, yet the migrated chrome reads byte-identical computed style on both sides.
  • Live dot: pinned by the @keyframes maka-pulse frame contract + the LiveIndicator literals (same values as the retired dot, so visually identical by construction); the animation escapes the computed-style proof by nature.

User-facing impact

None — zero visual change by design, locked by the contract + diff above.

Reviewer notes

  • streamVariants and LiveIndicator are deliberately off the barrel (like markerVariants): one consumer today, applied by relative import, so they stay internal, freely-removable styling details rather than public API. When a second consumer (reasoning / composer / onboarding) actually migrates, LiveIndicator gets promoted to public then.
  • The tests intentionally do NOT re-assert the stream shell's per-part class literals: that shell is proven by the computed-style diff, so re-stating the strings would only mirror the implementation. The source-string assertions are kept only for the live dot, which the diff cannot observe.
  • The maka-pulse keyframe lives in maka-tokens.css because @maka/ui ships no CSS of its own (its primitives style via app-provided tokens); a keyframe is a named global rule, not a leaf utility, so this is the same kind of token dependency as --accent. Reduced-motion suppression rides on the primitive's motion-reduce: utilities.
  • word-break:break-word stays an arbitrary literal, not Tailwind's break-words (which is the different overflow-wrap property).

…es (#332 PR3)

Move the tool live-output stream panel (`ToolOutputStream`) onto the
`@maka/ui` chat substrate, retiring the bespoke `.maka-tool-output-stream-*`
shell CSS with zero visual change.

- Add `streamVariants` (internal, off-barrel like `markerVariants`) to
  packages/ui chat.tsx — the panel/header/counts/body/chunk/tag shell as
  LITERAL arbitrary utilities that compile 1:1 to the retired declarations.
- Add the `LiveIndicator` primitive (barrel-public reuse surface) for the
  pulsing live dot, backed by one canonical `@keyframes maka-pulse` in
  maka-tokens.css. The dot's breath escapes the computed-style proof (a
  keyframe is a named rule + getComputedStyle is phase-dependent), so it is
  pinned by the keyframe frames + literals and verified by screenshots.
- Wire ToolOutputStream to the primitives; preserve the live auto-scroll
  behavior untouched (MessageScroller stays a separate #332 concern).
- Delete the `.maka-tool-output-stream-*` block + its per-feature keyframe
  from styles/tool-stream.css.

Tests: cascade source-string contract (apps/desktop) + streamVariants /
LiveIndicator unit tests (@maka/ui) + extend the computed-style diff script
to cover the stream shell. The 4 other duplicated pulse keyframes
(reasoning / composer / onboarding) adopt LiveIndicator in a follow-up
motion pass.
…ts (#332)

Review response for PR3. The migration kept the same rendered output but
trimmed the abstraction + test surface to the minimum the single consumer
actually needs:

- LiveIndicator off the package barrel (zero external consumers today). It
  stays internal, applied by relative import like streamVariants; it gets
  promoted to public only when the reasoning/composer/onboarding live dots
  actually migrate onto it, not speculatively before a second consumer.
- drop the unused `StreamPart` type export (no importer anywhere).
- keep streamVariants as the internal cva table (mirrors the merged PR2
  markerVariants pattern) — not inlined to local consts.
- thin the source-string tests: the stream SHELL is proven by the
  computed-style diff harness, so the tests no longer re-assert those
  literals (that just mirrored the impl). They keep only what the diff
  can't cover — the live dot's animation/keyframe pins — plus the
  retired-selector-absence + scale-drift guards.
- add `npm run check:chat-visual` wrapping the computed-style diff so the
  zero-visual proof is reproducible by anyone (the repo has no CI).

Verified: @maka/ui 12/12, desktop 1604/1604, typecheck clean, and the
computed-style diff still reads TOTAL DIFFS: 0 across 36 rows + 2 ::before
(incl. all 14 stream rows + the data-live accent ring) vs the pre-PR2
baseline.
The no-arg guard already exits non-zero, but its usage line showed the raw
`npx electron …` form and `<main.css>`. Point it at the actual entry point
(`npm run check:chat-visual -- <baseline.css> <head.css>`) and spell out what
each bundle is, so running it without args is self-explanatory.
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.

1 participant