Commit Graph
127 Commits
Author SHA1 Message Date
bowenliang123 59cd1be501 feat(client): expand the plugin settings card on the Open-in-Settings jump
The settings shell keeps its open/section state in private component
state and offers no plugin-facing open face, but the card registered on
settings.plugin.item is ours. After the jump's settings-trigger click
survives, it raises a short-lived in-bundle expand request; the card
consumes it on mount and lands open, scrolled into view. Degraded jumps
raise no request, and the request expires in 5s, so no stale signal
surfaces on a later manual visit.
2026-08-29 13:32:09 +08:00
bowenliang123 0090fa3a57 feat(plugin-info): add a settings entry below the GitHub row
The harness settings panel keeps its open state inside the shell
component and exposes no plugin-facing open face, so the entry drives
the real chrome best-effort: click the sidebar's dialog trigger, then
the Plugins section nav row (matched by its shipped en/zh labels).
Every step is individually guarded - a shell without a matching
trigger or section degrades the whole jump to a silent no-op, and no
path can throw into the render.
2026-08-29 09:54:24 +08:00
bowenliang123 5f9bc0d2dc feat(context): split the stats board into context/timing/token cards
The host fold derives whole-session timing totals from the durable
step/tool lifecycle (step/start -> assistant/message -> step/end;
tool/call -> tool/result by callId): step wall time, the model-call
slice, and per-call tool durations with a bounded per-name tally
(stateVersion 11; cached rows refold from the log).

The head row becomes four equal cards: an eight-cell context grid
(shape stats, event tally, cost estimate with its rate tooltip), a
token ring around the cache-hit share, and a timing ring splitting
model calls vs tool runs. Legend rows pair each donut slice with
its label, quantity, and share - no bars; the ring center type
scales with the ring so it never touches the stroke. Wire guards,
i18n, styles, and hostile-input test fixtures included.
2026-08-29 09:22:09 +08:00
bowenliang123 6654954236 feat(browser): open the lone row with a single-item category
Every one-item category (not just system) now auto-expands its only row
when the category opens, so one click lands on the content directly:
the lone tool schema and the lone surface node join the system prompt.
Multi-item categories keep opening collapsed.
2026-08-29 02:12:27 +08:00
bowenliang123 0c8312763e feat(client): jump from a chat reply to its turn in the Context tab
Add an icon to each finalized reply's assistant-actions row that opens
the Context tab pinned to that reply's turn: the click resolves the
reply's request seq off the served node seat, records it in a per-session
relay, and activates the tab; the view consumes the relay once its
projection data is in, flips the trend chart to turn bars, pins and
centers the turn's aggregate, and resets the shared scroller. Every hop
degrades quietly (unknown seat, unresolvable seq, missing tab, hostile
nodes, aged-out turn).
2026-08-29 01:44:51 +08:00
bowenliang123 86333a80d5 feat(client): link the trend card highlights to the shared category hover
Hovering a category in the Context browser (or the overview legend/bar)
now lights that category across the trend card: the matching segment in
EVERY chart bar (total and delta modes), the detail panel's composition
bar, and its per-category rows.

