Uh oh!
There was an error while loading. Please reload this page.
feat(ui): rework streaming output — step timeline, deep-thinking disclosure, tool trows, word fade-in (#636 rebased) - #644
Closed
jackwener wants to merge 10 commits into
Closed
feat(ui): rework streaming output — step timeline, deep-thinking disclosure, tool trows, word fade-in (#636 rebased)#644jackwener wants to merge 10 commits into
jackwener wants to merge 10 commits into
Conversation
Add optional ToolCallMessage.stepId, stamped by ToolRuntime from getCurrentStepId() (same source as ToolStartEvent.stepId), and map it into refs.stepId on the backfill path so post-restart model replay re-pairs a tool call with its assistant step. The field ships with its first consumer, the UI turn timeline (materializeTurns, next commit).
Replace PR1's neutral multi-step concatenation with a TurnTimelineItem sequence (thinking / text / tools) rebuilt in storage order: each step's thinking and text precede the tools whose stepId matches that step; legacy stepless tools keep the tools-then-summary reading before the text; leftover and live-only tools flush as trailing tool groups; adjacent thinking and tool groups merge. Aggregate assistant/assistantThinking stay for copy/export/ prompt-rail consumers, with timeline as the rendering source of truth. ToolActivityItem gains stepId for the pairing. Tests cover interleaved multi-step, legacy single, pure-tool tail, live-only append, and merges.
…ol trow Render the turn body from turn.timeline (materialize.ts) instead of the tools-section + single-answer layout: each step's reasoning, answer, and tools appear in production order. Commits 2 and 3 of the plan land together because both restructure TurnView and can't be judged apart. - DeepThinking: one controlled Collapsible (collapsed by default; no defaultOpen) replacing ReasoningPanel and the .maka-turn-thinking <details>. Live: shimmering '深度思考' title (new TextShimmer primitive + governance keyframe maka-text-shimmer) and smooth plain-text body that follows the tail; settled: Markdown + '复制思考过程'. '已截断' pill preserved. - ToolTrow: a contiguous tool run as one flat, borderless disclosure — single-tool groups render the tool's own row (no double nesting); multi-tool groups add a summary line (shimmering active-tool description while running, bucketed Chinese counts once settled via the pure summarizeTrowTools) that expands to flat-stacked tool rows. waiting_permission auto-expands the group. ToolActivityCard body extracted as ToolCardBody, shared by card and trow. - Delete reasoning-panel.css + import, the .maka-turn-thinking / .maka-turn-tools token blocks; update chat-marker + 406-motion governance contracts. Full desktop suite (2224) + root typecheck green; trow-summary unit-tested.
Retire the ▎ blink caret (.maka-bubble-streaming::after + @Keyframes maka-cursor) and its trailing-<p> display:inline hack in favor of a per-word fade-in over the freshly revealed streaming tail. New pure primitive stream-fade.ts: an append-record ring tracks each growth of the displayed grapheme prefix as a batch, prunes batches older than the fade window, and caps retained batches. From it we read a stable boundary offset (older text never re-animates) and the age of any offset (drives a negative CSS animation-delay so the entrance resumes mid-flight across ~60Hz re-renders instead of re-flashing). A shared word/char tokenizer groups whitespace and Latin runs while splitting CJK/emoji per-grapheme. streamFadeRehypePlugin wraps post-boundary tokens in .maka-stream-fade spans after rehype-highlight, advancing the cursor through code fences but never wrapping inside them. useStreamFade feeds both the answer bubble (via markdown-body's rehype pass) and DeepThinking's plain-text live body. Under snap (reduced-motion / visual-smoke) the hook returns undefined and callers skip wrapping, so deterministic captures show final text at full opacity. @Keyframes maka-stream-fade-in animates opacity only; the fade window is --duration-large (280ms) and FADE_MS is pinned to the same value so the JS and CSS windows agree. Contracts updated: 406 governance functional motion set, markdown-prose retired-hack asserts. Unit tests cover the ring (window slide, order-independent ages, cap), the tokenizer, and the rehype offset bookkeeping (multi-block prose, code fences unwrapped).
Finish the streaming UI rework wiring so the in-flight (live) path and the review fixtures exercise the new per-step turn timeline. - app-shell-session-events: the tool_start handler now carries the event's stepId onto the live ToolActivityItem, so an in-flight tool is ordered after its own step's thinking/text in the turn timeline instead of being lumped into one trailing group. Optional — legacy / degraded history emits no stepId and keeps the old grouping. - chat-surface stories: add a MultiStepReasoning story whose fixture is a single turn with two think->say->call steps (per-step thinking + text + stepId-tagged tools), so Storybook covers the 深度思考 → answer → tool trow per-step order. - visual-smoke turn-narrative seed: append a second, multi-step turn to the committed narrative so the auto-capture baseline locks the new per-step timeline alongside the existing legacy stepless turn. Verified: @maka/ui build + test, desktop build:main + full node:test suite (2237), @maka/runtime test, and root typecheck (incl. renderer + storybook projects) all green.
projectFunctionCall dropped event.refs.stepId, so sessions rebuilt from the runtime event log (the session view's source of truth) lost the tool-to-step pairing after reload and the UI timeline fell back to legacy tools-before-text ordering. Carry it through and lock with a projection test covering both the stepId and legacy no-stepId shapes. (Codex review P1.)
The fade ring measures raw buffer graphemes but the rehype pass walks rendered visible text, which link URLs and emphasis markers leave far shorter; once the cumulative hidden amount outgrew the 280ms window the streaming tail silently stopped fading. Count the visible graphemes, shift the boundary and age lookups by the hidden total (exact at the tail, eager near the boundary), and lock with a link-syntax test that asserts raw-coordinate age lookups. (Codex + Pi review P2.)
kind+messageId is already unique across a turn's timeline (one thinking and one text per step; tool groups keyed by first toolUseId). Folding the index in meant a group inserted mid-timeline remounted every entry after it, collapsing disclosures the user had opened. (Codex review P3.)
… answer A step that only calls tools persists no assistant row, so its tools carry a stepId no assistant message matches; buildTurnTimeline parked them in pending until the end of the turn, rendering the common 'call tools, then summarize next step' pattern as answer-then-tools. Ledger append order guarantees no later step's tools can be pending at an assistant row, so flush the orphans ahead of the step's content. (ChatGPT review P1.)
The group only auto-opened for waiting_permission, so an errored tool's banner and output hid behind the collapsed summary line — a diagnosability regression from the boxed cards, which kept errored tools expanded. Extract the predicate as trowNeedsAttention and unit-test it. (ChatGPT review P3.)
jackwener
commented
Jul 8, 2026
MemberAuthor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
收编 #636(作者 @Astro-Han,保留全部原始 commits)+ rebase 到最新 main。
Maintainer review 结论(第一性原理)
高质量 PR,三层核心全部通过:数据层 stepId 可选兼容+双向投影;时间线聚合三类工具排序均有 ledger 论证;stream-fade 只包裹尾部新词、负 delay 防闪、snap 全跳过。治理意识好(动画白名单登记、时长绑 token)。
非阻塞注记:流式期间每帧全文 grapheme 分词——与 react-markdown 每帧全量 parse 同级,未改变复杂度;未来流式增量化时一起收益。
Rebase 冲突
与 #639(CronJob 预览)在 tool-activity.tsx 相遇:取本 PR 的 ToolCardBody 抽取结构,CronJob 预览挂进共享 body 的结果分发(card/trow 同享)。
验证
build ✓;typecheck 0;ui 46/46;desktop 2248/2248(含本 PR 新增三组契约/单测)。