Commit Graph
154 Commits
Author SHA1 Message Date
bowenliang123 e94b3e400f style(client): right-align card sub-hint within title row 2026-08-25 10:00:38 +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
Bowen Liang 9938fb7cef style: wrap long comments to satisfy max-len lint rule 2026-08-24 20:57:52 +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
bowenliang123 55a675ef8b style(detail): tidy the trend detail head row
Restyle the request detail header for a cleaner, more consistent look:
- even 8px gaps and center-aligned items on the single wrapping row
- time pinned right with tabular figures
- provider figures (actual prompt / output / cache) as uniform neutral
  pills with tabular numbers, replacing the plain spans and the two
  same-green accent colors
- marker chip tinted with the warn color, matching the event list chips
2026-08-24 18:12:12 +08:00
bowenliang123 c3c6015a95 feat: step-level cache hit rate on the step detail line
Drop the heuristic estimate from the step header; show the provider-reported
cache-hit share of the billed prompt (cacheRead / prompt, truncated to two
decimals) instead. The host folds each request's cacheReadTokens into its
record (stateVersion 9); the stats board reuses the same formula via a shared
format helper.
2026-08-24 17:57:50 +08:00
bowenliang123 0eea57067a chore(client): strip comment noise in the client half
Apply the full-repo comment audit to client logic and components: drop
narration-style JSDoc and duplicated cross-file comments, keep only
non-obvious contracts (module-table require, projection keys, hooks
rules, no-white-screen guarantees). Fix stale claims: locale fallback
chain (en, not zh), unfounded 'harness block vocabulary' mirror, kind-chip
tint mapping, stats tooltip z-index, onToolFocus parenthetical.
2026-08-24 17:16:31 +08:00
bowenliang123 aaf1aa50a3 chore(host): strip comment noise in host and shared modules
Apply the full-repo comment audit to the host half and shared wire types:
delete restatements and decorative headers, compress rationale comments to
one line, and fix stale references (agent-loop buildRequest, live-surface
image cell, five->four priced categories). Kept only contract notes,
invariants (plain-JSON precondition, shadow-seqs protocol) and verified
dsh-mirror pins.
2026-08-24 17:16:27 +08:00
bowenliang123 d7486585fe style(browser): wrap markdown and long tokens in detail cards
Markdown cards render soft line breaks (a single newline now breaks the
line via white-space: pre-line — two newlines still separate paragraphs)
and long unbroken tokens wrap in place: the section card gets min-width: 0
so it shrinks below its content, the body adds overflow-wrap: anywhere
with horizontal overflow hidden, and code blocks keep pre-wrap with
overflow-wrap so long code lines wrap instead of scrolling sideways.
2026-08-24 15:38:40 +08:00
bowenliang123 c23ff27ac4 feat(browser): red run-state dot on failed tool-result rows
A failed tool result now leads its collapsed row with the chat's red
run-state dot (6px core, 10px halo) right after the chevron, so the
failure scans without expanding. The failure state comes from the fold's
`err` stamp OR the conversation snapshot's `isError` — the mismatch
matters: dsh only stamps the event envelope's `error` info
conditionally, while the snapshot block always carries `isError`. The
redundant ⚠ suffix on the tool-name chip is dropped (the dot marks
failures), and per-row conversation lookups are hoisted into one `conv`
for the merged condition.
2026-08-24 15:37:58 +08:00
bowenliang123 66c964d0ee feat(browser): tool-result detail cards — run-state pill, line count and raw/markdown switch
The call half of an expanded tool result drops its argument-count badge
(the rows below already show the arguments); the head's right edge now
carries a run-state pill — green OK by default, red Failed with the
payload's terminal `exit N` code when the fold's `err` stamp or the
snapshot's `isError` marks a failure (exit recovered from the dsh
`[exit code: N]` marker). The payload card heads its line count before
the raw/markdown switch (switch stays at the far right), and its text now
renders through the same switchable prose anatomy as every other detail
section instead of the fixed dim raw view.
2026-08-24 15:36:06 +08:00
Bowen Liang 25fe067c2e fix: keep turn strip labels centered in their block's visible slice while scrolling 2026-08-24 04:01:47 +08:00
Bowen Liang fd5b6224d7 feat: official-chrome plugin settings card, plus default trend display preference
Rework the Settings \u2192 Plugins card onto the framework's own patterns:
collapsed by default like the official PluginCard (header button discloses
the controls in place, chevron rotates), and each preference row is the
official settings-row pattern \u2014 a selector pill opening the shared Menu
dropdown from dsh-client-ui-primitives. Picking an option writes
immediately through the settings scope (display preferences, like the
official Language row).