The chart highlight rides a plain container attribute (`data-catdim`) +
static per-segment `data-cat` keys, so the dim/brighten pairing is pure
CSS and the memoized bars never re-render on a cross-card hover. The
detail bar mirrors the key one-way with its tooltip off (a cross-card
hover must not float a second tip), and the detail rows reuse the
legend-chip idiom: tinted background, bold label, ring on the color dot.
The overview's 'free' key stays filtered, as in the browser's live link.
2026-08-29 00:53:48 +08:00
bowenliang123 7b61d5ddc7 feat(browser): dual-face compatibility across the dsh 0.1.1/0.1.2 seams (#26)
0.1.2 rewrote the client composition and the history transport; every seam
this plugin touches now resolves by feature detection and degrades visibly
instead of erroring:

- history reads: `remote.session.page` (0.1.2; the inclusive cut
  `throughSeq` pinned to the target seq) over `connection.api.sessions.history`
  (0.1.1); both served envelopes unwrap ({ok,value} and {result:{ok,value}});
  packed chunk-row records skip (final events always ride verbatim); a
  malformed payload rejects into the retryable state instead of claiming
  absence.
- conversation join: `useChat` (ChatSnapshot.legacy.nodes) over the session
  snapshot's top-level `nodes`, both seats invoked in a stable hook order —
  fixes the empty File Activity card on 0.1.2 (#26).
- image loading: `uiConversation.imageUrl` (0.1.2) over
  `conversation.resolveImage` (0.1.1); absent faces degrade the cards to
  metadata-only.
- `/context` command: waits for the `inputTriggers` service (ctx.inject)
  instead of racing module arrival order, which 0.1.2's finer modules broke;
  the modal resolves `sessions` at close time for the same reason.

Absent faces drop features (static out-of-window hints, no command) — the
tab and the modal never render an error page.
2026-08-28 22:32:19 +08:00
bowenliang123 d25d44aff1 fix(host): keep undefined-valued scalars off the wire view (#29, #30)
model/provider/contextWindow now join the served snapshot by conditional
spread: an unknown value leaves the key ABSENT instead of materializing an
`undefined`-valued property. The harness's lossless-JSON pipeline rejects
such a view whole — on 0.1.2 that fails every projection push for the
session (the fresh-blank-session breakage reported in #29/#30; the same
property discipline the persisted state has held since stateVersion 10).
Guarded by plain-JSON view assertions over both the unknown and the known
model shapes.
2026-08-28 22:32:01 +08:00
bowenliang123 83bc071338 feat(browser): number the lines of the raw text view
Each source line becomes a block span whose counter-fed ::before sits
in a narrow left gutter; pseudo content never reaches the clipboard and
user-select none keeps it that way, so copying the body still yields
the exact source. Interior empty lines keep their height, a trailing
newline adds no row, and soft-wrapped continuations align at the
content column.
2026-08-28 12:15:40 +08:00
bowenliang123 8a5f1e0b02 style(i18n): per-category placeholders for the row filter
Each category's filter box now names what it scans — message text
(user), injected context (inject), reply or calls (assistant), tool
or arguments (tool results) — instead of one generic "filter by
text". The tools category keeps its existing placeholder.
2026-08-28 10:08:56 +08:00
bowenliang123 093dd9f69e refactor(browser): a shared row filter across every category
The tools' text filter generalizes into the browser-wide row lens: a
shared RowToolbar (input plus an optional control group) now filters
the four message categories too — user, inject, assistant, and tool
results — matching each row's tag and preview text exactly as shown
(call breadcrumbs and call-args summaries included; full bodies stay
out of scope, they load on demand).

The node-list branch splits into a derive pass (each row's tag +
preview facts, computed once) and a filtered render pass, and the
query is a lens on the OPEN category: opening a different one resets
it while step picks keep it. System stays searchless (one row) and
sort stays tools-only (reading order is meaningful for messages).
2026-08-28 10:07:21 +08:00
bowenliang123 d6cd02acad feat(browser): filter and sort the tool-schema rows
The tools category gains a local view state: a text filter scanning
each row's name, producer description, plugin chip, and raw parameter
JSON (a hostile schema degrades to no matchable text), plus a
size/name sort toggle — size (token price, mirroring the overview's
Top chips) stays the default, name gives lookup. The toolbar stays
mounted on an empty match so the filter can always be cleared, and
both survive step picks so one lens compares epochs.

Reuse the FileCard's control idiom: the search input and toolbar
share the lc-fa-search/lc-fa-ctl rules under browser-scoped names.
2026-08-28 09:58:13 +08:00
3244ff29a9 feat: tool plugin source attribution in the Context browser (#25)
Tool-schema rows in the Context browser carry a best-effort source tag
for the registering plugin, recovered without a harness change:

- a harness-logged `plugin` field passes through verbatim (future-proof);
- MCP-proxied tools resolve `mcp:<server>` from their public names;
- shipped first-party tools resolve from a pinned package map;
- a live `tools.register()` hook (cordis `internal/get` reader slot,
  with a caller-package stack fallback for local-link installs) tags
  third-party, agent-scoped, and dynamic registrations, and peels a
  prior incarnation's wrapper on reload;
- tools already registered when the plugin mounts render a localized
  "unknown plugin" tag explaining the boot-order gap.

Priority: `mcp:<server>` > live record > pinned map > unknown. The
chip rides the shared tag frame tinted with the brand badge tokens.

Rebased onto v0.35.0 with maintainer review fixes; the attribution
priority and reload-safety rework and the chip theming are described
in the PR review comment.

Co-authored-by: Nwflower <1679659@qq.com>
Co-authored-by: bowenliang123 <liang.bowen.123@qq.com>
2026-08-28 09:50:10 +08:00
bowenliang123 15ae3b96c3 feat(files): open files on the system; display workspace paths relative
The File Activity card gains two affordances, both resolved from the
client ctx and degraded per-host:

- Workspace-relative display: paths inside the session's cwd (read off
  the session-list row) shorten to a './'-relative form; absolute paths
  outside the workspace, Windows drive paths, and already-'.'-relative
  paths keep their verbatim form. Path search matches the display form.
- System open: when the host description advertises canOpenPath and the
  connection api exposes host.openPath, a file name opens on the user's
  system (fire-and-forget, failures stay silent); otherwise it renders
  inert. A pathless search books its pattern as a display-only pattern
  row — never relativized, never openable — including nested Code-Mode
  searches, which previously leaked the pattern into a resolvable path.

workspaceOf and canOpenPathsOf re-prove every field and bound hostile
snapshots (a throw on call or property access degrades to undefined).
Component specs for the card are restored and extended for the new
props; the fold gains a pattern-row case for nested searches.
2026-08-28 03:11:23 +08:00
bowenliang123 059c85a54c feat(files): show each read's line footprint; op times on the right
Reads now carry their line footprint in the op log, in a third color
next to the +/- deltas: `>>30` for the exact window read off the
result's presentation meta (hover names the line range), degrading to
`≈80` estimated from the limit argument when no meta survived — and
staying silent for an unbounded read, which names no footprint. Nested
PTC reads estimate the same way.

The op timestamp moved from the leading column to the right edge,
mirroring the file row above it.
2026-08-28 01:27:42 +08:00
bowenliang123 891c847f13 feat(files): render code files as language letter badges
Code files showed the same 🧩 puzzle piece. They now render as compact
letter badges over their language's GitHub-Linguist color — TS, TSX,
JS, PY, GO, RS, JAVA-equivalent short labels capped at three
characters — with the text color chosen per fill luminance (near-black
on the light shades, white elsewhere). Non-code files keep their emoji
glyphs (config, data, markdown, logs, sheets, documents, archives,
fonts, media), and each row's hover title names the resolved bucket in
both locales.

The icon column is a fixed-width flex cell so emoji rows and badge
rows align on the same path edge, with the chevron pulled in to keep
the left cluster tight. Test-file detection drops the .spec. form
(dot-delimited .test. and the underscore forms stay).
2026-08-28 01:03:28 +08:00
bowenliang123 1f10674999 feat(files): type-aware row icons in File Activity
Every row showed the same 📄. The row glyph now comes from glyphOf, a
table-driven fold over the entry's form and name: image/dir forms keep
their icons, directories resolve by last segment (root, tests, docs,
deps, build output, scripts, config, assets, hidden metadata), files
match special names first (lockfiles, test files, Dockerfile,
Makefile, .gitignore, LICENSE, .env) and then extension buckets
(python, notebook, shell, web, styles, database, config, data,
markdown, log, sheets, documents, archives, fonts, media, and a code
catch-all), falling back to 📄. The row's hover title names the
resolved bucket via new files.glyph.* keys in both locales.
2026-08-28 00:33:49 +08:00
bowenliang123 3ac8689feb feat(plugin): make the info-card tagline link to the repo 2026-08-28 00:14:21 +08:00
bowenliang123 6275ab00d7 fix(host): keep the projection folds total over untrusted session-log events
The projection registry drives apply straight off the session/event bus
with no error boundary: one throwing fold stalls the unit's cells and
the contextTimeline push feed — the browser waits on "loading" forever.
Three real throw paths existed, and one of them also broke the
plain-JSON checkpoint precondition (one undefined-valued property fails
EVERY projection-cache write for the session):

- applyTimeline now isolates each event: a malformed one is dropped
  whole (all-or-nothing — the state reference is untouched), so no
  event shape, present or future, can take the fold down.
- A tool/result with no paired tool/call (aged-out call event, foreign
  producer, duplicate callId) no longer stamps an undefined-valued
  node.tool onto the surface node.
- pricing and header-epoch folds tolerate null or primitive elements
  inside content-block and tool arrays instead of throwing on them.

Hostile-fixture tests lock each path: a log missing data.message, null
blocks nesting inside tool-result content, unpaired results, and junk
tool entries — every fold state still losslessly JSON-serializable.
2026-08-28 00:06:02 +08:00
bowenliang123 b7741360ed fix(files): survive conversation join gaps in the file-activity fold
A file-kind tool node whose conversation join entry is gone entirely
(an aged window — exactly the sessions the card renders worst) crashed
the whole Context tab: the fold dereferenced the undefined node to read
its error flag. The optional chain restores the skip the args guard
used to provide, and the per-node fold is isolated in a catch so no
single join node — even a hostile one throwing on property access —
can take the walk or the card down.
2026-08-27 23:54:21 +08:00
bowenliang123 cbbbfa5ff7 feat(files): count PTC nested calls, str_replace_editor, and real search targets
File Activity only folded top-level tool-result nodes, so PTC sessions
(run_code as the only tool) showed an empty card and the Minimal preset
went unseen. The fold now covers every surface that executes a file op:

- Nested Code-Mode calls: each settled sub-call in the conversation
  node's subCalls tree folds as one op, attributed to the nested tool
  and located on its parent run_code result (FileOp.parent; the op's
  own seq names the dispatch event, which has no surface row). The op
  log shows the program's description as the "why". Narrowing is fully
  defensive: malformed blocks drop out, and the walk is bounded by a
  depth cap plus a visited-set cycle guard.
- str_replace_editor (Minimal preset): view reads; create,
  str_replace, and insert write with their own line deltas.
- Searches resolve to real files when the result's presentation meta
  carries the complete matched-file list (FileOp.hits shows the count,
  include filters join the detail); a capped or malformed meta falls
  back to the searched path/pattern as before, and meta attribution
  survives an aged-out call head.

Closes #24
2026-08-27 23:48:32 +08:00
Bowen Liang ce93eb755d fix(trend): hold the step brief at a constant height while scrubbing
Hovering around the trend chart used to change the detail panel's height: the brief section only rendered rows that resolved (the turn opener vanishes outside retention, usage-only replies carry no reply node), and collapsed entirely - margins included - when nothing did.

- the brief lane ALWAYS renders: a fixed three-row min-height, rows appear inside it, unknown steps leave blanks instead of collapsing
- the In row is one of those constants - an empty step shows a muted "(nothing new)" placeholder in place of its chips, and stays inert (a row with no node has nothing to locate)
2026-08-27 02:51:04 +08:00
Bowen Liang fb99ba983a fix(client): remove the trend chart scroll edge fades
The 26px gradient overlays at the chart's left/right edges (shown when the history overflows) read as washed-out white patches; drop the overlays, their edges state machine, and the .lc-chart-fade styles. Scroll anchoring, turn-label centering, and the hover tip are untouched.
2026-08-27 02:12:56 +08:00
Bowen Liang 8e0cc30cc7 fix: move the trend hover tip out of the chart scroller to stop hover layout jumps
The absolutely positioned tooltip contributed scrollable overflow inside
.lc-chart-scroll: a wide reply preview hovering a right-edge bar inflated
scrollWidth and toggled the horizontal scrollbar per hover, jumping the
whole trend card. A new positioned .lc-chart-wrap now anchors the edge
fades and the tip beside the scrollable content; syncTip glues the tip to
its bar visible slice via transform (edge-clamped, centered when wider
than the viewport), which also pins the left fade that used to drift
off-screen with the scrolled content.
2026-08-27 01:50:14 +08:00
Bowen Liang 1a31b33191 feat: Agent Network card — live multi-agent context map in the Context tab (#21)
A new Agent Network card at the foot of the Context tab: the whole agent family (current agent, ancestors, subagents at any depth) as a live, fully responsive node map — fused context rings per session, family-colored lineage, halo status language, hover inspector, and click-to-jump into any agent's session. 693 tests, 100% coverage.
2026-08-26 23:09:19 +08:00
bowenliang123 3c7ca98d32 feat: fall back to npmmirror when official npm registry fails
Order registries as official npm then npmmirror; each source failure
(non-ok response, missing/non-string version, network error) advances to
the next source, and only an all-source failure resolves to null so the
plugin info card keeps its baked-in version. TTL cache and module-level
promise reuse are unchanged.
2026-08-26 17:16:09 +08:00
bowenliang123 61f3405471 fix(client): stop remounting the events list on every push
Two compounding defects made every incoming event flash the whole card:

- Rows were keyed by seq PLUS reverse-index; the list renders newest-
  first, so an appended event lands at index 0 and shifted every
  existing row's key, forcing React to destroy and rebuild the entire
  list on each push. Rows now key on the durable seq alone, so a push
  prepends one row and keeps every other row's DOM node.
- The truncation-title layout effect had no deps: every render (hover
  included) re-read scrollWidth/clientWidth for every label — one
  forced synchronous layout per row — and rebound the resize listener.
  It now resyncs only when the events list changes, with the resize
  listener bound once (root resolved inside the callback so the
  empty-to-non-empty transition keeps it live).
2026-08-26 16:39:54 +08:00
bowenliang123 01d16bbcf0 fix(client): re-anchor the trend chart only on real data pushes
The chart's layout effect ran after every render (no deps), so any
hover/select re-render near the right edge re-ran the stick-to-end
check — and by the time it read el.scrollWidth the value was already
the post-append width, which could misfire the anchor. The effect now
runs only when granularity, focusTurn, or the request list changes,
and the near-end check compares against the PREVIOUS pass's scrollWidth
(kept in a ref), so a reader near the right edge still follows an
appended bar while a mid-chart reader is never yanked back, and a
hover/select render never touches the scroll at all.
2026-08-26 16:39:19 +08:00
bowenliang123 bb05794fa0 test(client): silence deliberate render-error noise in error-boundary specs
React 18 dev replays a failed render through a fake DOM event; jsdom
reports it as an uncaught window error and vitest forwards it to an
uncaughtException, which no console.error spy can catch. A window error
listener that prevents the default keeps the deliberate throw inside the
test, alongside the spy silencing React's own boundary message.
2026-08-26 14:27:37 +08:00
bowenliang123 53feea9fea feat(client): list every File Activity op on expand and drop the unresolved footnote
- Remove the MAX_OPS=8 cap: an expanded file renders its whole operation
  log instead of folding older ops behind a static "+n earlier ops" line.
- Remove the unresolved counter and footnote: a file-kind call whose
  arguments aged out of the retained window names no target and is now
  skipped outright, with totals[kind].ops incremented only after path
  resolution — every op the card counts, it also rows.
- Drop the files.unresolved/files.moreOps strings and the dead
  .lc-fa-more/.lc-fa-note rules.
2026-08-26 13:58:22 +08:00
bowenliang123 be06b09940 feat(client): tint active File Activity kind chips and count-sort by the selected kind 2026-08-26 11:36:39 +08:00
bowenliang123 1a046f63c5 feat(settings): add a default sort preference for File Activity 2026-08-26 11:28:49 +08:00
bowenliang123 49c65b017c feat(client): add a by-path ascending option to the File Activity sort 2026-08-26 10:45:58 +08:00
bowenliang123 14dc922c63 feat(client): add a most-active/latest sort toggle to File Activity
The file list now defaults to most-operations-first (ties broken by
recency) and a lc-gran toggle on the meta strip switches to
most-recently-touched order; the sort applies to the filtered copy, so
the fold keeps serving entries latest-first.
2026-08-26 10:33:18 +08:00
Bowen Liang fe7d8dafb9 feat(client): replace the Messages card with a File Activity card
The bottom-right card no longer lists model-visible messages; it answers
what the agent DID to the user's files up to the picked step. Tool-result
nodes (live + archive) join the conversation snapshot for call arguments
and fold into one row per file with per-purpose counts (read/written/
searched), estimated line deltas from edit/write arguments, multimodal
and directory form glyphs, and failure dots. Header chips double as
purpose/form filters next to a path search box; a row expands into the
file's operation log, and each operation reveals the exact tool result
in the Context browser. The card scrubs live with the trend chart pick
(exclusive next-request bound); ops whose paths aged out of retention
are honestly counted in a footnote.
2026-08-26 08:36:02 +08:00
Bowen Liang da394d7e0a refactor(context): drop the 'Top Tool Schemas' chip row from the overview
The chip row lived in CurrentComposition, was fed the fold's per-tool
toolList summary, and the chips focused the Context browser on a tool
via a one-shot toolFocus callback. The browser already exposes the
tool schemas through its headers section, so the chip row was a
duplicate entry point that no longer pulled its weight.

Removes the lc-tools DOM block, the toolFocus/onToolFocusHandled
prop wiring across ContextView/ContextModal/Browser, the
'tools.top'/'tools.more' translations, the .lc-tools* CSS, and the
now-unused toolList summary across the wire + state schemas
(stateVersion 9 -> 10). estimateToolSchema stays because headers.ts
still prices individual tools for the browser's tool-schema cards.
2026-08-26 03:45:04 +08:00
Bowen Liang 5cc028ff00 feat(client): replace history paging with seq-targeted content fetch
The Context browser joined full content against the chat conversation
window; when a surface node's seq sat outside it, the fallback paged
blindly backwards through session.loadOlder() (up to 20 pages of 50
messages prepended into the shared chat window) and finally dead-ended
on a static note.

sessions.history already supports seq-anchored pages cut on whole
append-origin message boundaries, so ONE call with beforeSeq: seq + 1
returns exactly the page holding the event whenever the durable log
still has it. The browser now:

- fetches on miss with that single anchored read and maps the raw page
  into the node shapes it already renders (new client/historyPage.ts:
  user messages, assistant blocks, tool results paired with their
  in-page call head, compaction checkpoints with their summary);
- merges fetched nodes under the zero-cost window join (join wins);
- reports honest states instead of one dead end: loading, absent from
  the session log (compacted or cleared), failed with a retry button;
- drops the sessions.provide contribution and the MAX_AUTO_PAGES
  machinery entirely - the chat scrollback is never mutated again.

Hosts without the connection api keep the static preview-plus-hint
degradation.
2026-08-26 03:34:24 +08:00
Bowen Liang a9b6d287fe test: remove the legacy bundle-dependent suite
All 19 root-level specs and the fake-React viewBed helper are
superseded by the source-level suite (606 tests, 100% per-file
coverage). The packaging behaviors they pinned (bundle handoff, CSS
channel, HMR disposal) live on in scripts/bundle-smoke.mjs.
2026-08-26 00:58:57 +08:00
Bowen Liang f106b720ac test: rebuild the suite at source level with 100% per-file coverage gates
The old suite exercised the built lib/ bundle, so v8 remapped almost
nothing (8% src coverage) and every run needed a build first. The new
suite imports src/ directly under two vitest projects (node for
host/shared, jsdom for client), rendering real React 18 trees against
the real dictionaries and ui-primitives, with faithful in-memory
harness faces (cordis inject/effect, locale chain, slots, sessions
provide) instead of plugin mocks.

- 43 fine-grained, stateless, single-module spec files (606 tests)
  forked across workers: ~2.5s wall, no build step
- v8 thresholds at 100/100/100/100 per file over src/**
- host specs drive the real projection definitions (init/apply/view)
  plus one full cordis + SessionStore + SessionProjectionRegistry
  integration; the framework's snapshotJsonValue pins the
  projection-cache precondition on every intermediate fold state
- oxlint's src ruleset now covers tests/** as well
- typecheck covers the tests via tsconfig.tests.json
2026-08-26 00:58:39 +08:00
Bowen Liang ecd9af93a8 feat(client): open the system prompt row by default when its category is clicked 2026-08-25 23:08:31 +08:00
Bowen Liang 7ee5e8c8df feat(client): unify step brief rows with the context browser's tag+preview anatomy
Brief lines now decompose every node into a fact tag plus preview text,
mirroring the browser element row a click reveals: tool inputs lead with
the tool NAME chip, replies chip their call breadcrumb, injections tag
the form, user messages tag image attachments. Inputs and the reply share
one nodeChip renderer — compact clickable chips for inputs, a single
row-grown chip for the reply.
2026-08-25 21:05:10 +08:00
Bowen Liang a20f9a028a feat(client): show line count on all raw/markdown sections in context browser 2026-08-25 20:27:01 +08:00
bowenliang123 b3c5440637 feat(client): rename cards to Current Context / Context Trend (当前上下文 / 上下文趋势)
'History' in dsh names the chat message log itself, so the trend card's
old names (Context History / 历史趋势) read as a message list; the new
names align the two locales and match what the card shows. The overview
card follows the same pairing (当前构成 -> 当前上下文, already
'Current Context' in English).
2026-08-25 15:04:23 +08:00
bowenliang123 a7e3c0aa79 feat(trend): step brief under the history chart — turn opener, step inputs, and reply at a glance
Each history bar now carries its semantic identity below the detail head,
derived client-side from the served surface nodes (a request's seq IS its
response's node seq):

- User row: the message that opened the turn, recalled on any of its steps
- In row: what newly entered the context (tool-result chips, err dots)
- Response row: the reply text, the call breadcrumb for text-less replies,
  and the call suffix recovered via the conversation join for text+calls
  replies (the fold keeps calls only on text-less nodes)

Plus: one-line reply previews in the chart tooltip, and a click-to-reveal
bridge that opens the row's node in the Context browser (one-shot nodeFocus
prop, same hand-back pattern as toolFocus; reply nodes resolve on the NEXT
step's surface, per the assemble seq window).

No host/wire changes; steps beyond node/archive retention degrade silently.
Shared call-argument summaries extracted to callSummary.ts (browser reuses).
2026-08-25 14:39:54 +08:00
Bowen Liang 4a072f0c55 refactor(client): trim post-audit redundancies, no behavior change
- contextView: drop the tick-based locale re-render subscription; the
  harness slot outlet subscribes the LocaleFace revision and re-renders
  every entry on a locale switch (verified back to dsh 0.1.0-rc.7), and
  the bound t reads the active locale at call time
- browser: delete the never-read catColor map; countOf now takes the
  prebuilt byCat/prevByCat maps instead of rebuilding byCatOf per call
  (up to 13 map builds per render)
- categories: export CAT_COLORS built once from CATS; nodes reuses it
  instead of rebuilding per render
- trendChart: deltaOf rest-destructures prompt/output out instead of
  spread + two deletes
- statsBoard: hoist the pure cell helper to module scope
- services: drop the now-unconsumed LocaleService.subscribe face member
- tests: shift ContextView hook-slot indices after the tick removal,
  drop the dead stats.recycleSub fixture key and the dead locale
  subscribe stub
2026-08-25 00:13:26 +08:00
Bowen Liang 79950c9822 fix(browser): mark killed/failed job_output status lines as failed
job_output always terminates its read with the tool-jobs status line
[status: killed] / [status: failed, detail], and a background job that
dies settles with isError false — so the read showed a green OK for a
dead job. tailStatusOf now treats the two terminal failure statuses as
failures; completed/running/stopping stay notices.

This goes beyond the official chat UI, which renders job_output as a
generic card and shows OK too.
2026-08-24 23:22:39 +08:00
Bowen Liang 3f37450a0f fix(browser): mark persistent-shell death markers as failed
tool-bash-persistent/tool-pwsh-persistent append shell-death markers at
the end of a result's text with isError still false: [shell killed by
signal: X] and [shell exited: code N] ride LAST, after the [exit code: N]
of the command whose failure killed the shell — so the command marker
alone no longer anchors at the end and was missed.

tailStatusOf now parses the shell marker first and re-checks the
preceding text for the command's exit code, so the pill still surfaces
it. A clean shell exit (code 0 or code-less) loses the session but is
treated as a notice, not a command failure.
2026-08-24 23:20:44 +08:00
Bowen Liang bb5a83d61c fix(browser): mark tool results with trailing exit/signal markers as failed
dsh settles a failing command as a completed call (isError stays false;
the exit status is result data), so err/isError alone never flags a
non-zero exit and the context browser showed a green OK where the chat
row shows red. Mirror the chat's terminalFailed: a result is failed when
its text ends with dsh's [exit code: N] or [killed by signal: X] marker.

The exit-code match is now end-anchored like dsh's parseExitStatus, so
marker text quoted inside the output (e.g. a cat'ed log) is not a
failure, and the FAILED pill always surfaces the parsed exit code
instead of gating it behind err.
2026-08-24 23:02:51 +08:00
Bowen Liang efcdd37cb7 feat(trend): make delta mode signed end to end
Delta mode previously took per-category magnitudes for the bars while
the detail panel below still showed cumulative totals. Now:

- The chart draws diverging stacks: growth piles up from a solid zero
  baseline, shrinkage hangs down from it, with the zero line splitting
  the bar area proportionally so the px-per-token scale is identical on
  both sides. Axis reads +max / 0 / -max.
- The detail panel pairs the active request with the previous displayed
  record (same pairing as the chart): a signed net chip replaces the
  provider usage pills, category rows show signed figures on the
  success/error tokens, and each row's mini bar diverges from a center
  zero hairline, echoing the chart.
2026-08-24 22:06:56 +08:00
bowenliang123 6e19c8e950 refactor(trend): rename the diff display mode to delta
Aligns with the token-meter vocabulary of recent dsh releases (signed
token delta) and with the existing zh label 增量. UI labels, i18n keys,
settings schema, client logic and tests all switch from diff to delta.
The settings schema is now loose on defaultTrendMode so a stale
persisted value degrades to the default instead of throwing, and the
client ignores unrecognized values the same way.
2026-08-24 18:24:52 +08:00