返回 DeepSeek-Reasonix
TRANSCRIPT_ARCHITECTURE.md
根目录 / docs / TRANSCRIPT_ARCHITECTURE.md
1 # Natural-flow chat transcript
2
3 This is the sole production chat renderer, replacing TranscriptKernel, the window adapter and the measurement ledger. Rendering reference: local DeepSeek Harness `c291e7961a`. Reasonix keeps its controller, storage, composer, approvals and workbench; current synchronization and history behavior follow [Transcript v2](TRANSCRIPT_V2.md) and the [scroll and history contract](TRANSCRIPT_SCROLL_CONTRACT.md). Referencing Harness does not imply adopting its complete turn-outline navigation.
4
5 ## Ownership
6
7 ```text
8 Local / remote Follow v2 → shared consumer and bounded record store
9
10 Transcript session adapter
11
12 ChatSource: stable order + independent node/status subscriptions
13
14 ChatNodeList → ChatNodeSeat → message / process / tool / notice / tail
15
16 native document flow
17
18 DOM resize + reader intent → ChatScrollController → TranscriptViewportWriter
19 Full content references → ChatContentLoader → bound session content APIs
20 Markdown source → shared worker → stable prefix blocks + mutable streaming tail
21 ```
22
23 - `src/lib/chatViewSource.ts` owns a reconstructable projection, not another event log. Keys derive from existing message, call and user-turn identities. Unchanged order/node snapshots retain references. Structural publication coalesces in a microtask; stream updates use existing controller frame batching and match the live ID. Settlement replaces the same assistant host.
24 - `src/components/Transcript.tsx` adapts local and remote hosts. Lists subscribe only to order; seats subscribe to themselves and their process disclosure. Status/timers, navigation and drawer have separate subscriptions. Session replacement disposes subscriptions, queued publications, loader leases and observers.
25 - `src/components/ChatNodes.tsx` renders messages, thought/process disclosures, compact tools, notices, compaction, extensions and turn actions. Closed heavy bodies are not mounted.
26 - Loaded history remains in natural flow. There is no chat virtual window, absolute row positioning, resident/cold handoff, size ledger, geometry-state feedback, logical selection overlay, renderer override or safe-mode remount. TanStack remains for unrelated consumers.
27
28 ## Product behavior
29
30 The column is at most 800 px wide, with 24 px horizontal padding (16 px in narrow chat containers). Existing typography/themes apply. Native selection and scrollbars are used. History uses a bidirectional bounded window, retaining three adjacent pages of 32 messages by default. Paging beyond the budget reclaims the opposite end. Navigation lists loaded turns only; complete persisted history remains searchable and reachable through canonical search/locate.
31
32 ## Loaded-turn navigation and history access
33
34 Follow v2 intentionally derives the rail from loaded turns. Its marks reflect the resident window, not a complete conversation index. Reading older pages isolates live output; paging forward or locating a message makes reclaimed history reachable again. Reclaiming a page releases resident content without deleting persisted messages.
35
36 The rail navigates within loaded turns. To reach unloaded history, canonical search/locate resolves the target through the history index and requests its surrounding page; it does not sequentially load every intervening page from the newest position. Navigation obeys the scroll contract's generation and interaction fences: reader takeover cancels pending navigation, and stale callbacks cannot regain viewport ownership.
37
38 PR #10385 supersedes the complete-outline and cumulative-history behavior previously documented here. The [#10276 outline acceptance record](TRANSCRIPT_OUTLINE_NAVIGATION.md) preserves that earlier implementation and its validation; it does not define current production behavior. Desktop and Serve require `transcript-v2`, with an upgrade error for unsupported peers and no legacy chat fallback.
39
40 | Capability | Result |
41 | --- | --- |
42 | User content, attachments, images, copy | Kept; chat edit-and-resend removed |
43 | Assistant Markdown, code, tables, math, images, safe links and citations | Kept; answer source is not truncated |
44 | Reasoning | Latest nonempty line while streaming; first line and existing duration after completion; lazy disclosure |
45 | Tool/subagent progress | Compact name, subject and status; independent details drawer |
46 | Turn process | Collapsed only with a final answer, complete user boundary and successful completion |
47 | Partial, failed, interrupted, tool-only or incomplete-page turns | Output and faults remain visible |
48 | Turn actions | Copy complete answer; ordinary conversation fork using an eligible checkpoint |
49 | Rewinds, worktree forks, summary/delivery/acceptance/verification workflows | Removed from chat; backend/other consumers retained |
50 | Context recovery, history errors and interactive extensions | Kept through existing command/interaction hosts |
51 | Selection popup and permanent transcript diagnostics | Removed; native copy and development diagnostics retained |
52
53 Manual process disclosure survives session navigation in a bounded in-memory map; streaming does not override it. Fork is disabled during running, hydration, pending actions, read-only state or missing `canConversation` checkpoint capability. It calls the existing ordinary `fork` command.
54
55 The drawer overlays the column: `min(560px, 60%)`, full width below a 900 px chat width. It has independent scrolling, an inert background, focus trapping, Escape dismissal, child/parent call navigation and post-commit trigger-focus restoration. Session changes unmount it; target changes reset its request epoch.
56
57 Tool and thought previews use 8,000 characters. Full loading/copying exposes pending/error/retry state; copying awaits clipboard completion. Code initially shows 200 lines and copies its entire source even when collapsed. Browser find covers mounted content only.
58
59 ## Full content and asynchronous ownership
60
61 History still uses existing page cursors. Prepend projects new nodes and repairs turn boundaries; replacement rebuilds from the current authoritative source. Snapshot revision, session generation and stream-attempt ownership remain in existing layers.
62
63 `ChatContentLoader` limits each mounted session to four active requests. Equivalent item content shares a promise; changed source content does not reuse an older request. Disposal fences queued/in-flight results. Completed requests are removed instead of forming an unbounded full-text cache.
64
65 User/answer bodies resolve automatically; thoughts/tools resolve on full-disclosure or copy requests. Snapshot reads are field-selective, so reading an answer does not eagerly load its thought. Tool details read detached raw immutable records, bypassing Item preview/archive limits. Fetched tool bodies are not patched into the controller or retained in the cut. Small references and inline tool records remain subject to the existing inactive-cache budget, allowing reopening. Stale cuts use the existing reload path and expose retryable errors.
66
67 The legacy history store no longer starts whole-page reference prefetches outside the loader budget. Its tool reader resolves references by call ID without expanding sibling calls or caching their full bodies. An unresolved or stale body reference cannot fall back to a successful copy of its preview.
68
69 The UI checks source identity before accepting full content. Drawer closure, target changes and session replacement invalidate old callbacks. Worker parsing checks message/text revision and mount lifetime. `surfaceCommitToken` readiness follows the correct initial DOM commit and two animation-frame opportunities; stale effects are canceled.
70
71 ## Scrolling and rendering
72
73 `ChatScrollController` holds follow intent, stable node key/viewport offset, preceding keys, native offset and task epochs outside React. `TranscriptViewportWriter` is the only direct chat scroll writer, enforced by the static gate.
74
75 Initial entry follows latest; revisiting restores bounded in-memory position. Even a small upward wheel movement releases follow inside the 24 px bottom tolerance. Touch, scroll keys and scrollbar input acquire reader ownership. Downward arrival within 24 px resumes follow. Return-to-latest and a new running user turn explicitly resume follow.
76
77 Prepend, disclosure, image/Markdown layout and input-area height changes use the stable node and offset. A disappearing process child falls back to its summary; a removed node falls back to a surviving previous node or the first node. User scrolling during pagination updates the anchor. There is no scrollHeight-difference compensation.
78
79 One ResizeObserver observes the column, viewport and mounted nonempty node hosts. A MutationObserver refreshes the observed host set. Both coalesce into an animation frame; neither publishes geometry into React nor writes synchronously in ResizeObserver delivery. Browser auto-anchoring is disabled. Unchanged writes are no-ops; settled content must stop producing writes.
80
81 Streaming and final messages share MarkdownHistory. Worker parsing reuses stable prefix blocks and changes the mutable tail. Final parsing resolves references, footnotes and incomplete syntax without replacing the whole answer. Parsing failure is isolated to a copyable raw fallback and lightweight notice. Tables have natural rows and horizontal overflow; long code uses disclosure, never vertical virtualization.
82
83 Within the resident history window, offscreen completed source text stays mounted as plain text until worker formatting activates near the viewport. Parsed blocks remain mounted while their page remains resident. Page reclamation releases the associated rows and parsing work; lazy formatting does not imply indefinite retention of previously loaded text. Closed process bodies deliberately unmount.
84
85 Worker clients are leased by mounted sessions. Last release terminates pending tasks; aggregate diagnostics retain numbers only, not source or AST data. Disposal releases observers, source listeners and full-content results. Existing bounded caches own inactive history.
86
87 ## Compatibility and verification
88
89 The current Desktop/Serve protocol boundary is `transcript-v2`; compatibility and derived-index changes are described in [Transcript v2](TRANSCRIPT_V2.md#compatibility-and-change-notes--兼容与变更说明). Persisted session-log encoding, provider messages, tool schemas and prompt-cache bytes remain unchanged. Old persisted display preferences cannot select an old renderer. Any rollback must keep Desktop and Serve protocol-compatible; a frontend-only rollback is not a general compatibility guarantee.
90
91 Run from `desktop/frontend`:
92
93 ```sh
94 pnpm test:transcript
95 pnpm test:stream
96 pnpm test:composer
97 pnpm test:remote
98 pnpm test:app-lifecycle
99 pnpm test:motion
100 pnpm test:typecheck
101 pnpm build
102 node scripts/run-tests.mjs --keep-going
103 CHAT_BROWSER=chromium CHAT_EXPANDED=1 CHAT_SOAK_SECONDS=60 node bench/chat-transcript.mjs
104 CHAT_BROWSER=webkit node bench/chat-transcript.mjs
105 CHAT_BROWSER=electron node bench/chat-transcript.mjs
106 node bench/transcript-layout.mjs
107 node bench/transcript-layout.mjs --electron
108 node bench/composer-transcript-stability.mjs
109 node bench/run.mjs
110 ```
111
112 Point `PLAYWRIGHT_BROWSERS_PATH` at the installed browser cache when needed. Chat replay uses the real Transcript, Markdown and Composer in a production fixture. The whole-app benchmark additionally uses actual application composition with the existing mock transport. Neither substitutes for a live backend/native-IME soak.
113
114 Gates remain input P95 ≤200 ms, switch P95 ≤300 ms, longest task ≤500 ms and released heap growth ≤20 MiB. See [measured acceptance evidence](CHAT_REFACTOR_ACCEPTANCE.md), including unverified platforms.
115
115 lines MARKDOWN