Commit Graph
934 Commits
Author SHA1 Message Date
bowenliang123 f6ab4a29ff chore: release v0.69.1 2026-10-12 08:40:43 +08:00
bowenliang123 0f01900a91 fix(client): track a placement mount by its own state, not its disposer
watchPlacement inferred "this side is mounted" from the mount handle
being a function, so a face that seats the seat and hands back no disposer
(a stripped or foreign `slots`) read as never mounted — and every later
preference publish registered a second copy of the same seats. Track the
mounted state directly; the handle is unwound when there is one.
2026-10-12 08:39:50 +08:00
bowenliang123 9873eb5836 fix(client): mount the client bundle once per page (issue #137)
The same package can mount twice into one page's slot ledger — a static
profile bundle plus a later activation of the same package. The
client-plugin runner force-assigns a fresh shadowing priority to every
registration, so the registry's same-id duplicate guard never fires across
the two mounts, and the conversation header — which renders the raw ledger
and matches the active view by entry id — paints both copies of every
list-slot seat.

apply() now reads the raw ledger first (SlotRegistry.entries) and mounts
nothing when this bundle's chat-to-Context jump entry is already seated.
The read fails open: an absent, shape-shifted, or hostile face reports
unseated and the mount proceeds, so an older line never blanks a working
mount. The entries seam is pinned per baseline.

The harness-side defects (the runner forcing a rank on additive list
slots, and the header rendering the raw ledger) are left to
deepseek-ai/deepseek-harness.
2026-10-12 08:39:17 +08:00
bowenliang123 2468390522 chore: release v0.69.0 2026-10-12 02:43:22 +08:00
bowenliang123 2ce5d9a7db feat(client): narrow the comms timeline by the picked agent
- A pick anywhere (graph node, member card) now steers the comms timeline
  beside the task board: rows narrow to the traffic the picked agent sent or
  received, the dropdown steps aside for the pick chip (label, never a raw
  id), and an agent without traffic says so
- The pick governs while it stands: the dropdown's own choice yields on
  landing and never resurrects stale once the pick clears; without a pick the
  dropdown keeps its free per-participant browsing
- The pick key resolves once in the view (roster name -> session id, a
  subagent's own id) — the one key both boards narrow by; zh/en gains the
  no-comms note
2026-10-12 02:21:17 +08:00
bowenliang123 00a612d285 style(client): size the member icons down to the task cards' own
The members board's 26px avatar boxes dwarfed the 20px hue icons the task
board settled on; one base class now carries the shared size, so the members'
cards, the task rows, the band heads, and the opened agent boxes all read at
the same scale — and the task board's size patches fold away.
2026-10-12 02:10:22 +08:00
bowenliang123 ac5b36b784 feat(client): unfold task cards into boxed brief, agent, and reply sections
- A task card opens on a pick (or Enter/Space on the row): the task brief, the
  owning agent, and the final report-back each box up in the context tab's own
  section chrome, with the raw/Markdown switch on every box head — Markdown the
  default, the report-back stamped with its time
- Closed rows keep the compact read, the description clamped to two lines and
  rendered through the shared Markdown renderer (memoized per text, so board
  hover re-renders never re-parse)
- The row pick no longer pins: pinning stays with the owner chips, the band
  heads, and the agent box inside an opened card; Enter on a nested control
  keeps activating that control instead of folding the card
- zh/en gains the box labels (task brief / agent / final reply)
2026-10-12 02:05:11 +08:00
bowenliang123 a4c737b354 feat(client): rebuild the task board as the family's work board
- Plain subagents join the board: each delegation reads as a task of its own
  (the prompt as the subject, the session state as the status) with the run's
  live figures on the row — tokens · duration · steps and the pressure-tinted
  occupancy; the row itself is the agent (family-hue icon, session id on hover)
- One vertical, owner-grouped layout replaces the status/owner switch: roster
  members first (the lead in brand hue, teammates by stable roster hue), stray
  owners the roster never served, then headless delegation rows in spawn order,
  the unowned pool last; status lives on the rows
- The toolbar gains search (subject/owner/id) and status filter chips with
  counts; the completion bar now counts every row on the board
- A pick anywhere narrows the board to that agent — graph nodes and member
  cards included, subagents by their own session id (the pick chip names the
  label, never a raw id); the members board reports picks raw and the view owns
  the toggle, so a release lets go of the paired narrowing wherever it came from
- Renamed to Tasks (team tasks and subagent delegations); zh/en updated, the
  dead view-switch keys removed
2026-10-12 01:29:48 +08:00
bowenliang123 8b05fab68c feat(client): move the network card's composition legend above the graph
The legend joins the header stack — after the chips row, before the stage —
instead of dangling under the canvas, so the category colors read against the
cards before the forest scrolls. The bottom margin keeps the chips -> legend ->
stage rhythm at 10px.
2026-10-12 00:47:53 +08:00
bowenliang123 d02f19cc1c fix(client): let the Fleet network card size freely beside the boards at 1:1
- The cockpit row splits evenly: the graph column drops its flex-[2] weight
- The network card's flex basis returns to its own content (flex: 1 1 auto):
  a 0 basis let engines size the row line under a deep forest's canvas,
  clipping levels behind the card; content now sets the floor and the card
  still stretches to the boards column's height
2026-10-12 00:38:57 +08:00
bowenliang123 2835230485 feat(client): tighten the network graph cards and drop the corner jump
- Cards keep a 178px minimum width (slot pitch floor 150 -> 190) with tighter
  padding (9x10 -> 7x9) and a 100px height, so dense families read wider and
  calmer
- The corner arrow button is gone: in the Fleet seat a card click selects and
  the jump stays with the boards' own open buttons, while the sidebar seat
  keeps opening on click; the select-mode subtitle no longer names an arrow
2026-10-12 00:38:17 +08:00
bowenliang123 d648edec1e feat(client): give the members board's other agents the member-card form
- The other-agents section renders the family's plain subagents, ancestors, and
  siblings as the same cards the team roster uses — label as the identity (two
  clamped lines), node-state dot, status + model line, landed latest reply, and
  an open button; the card hue follows the graph's family hue
- Member cards gain the run's activity meta (tokens · duration · steps, with
  the pressure-tinted occupancy figure), team cards included
- A compact card layout (8x10 padding, tighter gaps) and a 200px grid floor
  keep the two-column read the common case; the fleet-row list is gone
2026-10-12 00:37:44 +08:00
bowenliang123 9d8b7b7bf0 feat(client): stretch the Fleet network card to the boards column's height
Side by side in the cockpit row, the Agent network card now fills its column
(the browser view's lc-col-browser idiom), so its bottom edge meets the
members-plus-tasks column instead of dangling above it. Wrapped onto its own
line on a narrow pane the stretch is a no-op — the card keeps its natural
height.
2026-10-11 23:44:21 +08:00
bowenliang123 928abdac97 feat(client): rebuild the Fleet tab around one stable three-shape layout
- Overview strip: family stats (agents/running/done/tokens/est. cost) with an
  adaptive progress bar — shared tasks when a team serves any, else subagent
  completion, and no bar for a solo session
- Agent network: taller link channel (level pitch 144→168), node cap 25→40,
  and a card click now selects (ring + the task board's owner filter) instead
  of jumping sessions; the jump moves to a corner button on each card
- The graph's cursor-following inspector leaves the Fleet tab (the boards
  carry that detail); the sidebar mount keeps it
- Members board absorbs the roster: team cards section plus a compact
  other-agents section, one search/status/sort toolbar steering both; the
  standalone FleetList card is gone
- Task board: completion bar, status/owner grouping switch, owner pinning and
  hover lineage from task rows, transitive prerequisite-chain highlighting,
  and a guidance empty state without a team
- Both boards stay mounted with guidance notes in team-less and solo
  sessions, so the page never reshapes between session kinds
- Hoist displayCurrencyOf into cost.ts (was duplicated per mount)
2026-10-11 23:35:29 +08:00
bowenliang123 2c8095c34d chore: release v0.68.1 2026-10-11 21:08:45 +08:00
GrivnandClaude Opus 5.5 edf6e24b44 fix(client): let the window be dragged by the insight page head (#136)
On DSH Desktop for macOS the shell moves the window by elements marked
`data-window-drag`, and the shipped pages mark their head, which their
top padding stretches over the hiddenInset title bar band. The insight
page head already takes that band (`--dsh-frame-top-clearance`) but was
not marked, so the window could not be dragged from the top of the page.

Mark the head. The harness keeps buttons, links, fields and anything
outside its root out of the drag area, so the settings chip, the
balance link, the date range and the range group stay clickable, and the
date picker's portaled dialog is unaffected.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:02:56 +08:00
bowenliang123 3bacd791ed feat(client): name the Fleet task board in the harness's own terms
The card titled itself 任务看板 / Task Board while its empty state already
quoted the harness's agent-team UI verbatim (暂无共享任务,可以通过对话创建).
The title now carries the same term the harness's client-ui-agent-team
serves (共享任务 / Shared tasks), the two member-card tips follow
(在共享任务中…), and the subtitle describes the card as the agent team's
shared task list (智能体团队共享任务清单).
2026-10-11 19:36:37 +08:00
bowenliang123 293e954fb1 feat(client): lay the Fleet team and task boards out as two columns
The Fleet tab's Team Members and Task Board cards stacked vertically no
matter how wide the pane ran. They now share a `.lc-cols` row — the
plugin's own two-column idiom (flex-wrap + the shared
`flex-1 min-w-[min(360px,100%)]` floor the head-band cards ride) — so
they sit side by side as two equal-height columns and fold back to
full-width stacking below two 360px floors plus the row gap.

Gating semantics are unchanged: the task board still serves whenever the
team projection exists, even without a family forest (the duo row then
carries it alone at full width), and the members board still needs the
forest for its roster join. Tests pin both the together case (same
`.lc-cols` parent) and the forest-null case.
2026-10-11 18:07:24 +08:00
bowenliang123 f0b0e12e58 fix(client): guard the settings binding against hostile values and dead scopes
Three review passes (standards, contract, over-engineering) over the whole
settings surface left four follow-ups worth taking:

- sync() wraps its snapshot parsing in the compatibility.md parser gate's
  bounded catch: a served value that throws on property access keeps the
  current state instead of throwing into the mirror's fan-out, with the
  mandated hostile fixture beside the happy path.
- the attach disposer now clears the scope, so a set() after disposal never
  writes into a dead form; a superseded attach keeps the newer scope bound.
- dead surface trimmed: six type re-exports nothing imported through the
  module, a single-caller query helper, and an orphaned export on the
  settings-card props interface.
2026-10-11 17:03:42 +08:00
bowenliang123 fd782119e8 feat(client): organize the preference card into three titled groups
The card's eleven rows had grown into one flat list whose only structure
was "Topic · Field" prefixes baked into the labels. The rows now sit under
three titled groups — Entry & Visibility, View Defaults, System Prompt —
so the card reads as where the plugin appears, what its surfaces show by
default, and what it injects, in that order.

The harness projects no group chrome into the `plugins.bundle.config`
slot, so the card draws its own: group heads mirror the harness's
Plugins-page `.sectionTitle` (14px/500 primary, one weight step above the
pinned-400 row labels), separated by its row-separator token. Each group
carries `role="group"` + `aria-label`; the prompt hint moves inside the
prompt group it describes.

Labels drop the prefixes the group titles now carry ("Trend Chart ·
Default granularity" → "Trend Chart · Granularity", "System Prompt ·
Prefix" → "Prefix"), zh/en in lockstep. The spec test asserts the three
groups, their order, and the hint's seat; row order is unchanged.
2026-10-11 16:41:45 +08:00
bowenliang123 fd66d80543 refactor(client): split the i18n dictionaries under the 1000-line cap
i18n.ts had grown past the repo's 1000-line limit with the two dictionaries
inline (the prefix/suffix keys pushed it further). zh — with DictKey, the
key-set source of truth — and en, pinned to it at compile time, move to their
own files; i18n.ts keeps the shared Translate type and re-exports.
2026-10-11 13:55:42 +08:00
bowenliang123 d75b539419 feat: system prompt prefix/suffix settings joined into every session
Two new per-user preferences, `promptPrefix`/`promptSuffix`, hold free text
joined at the very start / end of the harness's assembled system prompt for
all agents (subagents included) — a per-deployment persona or behavioral rule
riding the harness's own prompt registry instead of session-log surgery:

- src/host/promptInjection.ts registers two global prompt sections through a
  deferred `ctx.inject(['systemPrompt'], …)` (a composition without the
  registry degrades to a no-op; the registration unwinds with this plugin's
  fiber). Orders anchor on the harness's own section-order registry, so a
  central renumbering keeps the prefix first and the suffix last. Text is
  registered `interpolate: false`, so `{{...}}` in user text stays literal.
- The section text providers read the volatile config references the loader
  rewrites in place on preference edits, so an edit applies from the next
  turn without remounting the entry; new sessions carry the text from their
  first turn through the harness's own system-prompt projection.
- The settings card grows two free-text rows (local draft, committed on blur
  and on unmount; a host push never clobbers in-progress typing), zh/en
  strings, and the served-value sanitize/degrade path for the string fields.

Verified against a running harness: the committed text persists in the
profile layer, and a new session's system prompt starts with the prefix and
ends with the suffix. docs/compatibility.md documents the seams, and the
compat matrix probes them on both supported lines.
2026-10-11 13:55:14 +08:00
bowenliang123 425fd2e7b8 chore: release v0.68.0 2026-10-11 10:37:14 +08:00
bowenliang123 f0ee3195ac feat: rebuild the Fleet tab as the agent fleet cockpit
Split the Agent network card into graph + cursor-following inspector
(delegation prompt, latest reply, descriptor, owned tasks, comms), add
the members board and the status-grouped task board off the agentTeam
projection with inline dependency chains, a filterable/sortable roster,
and an on-demand comms timeline over relay/mailbox/settled records.

Data rides a new /api/dsh-context/fleet route (one observeSession read
per session through the shared cold-read gate); hover, pin, owner
filter, and task focus are lifted to the view so every panel steers the
same inspector.
2026-10-11 07:52:58 +08:00
bowenliang123 9b0618a7a6 feat(client): gate the Fleet tab behind a preference
The preferences card gains a "Fleet tab" row (Show/Hide, default Show): the tab is
registered or unwound live, and the placement preference still has the final say —
'sidebar' serves no conversation tabs at all. The ninth preference field rides the
same Config schema and settings transport as the rest, including the fail-open read
(a value the plugin cannot read degrades to Show) and the rollback when a write is
refused.

The three on/off rows (entry visibility, the Fleet tab, the duration curve) now
share one row shape and one set of Show/Hide labels instead of a copy each, and the
mount/unmount/subscribe watcher both gates ride is one helper (toggleGate.ts) —
watchInsightPage and watchFleetTab were the same skeleton twice, down to the
registration-handle guard.

One preference now costs several coordinated edits in the settings store, so the
snapshot comparison is a shallow compare over the state's own keys and the refused-
write rollback is one table lookup, not a chain that has to grow with every field.

The stats board's family cell hands its jump to the caller: statsContext no longer
reaches into the tab bar for the Agent Network view. contextView owns the targets —
the Fleet tab when the preferences serve one, else the right Sidebar panel that
keeps the card inline — and drops the cell's button role when it serves neither, so
a hidden tab can never leave a dead button behind.
2026-10-10 20:21:57 +08:00
bowenliang123 ace15cac11 feat(client): move the agent network onto its own Fleet tab
The Agent network card leaves the Context tab's foot for a page of its own —
the Fleet / 编队 tab, right of Context (order 30). The card is unchanged,
but both mounts now read one shared `useContextSession` data path and fold
the current agent's figures with one `agentSelfOf`, so the panel and the tab
cannot disagree; the Fleet page keeps the same cold-start retry note and the
same baseline gate. The tab label key splits into `tab.context` / `tab.fleet`.

The right Sidebar's Context panel keeps the card inline — its column has no
room for a tab of its own — so the center Context tab renders it only for the
sidebar host, and nothing else leaves that tab.

The stats board's Agent Network cell used to scroll to the card further down
the same page; it now activates the Fleet tab (activateContextTab is
generalized to activateViewTab). Its miss arm is unreachable for our own
tabs: the board and the Fleet tab ride the same placement gate.

The bundle smoke's fake registry now records `register()` calls instead of an
inject callback's return value, matching the real registry — one inject may
declare several registrations, which the second tab exposed.
2026-10-10 20:10:55 +08:00
bowenliang123 3aed76b493 feat(client): reveal an injection's item from its events row
An injection row's label is now a link into the Context Browser: hovering
underlines it, and a click selects the step that first carried the item,
opens its category, expands its row, and scrolls the browser card into
view. An injection event's seq IS the seq of the surface node it added
(fold.ts), so the events row and the browser row are the same item; the
node's own `gone` stamp bounds the reveal to the steps before its removal.

The step comes from the existing locateStepOf(), which now sorts the wire
the way the browser's own entry stamps do — a re-ordered log must not move
the reveal past the step that actually carried the item.
2026-10-10 19:28:00 +08:00
bowenliang123 f1505420a5 feat(client): stamp turn/step positions as the compact T1 S9
The events card's position column spelled the same fact out in full
("第 1 轮 · 第 9 步"), crowding the row's meta tail on narrow panes. It now
uses the notation the browser's own rows already carry (browser.stepAt):
T1 S9, T2 S3→4, T50 S8 → T51 S1.

eventAt() is shared, so the request detail's marker chip and the trend
chart's marker tooltip read the compact form too — one notation for one datum.
2026-10-10 19:27:32 +08:00
bowenliang123 df136da57d chore: release v0.67.0 2026-10-10 18:10:23 +08:00
bowenliang123 667e1a9c71 fix(client): hold every data-dependent lane so the page stops reflowing
Opening the Context tab, and every step landing after it, moved the cards
below: the page grew 343px on a cold open and the trend card jitters by a few
pixels on each step. Seven independent sources, all the same shape — a box
whose height came from its content instead of from its role:

- The brief lane under the chart held a 76px floor but its rows were 24.5px
  bare and 26.5px carrying a chip, so the lane measured 79.5px or 81.5px
  depending on the step. Every row is now a constant 27px.
- The detail header let its metric pills share a wrapped line with the title,
  so its height followed which pill topped each line as the figures changed.
  The pills now claim a full row of their own.
- The file card's empty state replaced the controls and the 320px list with
  a one-line note, a 265px collapse on the first read of a session. The band
  and the list lane now render in every state, and the lane holds the list's
  height.
- The file card's filter band rewrapped as its tallies gained digits; it now
  reserves its folded height, which depends on the card's width alone.
- The context browser's category rows wrapped as the per-step item counts
  changed; they now stay on one line and the name ellipsizes.
- A title carrying only text wrapped when its scope chip changed length
  (longest in English), resizing the card mid-scrub. Text-only titles keep
  one line; titles carrying toggle groups keep the wrap they need.
- The trend card's pending body rendered as a one-line note and grew by its
  whole height when the first step arrived, shoving the page down. It now
  stands in the panel's OWN boxes — same classes, same grid — so the detail
  fills the card instead of growing it.

Measured across 360px–1440px in both locales: every card's height variance
while scrubbing a 40-step session is 0, and the cold-open fold is 5px.
2026-10-10 17:15:37 +08:00
bowenliang123 861ee77d03 refactor(client): spell out the tools that fit, fold the rest into one chip
The stats card's tools row capped the tally at an arbitrary top three, so a
session that used five tools showed three and quietly dropped two, and the
row wrapped to a second line whenever the count grew a digit — which resized
the node, the stats card, and every card below it mid-scrub.

ToolPills now keeps ONE line, like the Input/Output row above it, and folds
whatever does not fit into a trailing "其他 +N". The fit is measured, not
guessed: an off-flow gauge carries every pill and every chip width at its
natural size, and a layout pass picks the widest set that fits alongside the
chip it would actually carry. A pill's width does not depend on whether its
neighbours are on screen, so the gauge answers for the tail the row is not
showing, and the row renders only what fits — nothing is ever clipped or
overlapped. Measuring the live chip instead would feed the pass's own output
back into its input; measuring only the widest chip would cost a pill to
digits the chip never shows.

With no tally — the split generation's head carries none until the detail
lands — the row holds a lone "其他 +0", so the card reads as a figure
settling into place rather than a blank gap filling in. Because the row never
wraps, the node's height depends on the card's width alone and stops
resizing as a session's tool mix changes.
2026-10-10 17:14:54 +08:00
bowenliang123 209169df86 refactor(i18n): check the en dictionary against zh at compile time
Both dictionaries were annotated Record<string, string>, which is an
index signature and a promise of nothing: a key added to zh alone, or
to en alone, typechecked and shipped. The only gate was a runtime
deepEqual in the spec, so the asymmetry reached a built bundle before
anything failed.

zh is now the key-set source of truth and en is checked against it, the
harness's own idiom (see ui-conversation/src/client/locales.ts in
deepseek-harness, where 51 of 61 locale files do this). A key reaching
only one side is now a compile error, verified in both directions.

The spec keeps what types cannot see — non-empty values and placeholder
agreement — and drops the key-set test, which can no longer fail.

Translate keeps its key as string. Call sites compose keys at runtime
(`'form.' + form`, a weekday template), so a DictKey union would mean a
cast at each one, and the cast would suppress the check this change
buys.
2026-10-10 12:55:43 +08:00
bowenliang123 887615497d feat(client): show the agent cards' work state in the harness's own glyphs
The header popup marks each subagent with a rolling spinner while it
works and a green dot once it is done; the cards had no marker at all,
because their only candidate signal never existed. SessionSummary has
no completion field, so the row read was dead and always false — the
popup takes its answer from subagentTiming.lastTurnCompleted, and so
does a card now. A session that is not a subagent never carries that
projection, so its own folded steps against a stopped session stand in.

The glyph is the harness's own StateDot primitive at one fixed size, so
a card flipping between states never shifts its title.
2026-10-10 12:33:05 +08:00
bowenliang123 cd26173459 style(client): widen the agent card grid
The natural cell pitch was 208px, which left the two-line titles on a
subagent's card hugging the edge. Every card takes the same, larger
pitch — 276px — so the graph stays one grid and no card claims room
its neighbour does not get. A stage narrower than the pitch still
compresses it, so the family never overflows horizontally.
2026-10-10 12:00:41 +08:00
bowenliang123 38fe279213 feat(client): give every agent card a duration and a step count
The card footer carried the run's duration alone, so two things were
missing: a node that never produced timing rendered a footer with no
figure on the left, and the current agent's card showed nothing at all —
the harness only ever writes a subagentTiming projection onto subagent
sessions, so a parent session has no duration to read from the list.

The step count joins the duration, and the current agent's own
whole-step time reaches its card from the tab's timing totals. The
inspector reads the same figure under the same name, and the README
follows.
2026-10-10 11:59:54 +08:00
bowenliang123 c8f2320afc docs: refresh the agent network portrait and notes
The README's Agent Network section describes the card/bar/lineage design
with a regenerated screenshot, and compatibility.md records the bar-based
degrade wording plus the catalog spawn-order sibling sort.
2026-10-10 11:25:31 +08:00
bowenliang123 32cb738e88 feat(client): rework the agent network as live cards
Each node becomes a card: title over a fixed two-line zone (every card's
rows align), the agent's consumed tokens with an estimated price when the
models.dev book has the rate, a composition bar replacing the donut (free
window is the track), and a footer with the run's duration at the left and
the bar's occupancy figure at the right.

Links are S-curves fanned out of the parent card's foot in the family hue
with joint plugs at both ends; hovering or focusing a card lights its whole
lineage (ancestors + subtree) while the rest of the graph dims. Sibling
order follows the parent's subagentCatalog projection (the harness header
popup's own spawn order) when cached, with the activity heuristic as the
degrade. Narrow panes fold the family into single-column bands instead of
crushing cards, and the stage measures its content box, so the phantom
horizontal scrollbar at exact-fit widths is gone.

The inspector gains the hovered node's composition readout; keyboard focus
drives the same inspector/lineage affordance as hover. Card surfaces step
one layer up with the l2 border (the inspector's own nesting recipe) so the
cards read clearly in both themes.
2026-10-10 11:24:47 +08:00
bowenliang123 5541de3a61 docs(compat): refresh the verification stamp
The header still read 2026-10-07 / dsh-context@0.65.0 while the tree had
moved on through two releases and four host changes, which AGENTS.md's
"keep it current" rule does not allow. The matrix claim is now precise too:
it runs for every row in CI, where both workflows fetch the tags and a
missing prerequisite fails the run, and it says so on the dev box where a
checkout-less skip is still the right answer.
2026-10-10 02:19:45 +08:00
bowenliang123 9b31d40add build: drop the client externals the bundle never requires
PLATFORM_MODULES claims to be "the platform-module subset this plugin
requires", but four of its eight entries never appear in the built client
bundle: cordis (every client-side import is `import type`, erased before
resolution), dsh-client-store, dsh-client-ui-slots and react-dom/client.
The emitted require list is unchanged; the purity gate already fails the
build on any @deepseek-ai/ import outside this list, so a genuinely new
external cannot slip in unnoticed.
2026-10-10 02:19:25 +08:00
bowenliang123 0164778f95 chore(docs): move the social-preview generator out of docs
AGENTS.md keeps docs/ to end-user-facing files, and a headless Chromium
generator plus its HTML template are build tooling. They move to
scripts/social-preview/ beside the other maintenance scripts, and the npm
script and its usage comment follow.
2026-10-10 02:19:00 +08:00
bowenliang123 9ccf236123 test(host): collapse the duplicated per-baseline registry run
RegistryDriver's constructor took a Baseline and stored it as a public
field it never read, and registry.spec.ts looped the whole suite over
BASELINES to pass it in — so eleven tests ran twice, identically. Every
supported line shares one documented registry contract, which is what the
driver already mirrors; the driver now takes no baseline and the suite runs
once. The per-tag complement, the ACTUAL dsh registry sources, still runs
in the compat project.
2026-10-10 02:18:37 +08:00
bowenliang123 ba8d7c7624 refactor(client): unwrap the zero-argument component factories
makeDonut() and makeSliceList() took no arguments, closed over nothing,
and returned a component their callers then held in a per-mount local —
seven call sites paying for a factory that bought no identity stability a
plain module-level export does not already give. Both export the component
directly now; sliceList's inline props type becomes a named interface,
since it was written out twice in the signature.

objectsOf and rpcCallOf lose an export keyword each: whole-tree search finds
no consumer outside the module that defines them.
2026-10-10 02:18:13 +08:00
bowenliang123 73f279ea8f refactor(host): share the route registration and the record re-proof
The four Connection fetch routes repeated the same block verbatim — extract
the registrar, guard it, register inside a refuse-quietly catch — and
balance.ts and skills.ts differed only in the route constant and the label.
registerPostRoute now owns the shape, the bind-at-extraction guard and the
catch, so each watcher states only its own handler.

Three of the four files also hand-copied the plain-object re-proof, and
backfill's copy had drifted: it accepted arrays, the other two did not.
They share one host-local helper now, at the stricter semantics. The
client's own asRecord is a different, deliberately permissive boundary with
dozens of callers, so it stays as it is.
2026-10-10 02:17:46 +08:00
bowenliang123 483e76b0c4 fix(host): copy a day's nested activity records before serving them
The contextActivity view copied each day entry shallowly while its own
comment promised the registry, the cache writer and the wire could never
mutate the fold's record. Since stateVersion 2 and 4 the entry carries a
four-level `cost` ledger and a `skills` table, and both stayed aliased to
the persisted state — the exact rule foldWire.ts states for its own served
fields and honours by copying every level of `cost`.

The copy is conditional-spread throughout: one `undefined`-valued property
fails the whole lossless-JSON push. The ledger walk moves beside
addBilledUsage as its mirror, so the timeline and activity halves price and
serve through one implementation.
2026-10-10 02:17:18 +08:00
bowenliang123 3611d50419 fix(host): skip the reserved prototype key in the tool-timing tally
A durable tool/call naming itself `__proto__` reached bumpToolTotals, whose
Object.hasOwn guard passes for it — the key is never an own property. The
plain assignment then set the tally's prototype instead of booking a row:
the count was lost, the 16-name cap never saw the key, and foldWire's
`for...in` copied the inherited `calls`/`ms` as `{}`, which the strict
tool-timing schema rejects. The registry parses every served view with no
error boundary, so that throw stalls the unit's push feed — the "loading"
forever failure compatibility.md designs out, and the same defect the
compat matrix already pins as `poisonedStarves`.

The client boundary has skipped this name since issue #44, with a
regression test beside it; the host fold, where the untrusted name first
lands, did not. The hostile fixture needs no second tool run to reproduce:
a later one re-clones the tally through a spread, which launders the damage.
2026-10-10 02:16:50 +08:00
bowenliang123 f50f33d58b fix(ci): run the compat seam matrix on pull requests
The matrix skipped itself on every PR: pr-checks.yml never fetched the
baseline tags and never set DSH_REPO, so skipReasons() always reported a
missing checkout and describe.skipIf dropped the whole per-baseline block —
the registry apply, the wire keys, the checkpoint JSON gate, cold restore
and every client seam probe. Only the release workflow fetched them, so
the compatibility proof compatibility.md claims for "every pnpm test" was
a no-op until a tag was cut.

Both workflows now fetch the tags and export DSH_REPO, and a skip under CI
fails the run instead of hiding behind green: a missing prerequisite there
is a broken gate, not an absent one. The two test runs collapse into one,
which also brings the lint gate the release workflow had and PRs did not.

The matrix's own vocabulary guard compared a hand-copied constant against
hand-copied baseline lists, so it could never see a drift in the fold — and
had drifted: approval/asked, approval/decided and session/end-seed are
folded but declared by no baseline. It now reads the fold's case labels out
of src/host/fold.ts, so a new fold case cannot go unprobed, and the history
probe pins the `records` envelope historyPage's rowsOf rejects without, not
just the remote's service name.
2026-10-10 02:16:22 +08:00
bowenliang123 9aaa11591a test(client): split the oversized specs into siblings with shared harnesses
browser.spec.ts (2360), trendChart.spec.ts (1629), contextView.spec.ts (1504), and
overview.spec.ts (1065) exceeded the 1000-line limit. Each splits along its existing
describe seams into same-directory siblings, with the fixtures that more than one
sibling needs extracted once into a shared harness.

The move is verbatim: test titles, describe blocks, and every assertion line are
preserved exactly (2061 tests before and after, no duplicate titles, no skips), and
the suite reports the same coverage.
2026-10-10 01:21:15 +08:00
bowenliang123 52a5843248 refactor(client): split the context browser into schema, tool-card, and node siblings
browser.tsx was 1252 lines and carried three separable concerns: the
producer-dependent tool-schema readers, the tool block (schema tables, call cards,
argument/result text), and the per-node rendering. They move into same-directory
siblings and makeContextBrowser keeps the file as its single public entry, so no
importer changes.

The split is a verbatim move: lib/client.js is byte-identical to the pre-split build
apart from the artifact banner.
2026-10-10 01:20:49 +08:00
bowenliang123 d746d57435 refactor(host): derive the fold bounds and drop the duplicated decode-tally aliases
FoldBounds restated six Config fields, DEFAULT_BOUNDS restated them again, and
resolveBounds listed them a third time. Deriving the type from the defaults leaves
one source for the bound vocabulary, and Config now composes Partial<FoldBounds>
with Partial<PluginSettings> — which also declares defaultDeltaBase, a field the
schema already carried while the interface omitted it.

DecodeSpans and DecodeCounts were byte-identical aliases of the same record shape.
2026-10-10 01:20:25 +08:00
bowenliang123 c4f5500de7 refactor(host): split the fold module into state, metering, and wire siblings
fold.ts held the persisted state shape, the retention bounds, the untrusted-usage
sanitizer with its accumulators, the surface projection, and the served-value
builders at 1234 lines. The state, the metering accumulators, and the wire builders
move into flat siblings; applyTimeline and the surface projection stay together
because the clone contract they enforce spans both.

The metering extraction also unifies BilledUsage with the shared CostBucketTotals:
one four-bucket vocabulary, so the accumulator input is spelled `uncached` like the
totals it feeds. The persisted and wire shapes are unchanged (both already carried
`uncached`), so no stateVersion bump and no cached-row invalidation.

Production callers import the owning sibling; fold.ts keeps re-exporting the
pre-split surface for the specs.
2026-10-10 01:19:59 +08:00