Commit Graph
5 Commits
Author SHA1 Message Date
Menghuan1918 b7af498427 perf: stabilize renders (transcript row reuse, tree Sets, shared + options, batched drags)
SideChat transcript: every 2s poll rebuilt ALL row objects, so every
row (markdown parse + DOMPurify included) re-rendered even when the
log had not changed. transcriptRows now takes the previous poll's
rows and re-adopts the position-aligned rows whose content is
identical (reuseRows) — only the changed tail rebuilds. The view
feeds the memoized array back through a ref; unit tests pin the
identity contract (unchanged rows keep object identity, changed rows
and everything past the change rebuild, tool-result landings never
re-adopt).

FileTree: expanded/revealed membership was an includes() scan per
rendered row (O(rows × expanded) per render on deep trees); one Set
per render keeps it O(1) per row.

Shell: buildNewTabOptions was inlined in the two Workbench JSX
sites — two fresh arrays per render (drag state, resizes) re-rendered
every LeafView's + affordance. One shared memo now.

DiffPane resize / TerminalView ResizeObserver: pointer and resize
streams fired a setState / glyph-measuring fit() per event; both ride
per-frame batching now (frame-batcher in the core, a local rAF gate
in the lazy terminal chunk). SessionLens sizes each row's content
with a Blob (a UTF-8 encode of the whole edit) — that moved from
per-row-per-render into one memoized pass per changed fold.
2026-09-01 16:01:34 +08:00
Menghuan1918 561ad66071 feat(sidechat): main-conversation Blocks, turn usage tail, reconnect banner
Rendering-layer upgrade for the Side Chat transcript (data already rides
the raw events; the mapping just stopped discarding it):

- Tool rows with a derivable structure now render the host's
  ui-primitives atoms instead of a generic double <pre>: bash calls map
  onto TerminalBlock (command from call args, output + exit pill
  recovered from the result's trailing exit marker), edit/write map onto
  DiffBlock (call-time literal replacement, refined by the result meta's
  contextual hunks), read maps onto ReadBlock (line-window meta, same
  semantic narrowing the host applies). Every narrowing declines to the
  generic row on malformed input.
- turn/end now emits a turnSummary row: token usage aggregated from
  assistant/message.usage (output summed across steps, input from the
  last request) plus the envelope-time wall duration, printed with the
  main conversation's formatTokens/formatDurationMs cadence.
- The client entry injects 'connection' (DSH 0.1.2-alpha.2+) and the
  thread view shows the host's ConnectionIndicator while the Remote
  transport is down; the reconnect control calls reconnect() and a
  disconnected -> connected transition pulls the transcript immediately
  instead of waiting for the next poll tick.

19 new locale keys propagated across zh/en and all 19 third-language
dictionaries; unit coverage in sidechat-transcript.spec and a new
sidechat-view render spec (jsdom). Real-host mount smoke: 14/14.
2026-08-31 23:28:20 +08:00
Menghuan1918 28447ca2e8 fix(sidechat): read the transcript through the plugin's own events route
DSH 0.1.2-alpha.1 removed the client `ctx.connection.api` face (Remote
gateway migration), so every Side Chat transcript poll (`sessions.history`)
threw TypeError into the view's catch and was swallowed — the tab rendered
an empty transcript forever, silently (no console error, so the mount lane
never caught it). The successor APIs are not a drop-in either:
session/follow|page reject plain session addresses for origin:'subagent'
logs (agent-busy fence) and paging cannot read past the current cursor.

Move the transcript to the plugin's own wire — one transport for every
DSH generation:

- new `sidechat.events` route: live threads read the in-memory
  `agent.session.events`; cold threads read `sessionPersistence.inspect`
  (both seams exist since 0.1.0-rc.8; alpha.1's inspect returns the
  expanded logical log, never the packed chunk-row encoding). The
  inherited seed is cut host-side (`threadOwnLogEvents`), polls ride an
  `afterSeq` delta, and the response is tail-capped at 8000 events (the
  old client walk ceiling).
- SideChatView.fetchThread is single-path; collectOwnEvents (the client
  walk) is gone, and so are the ctx.connection mirror and inject entry
  (no consumer left).
- transcriptRows keeps re-cutting at session/end-seed as defense in
  depth.

Verification:
- unit: sidechat.events route (seed cut, afterSeq delta + never
  resurrecting the seed, cap, cold/live sources, 404/503/bad-request),
  full suite 1104 passed; typecheck and build (purity gate) green.
- mount e2e vs a real dsh-v0.1.2-alpha.1 source build: 14/14, including
  the Side Chat tab sweep polling sidechat.events (performance resource
  assertion) and live + post-dispose (persisted) route reads.
- mount e2e vs PATH dsh 0.1.0-rc.8: 14/14 (no regression).
2026-08-30 22:25:45 +08:00
Atlas Zeng b7aab48deb feat(sidechat): Codex 风格转录重构 — 首问显示 / 注入拆分 / 折叠行 / 弹窗去抖 (#314)
* fix(sidechat): split context injection from the first user message

The first composer message rode the same user/message event as the side
boundary prompt (+ parked snapshot), and the transcript dropped that whole
row by its boundary prefix — swallowing the user's first question.

Deliver first contact as TWO messages: the boundary+snapshot rides
agent.inject (queued next-step context, claimed first at the opening step,
no wake) stamped with source kind 'plugin', and the question is the
follow-up that wakes the driver. The log records injection and question as
separate events; the transcript maps injections (plugin-sourced, or the
legacy boundary-prefix blob of pre-split threads) onto a collapsible
injection row and renders genuine user messages — the first one included —
as user rows. boundaryDelivered still recognizes the injection by its
unchanged boundary-prefix opening.

* feat(sidechat): codex-style collapsible rows and creating shimmer

Tool calls, thinking and context injections now share one quiet
single-line chrome (chevron + label + a one-line argument summary) that
expands into an indented body on a hairline thread — no cards or fills.
Streaming labels and the creating hero shimmer (shimmer = generating);
failed tools go danger; reduced-motion stills every loop.

* fix(sidebar): debounce subagent auto-open past the title frame

The host delivers a new child's origin and title in separate frames, so a
Side Chat thread's first visible frame lacks its 'Side: ' label and
tripped the 0→N auto-open — every thread creation popped the task page.
The trigger now rearms for 500ms and re-evaluates the original baseline
against the live snapshot, by which time the title filter recognizes the
thread. Genuine subagents still auto-open, half a second later.

* fix(sidechat): render legacy threads' first message as its own bubble

Threads created before the host split still log the boundary (+snapshot)
and the question as ONE 'user' message; the transcript mapped that row
wholly onto the injection row, so the user's first question only appeared
inside the expansion. The boundary prompt is a known constant, so the
legacy row now splits THERE: the injection row keeps the prompt, the
remainder renders as the user's real message (a pre-split snapshot, when
present, rides along — the common case is a pure question). Row React keys
become kind:seq so the split's two rows never collide.
2026-08-22 15:34:05 +08:00
Atlas Zeng 7f4f84c4cf feat(sidechat): 侧边对话——每对话一 Tab(Codex 对齐)+ 完整上下文继承 + 保存为新会话 (#286)
三轮实测驱动迭代:种子信封透传(surfaceOp 校验)、每线程一 Tab 交互重做完、四连修(fork 丢 this / 自绘 history+save 图标 / collectOwnEvents 大页回源修重开丢工具行 / descriptor 入种子修目录 corrupt 行 + 前缀过滤保拓扑零噪音)。739 单测 + 挂载冒烟 12/12 全绿。
2026-08-21 15:41:14 +08:00