Add a second preference, defaultTrendMode (total/diff): the Context tab's
trend chart opens cumulative or per-request diff per the stored choice;
in-chart toggling stays mount-local and never writes back. The settings
store's choose() generalizes to set(field, value), with one prefsOf()
validator shared by scope sync and the local echo.
2026-08-24 03:50:30 +08:00
Bowen Liang 0219d8045f feat: per-user settings namespace with default trend granularity preference
Register the dsh-context settings namespace on the Host (dsh-settings seam,
schemastery schema, optional composition) and claim its Plugin configuration
card in the browser (settings.plugin.item, keyed by namespace). The card
edits the default trend granularity the Context tab opens with; in-chart
toggling stays mount-local and never writes back.

Degrades cleanly: no settings provider -> no registration, no card, step
default; remote browser (memory mode) -> unavailable scope, no card.
2026-08-24 03:36:57 +08:00
Bowen Liang 185c7ffced feat(host): show net freed tokens on compaction and prune rows
The row carried the producer's gross shadow price, while the trend chart
visualizes the net drop. The shadow-price protocol guarantees the
replacement folds in synchronously after the metering event, so rewrite
the event's tokens to removed-minus-replacement at that moment; a
metering event left without a replacement keeps the gross price.
2026-08-24 03:08:59 +08:00
Bowen Liang d0560b6b6b feat(host,client): record plan-mode toggles and resume model switches
Plan mode includes a guidance section in every model request while
active, so its durable plan/mode event is a real context-composition
change and earns a neutral mode event row (own kind chip + filter).
A request header logged with reason 'resume' carrying a different model
is likewise a real switch made between sessions — record it alongside
the in-session 'change' ones.
2026-08-24 03:06:07 +08:00
Bowen Liang e4dab4d17e feat(host,client): show the notice summary on inject event rows
A notice-form source already carries the producer's bounded one-line
account; record it as the event's detail and render the row as
title · producer · account, matching the dsh transcript row shape.
2026-08-24 03:02:07 +08:00
Bowen Liang dc29e0bc67 fix(host): classify any non-'user' source kind as injected context
The dsh transcript treats a user/message as injected context whenever its
durable source kind is not 'user'; the fold's narrower check (plugin /
skill-invocation / has form) misclassified formless producers — goal
continuation rounds, team relays — as user messages, skewing the token
attribution and hiding their inject events. Keep the form check as a
fallback for foreign sources without a readable kind.
2026-08-24 02:58:46 +08:00
Bowen Liang e30e33fc57 feat(host): label injection events with their producer source
Mirror the dsh transcript's context provenance on inject events:
agent-instructions names its reconciled file paths, a plugin source its
plugin id, and any other producer (skill-catalog, ...) its durable kind,
so the events panel shows '目录更新 · skill-catalog' style rows like the
harness GUI does.
2026-08-24 02:54:15 +08:00
Bowen Liang e207083a56 feat(client): frame attachment cards with a hover affordance
Each image attachment card now carries its own border (border-l1, 8px
radius); hover strengthens the frame to border-l2 and washes the card with
the interactive row hover token — the same affordance as the browser's
clickable category and message rows.
2026-08-24 02:36:49 +08:00
Bowen Liang e6dcfeea1c feat(client): preview image attachments in a chat-style lightbox
The attachment card's thumbnail click used to open the full image in a new
browser tab. Now the whole card (tile + metadata column) is one click
target, and clicking it opens an in-page preview modal mirroring the chat
history's ImageLightbox recipe: body portal via the module-table react-dom,
blurred mask on the same dsw theme variables, contain-fit image, circular
close control (IconCloseOutline16 from the seeded ui-primitives), Escape/
mask close, and focus restored to the opener. The ui-attachment package
itself is not seeded into the browser module table and cross-plugin value
imports are gated off, so the recipe is ported onto the plugin's lc-*
classes with fallbacks; load failures still retry on click.
2026-08-24 02:36:41 +08:00
Bowen Liang 239ce675f0 feat(client): restate the Context Stats hint as current-context contents 2026-08-24 00:18:16 +08:00
Bowen Liang d68422a630 feat: count Context Stats images within the current context only
The image count moves off the persisted whole-session counter onto a
per-node imgs field (absent when zero), and the view sums it over the
live surface — matching the Tool Calls cell's semantics: images of a
message compacted or pruned out of the context stop counting. The
persisted-state shape changes, so stateVersion bumps to 8 and cached
rows are refolded.
2026-08-24 00:02:08 +08:00
Bowen Liang 39554df985 feat: add a Tool Calls cell to the Context Stats board
The cell sits left of Images and counts the tool calls whose result is
live in the current context: one tool/result folds to exactly one tool
surface node, so the host derives the figure at view time from the live
surface (no persisted counter, no state-version bump). Calls still in
flight and results compacted or pruned out of the surface are excluded.
2026-08-24 00:01:47 +08:00
Bowen Liang 69c175e76a feat(client): narrow the Context Stats board cells
Shrink the stat-cell grid minimum (76px -> 62px), gap (8px -> 6px) and
padding (8px -> 6px) so the board's nine cells pack tighter.
2026-08-24 00:01:43 +08:00
Bowen Liang e53c375a15 feat(client): hide the composer while the Context tab is active (#14)
* feat: add PR checks workflow for automated testing and validation

* feat(client): hide the composer while the Context tab is active

The view ring mounts one view at a time, so the tab's .lc-root existing
inside the shell scrollport is exactly 'Context tab active' — a single
:has() rule hides the sticky composer seat and hands its height to the
dashboard. display:none keeps the seat mounted, so drafts survive the
round trip.

Interactive takeovers (approval / question / plan-review panels) replace
the bar inside the same seat; keep the seat visible while one is present
so a pending decision is never hidden behind the focus mode.

Closes #11

* ci: build before test in PR checks

Client tests mount the built bundle (viewBed.ts reads lib/client.js), so
pnpm test without a prior build fails with ENOENT in a clean checkout.
2026-08-23 23:33:15 +08:00
Bowen Liang a26f5437c3 fix(client): hoist hooks above early returns — React #310 on data/event arrival
ContextViewBody returned the loading frame BEFORE its displayRequests/
markers useMemos: when the projection value landed on the same mounted
instance (a pure timing race), the hook count grew between renders and
React threw #310, which the ErrorBoundary masked as a '数据读取失败'
card. EventList returned its empty branch before useRef/useLayoutEffect:
an empty -> non-empty transition in one instance at best corrupts the
hook list (React logs 'Expected static flag was missing') and at worst
throws the same #310 on builds that don't recover.

Both early returns now sit below every hook; data-less renders fall back
to empty collections so the memos stay unconditional.

Regression tests mount the built client bundle on real React in jsdom and
drive both transitions (loading -> loaded, events empty -> non-empty),
failing on a boundary catch or any hook-order console error.

Closes #12
2026-08-23 23:03:47 +08:00
sxgitmiao be751da48d feat(client): add Total/Diff display mode to the Context History chart
Adds a display-mode pair (全量/增量, Total/Diff) next to the Step/Turn granularity group in the Context History card. Diff mode plots each request's per-category |change| vs the previous request, with the hover tip reporting the signed net change (Δ +1.2k / Δ −535.5k); Total keeps the cumulative composition and stays the default.

Original PR by @xieshuaix (delta toggle); reworked before merge: the on/off delta switch became a two-way mode control (mode: 'total' | 'diff' end to end), the two toggle groups were wrapped so the title's auto-margin push happens once, trend.hint was shortened/localized, and the fiber-slot/button-count tests were updated.
2026-08-23 22:49:09 +08:00
Bowen Liang 095e2f525e fix(host): tag skill results even when the tool/call event is gone
- drop the redundant node.cat === 'tool' guard (categoryOf always
  returns 'tool' in the tool/result case)
- fall back to the rendered <skill_content> wrapper when node.tool is
  unresolvable (trimmed window, replay), so a skill load never silently
  degrades to an ordinary untagged tool row; the wrapper only appears in
  genuine skill results
- regression test for the fallback path, and that a plain tool result
  without the wrapper stays untagged
2026-08-23 22:25:30 +08:00
Bowen Liang 02807de68c Update src/client/i18n.ts 2026-08-23 21:34:56 +08:00
tangjunyan.5703 a2641b7d29 feat(host): surface skill-tool loads as tagged tool results + Skill inject event
A skill load via the `skill` tool returns the loaded skill's instructions
as a tool result. Keep it a tool result (that is what it is), but make it
findable: tag the node with the skill name (so the Context browser labels the
row `Skill · <name>`) and record an inject event so a `Skill 注入(name)`
entry shows in the Context Events card.

- fold: detect tool/result with node.tool === 'skill', parse the skill name
  from the rendered <skill_content name="…"> wrapper, set node.skill, emit a
  sub:'skill' inject event. Category stays 'tool'.
- browser: tool row shows the Skill · <name> tag when node.skill is set.
- i18n: add node.skillTag (zh/en).
- test: regression for the skill-tool result path.
2026-08-23 18:45:35 +08:00
bowenliang123 c27904b5f5 feat(client): chip the collapsed User Messages rows that carry image uploads
The row joins the conversation snapshot and counts image blocks via the
same imageRefOf the expanded body uses; the chip (localized 'attach.image',
×N when several) shows only while collapsed — expanded, the image grid
speaks for itself.
2026-08-23 05:13:56 +08:00
bowenliang123 93cf924445 fix(host): drop unnecessary ?? 0 guards on TimelineState.images (stateVersion 7 refold guarantees it) 2026-08-23 05:11:27 +08:00
bowenliang123 271f4e3395 refactor(client): unify Context browser detail rendering into one section-card system
Every expanded element body is now a uniform stack of Section cards
(labeled head + body): prose carries the Raw/MD switch in the head, tool
calls/params/images/raw JSON share the same frame, and one blocks→sections
pipeline serves user/assistant/tool-result/injection bodies. Replaces the
floating-switch, bare-pre, and per-kind card idioms; drops the dead
lc-br-blk/lc-br-pre/lc-br-md/lc-br-rich-inline styles and the unused
RichCard/inHead/dim parameters. Element-row tool tags lose their inverted
chip and share the subtle injection-form tag style. Test bed: function
components now receive props.children like real React.
2026-08-23 04:56:17 +08:00
bowenliang123 af8f695854 feat(client): Context tab browser stretches to the left column's height
The /context modal keeps the browser content-sized.
2026-08-23 04:32:56 +08:00
bowenliang123 93dfe3941d feat(client): shorter stats labels (Cache Hit / Cost) 2026-08-23 04:32:55 +08:00
bowenliang123 f15b9ecddc style(client): narrower stats cells (76px min, 8px side padding) 2026-08-23 04:18:32 +08:00
bowenliang123 78245e1b75 feat: whole-session image count in Context Stats
Fold counts image blocks (user uploads + nested tool-result images)
cumulatively over the complete session log, rides the wire snapshot as
an optional additive field, and the stats board shows it as a new cell
between prunes and cache hit. stateVersion 7 refolds cached rows.
2026-08-23 04:18:26 +08:00
bowenliang123 3c48acbed0 fix(host): weekends bill at off-peak rates all day
Follow DeepSeek's peak/off-peak rule adjustment: Saturdays and Sundays
(Beijing Time) no longer distinguish peak windows, so the whole weekend
prices at the half-price off-peak rate. Peak windows are now expressed in
Beijing Time (weekdays 09:00-12:00, 14:00-18:00), equivalent to the old
UTC windows on weekdays. List prices themselves are unchanged.
2026-08-23 03:55:18 +08:00
bowenliang123 c208f55d07 feat(client): labeled Raw/Sent/Token rows on image cards, shown only when known
Each fact gets its own labeled row: Raw (pre-normalization dims), Sent
(normalized dims + stored size, dynamic B/kB/MB units), Token (estimate).
Facts that dsh never recorded (e.g. no originalDimensions, no byte size)
simply leave no row behind.
2026-08-22 18:08:41 +08:00
bowenliang123 eb5c8ebf2d fix(client): wrap long image attachment filenames instead of truncating 2026-08-22 18:08:35 +08:00
bowenliang123 c003916360 feat(client): two equal-width image attachment columns with token badges
The attachment grid becomes a two-column equal-width grid
(repeat(2, minmax(0, 1fr))) and every image card gains a token-cost
line (≈N tokens, with an explanatory tooltip) estimated through the
official DeepSeek image-size conversion on the stored dimensions.
2026-08-22 16:31:13 +08:00
bowenliang123 d3cdc263d6 feat(client): read the official contextBreakdown projection for the composition card
The composition legend and tooltips now show the exact system/tools/
messages counts of the chat composer ring's click-open panel (the
token-meter's contextBreakdown projection) instead of the fold's own
sums re-anchored to the provider total: parts carry the heuristic count
(raw) for display beside the provider-anchored bar width (value), and
the message bucket subdivides into the four surface categories by the
fold's per-category ratios, always summing exactly to the official
message figure. Absent the projection (older harness), the fold's own
sums serve unchanged.
2026-08-22 16:31:05 +08:00
bowenliang123 d9c09782fc feat(host): price image blocks by DeepSeek's official image token calculator
The token-meter heuristic prices image blocks through its generic JSON
branch (~40 tokens for the durable ref), while DeepSeek's vision model
actually bills 117-384 tokens per image by pixel dimensions. Port the
official docs image-token calculator (patch grid 14px, downsample x3,
384-token cap, minPixels 384^2) into src/shared/imageTokens.ts and price
image blocks with it in the fold; unknown dimensions keep the JSON
fallback. stateVersion bumps to 6 so cached rows refold.

The differential fold check replays a parallel image-delta surface so
image-bearing logs still match the official contextBreakdown exactly,
and adapts to the 0.1.1 wire.view contract.
2026-08-22 16:30:55 +08:00
bowenliang123 ad1e6872ba feat(client): collapsed browser rows lead with an inverted tool breadcrumb + call summary
Tool-result and call-bearing assistant rows now open with an inverted
mono chip naming the tool(s) (label/surface tokens swapped — black on
white in light mode, white on black in dark), multiple calls joining as
a breadcrumb (bash › write). The preview then carries the call's own
summary line: a bash-style `description`, else the target path of
path-taking tools (edit/read/write via file_path/path/filePath), falling
back to the reply text or the generic label. The redundant 'calls: …'
preview is gone — the chip says it once.
2026-08-22 04:31:34 +08:00
bowenliang123 654eca8cdb feat(client): tool calls in assistant messages and tool results share one arg card
Both render the call as a card whose head names the tool (→ / ←) with
the argument count, the body lists arguments as name/value rows — the
type column is gone, the name sits in a narrow wrapping left column,
the value wraps in a mono right column (height-capped). The tool name
steps up to 13px mono. The tool result's raw lc-br-call block is
replaced by the same card.
2026-08-22 04:16:56 +08:00