The dashboard's row IS the team now: each listed session folds its WHOLE
subagent subtree into itself (all levels, mirroring the Context tab's cost
fold) — subagent-origin sessions never list, so nothing counts twice. The
session card's billed tokens and priced cost (now labeled 团队用量 /
团队费用), the KPI band's volume and spend, the last-7-days usage chart,
and the heatmap all read the family scope: the subtree's cost buckets
merge per row, the daily ledgers merge day by day, and the composition
split proportions each member's OWN composition instead of the root's.
The flow's fourth node becomes a three-row team ledger: the family's
billed-token and estimated-cost totals in the header, then one row per
scope — the current agent and the subagent subtree with its session count
— each pairing its billed tokens with the estimated cost (billedTokensOf:
the token face of the same buckets the estimate prices). The subagent
scope is the WHOLE descendant tree, which the copy now spells out; the
rows render unconditionally (a scope with nothing billed reads 0 / —),
and both tooltips gloss the token figure.
The six-cell grid becomes a left-to-right flow: two source cards (Input /
Output — human inputs plus the whole session's file reads/writes, with
searches and image reads when they happen; Context Events broken down by
kind) feed the highlighted Current Session node (turns / steps / subagents
— the subtree's session count, folded from the session list alongside the
cost usage), which drains into the two effect cards (Tool Calls with the
three most-called tools and an overflow pill; Cost with the family figure
and, when a subagent subtree reports, the this-agent/subagents split).
Measured bezier ribbons — a translucent band plus a traveling dash per
element, not a conservative Sankey — connect the nodes and flip to a
top-to-bottom stack when the card narrows past the container-query fold.
A ctrl+f-style lens scoped to one section, toggled from a magnifier
button beside the copy control: a debounced query (150ms, so rapid
typing or deleting rescans once instead of per keystroke), matches
wrapped as DOM-level <mark> over the rendered body (same lens in raw
and markdown modes, capped at 500 rendered marks with a '+' counter),
Enter/Shift+Enter or the chevrons cycling the active match into view,
Escape folding the bar. The section body remounts on a text change so
React never reconciles over the injected marks; the restore rebuilds
the original text nodes, keeping the copyable source byte-identical.
The card's reading was the ring's integer (`Math.round`), so a 101.6k /
1.0M session printed 10% while the kernel's own occupancy read 10.2%.
The percent now comes from one shared `occupancyPercent` helper that
mirrors dsh's `contextOccupancy` — the composer ring's derivation
(`min(100, round(used / window * 100))`, `used` = `contextPressure`'s
`projectedTokens ?? pressureTokens`) — on a 0.1 grid, and keeps the
kernel's numerator precedence ahead of the plugin's derived anchor. The
kernel symbol is unreachable from a client plugin (no package export, a
self-registering browser bundle), so the formula is mirrored by
necessity, as docs/compatibility.md now records.
The Agent Network rings read the same helper (whole tenths stay compact
there), and the number row wraps so the wider reading folds to its own
line instead of spilling on the narrowest panes.
The token card's corner already carries the session's cache-hit share
(ef72a7b), so the Context Stats board duplicated it. The grid is now six
cells: turns, steps, human inputs, live tool calls, and the two cost cells.
The removal takes its dead plumbing with it — the card drops its `usage`
prop, and `cacheHitPercent` loses the `decimals` override that existed only
for this cell's one-decimal display. The README rows and the card's
screenshot move the figure to the token card.
The token card's title row gains the session's cache-hit share in its
right corner — the harness chat line's own reads-over-billed formula,
reusing the stats board's label and tooltip — hidden until something
was billed. The timing card's throughput chip drops its pill chrome and
bold weight, now reading 输出速度 N tok/s (zh) / TPS N tok/s (en) with
the TPS expansion on the tooltip; both corners share a new plain-text
.lc-card-rate style and keep their native-title tooltips.
The Context Insights surface moves from a sidebar-foot button opening a
frame-wide overlay to a first-level panel, a sibling of the shipped
Plugins and Automation tasks pages: the page registers on the layout's
keyed `main` slot and the entry on `sidebar.panellist` under one id
(`dsh-context`), so the shell owns the entry row and panel switching —
mounting the page IS opening it (no plugin-side open state, the footer
entry, the overlay seat, and the overviewStore all go away). The
`insightsEntry` preference now mounts/unwinds the registration pair.
Both seats ship on every supported line (0.1.5-rc.1+), pinned per
baseline by the new insightPage seam probe; runtime-verified on a
disposable 0.1.5-rc.1 profile.
The chrome mirrors the shipped first-level pages: one scroll region
with the 960px centered column, the 20px page heading beside the range
group and the balance capsule, and the page element as the `lc-ov`
query container. Inline-size containment makes the page the containing
block for fixed descendants, so the heatmap/usage/balance tooltips can
no longer ride the harness Tooltip (its portal prop is 0.1.7+): they
now carry `data-lc-tip` served by the page's own hover tip — one fixed
bubble portaled to <body>, delegated off the page root (the image
lightbox's precedent), correct on every supported line.
Session card jumps need no close (openSession's own navigation returns
to the conversation); the settings row jump unmounts the page by
selecting the Plugins panel. Verified across 1440-390px in both
locales, and the /context modal is untouched.
The Duration toggle (added in 01b1532) never joined the title-adjacent
zero-margin rule, so its bare .lc-gran kept margin-left: auto and split
the free space with the control cluster — on wide cards it drifted to
the middle of the title row (242px each side at ~930px), and on wrapping
widths it landed wherever the auto margin happened to put it (line 1's
right end in English, line 2's lead at the narrowest).
DNA / Adaptive / Duration now ride the title text's right as ONE
multi-toggle segmented group (.lc-trend-mods), the tips moving from the
group wrappers onto the buttons; the row keeps a single auto-margin push
(the control cluster), so wrapping drops the two semantic groups whole
instead of scattering the modifiers. Also drop the stale "Total/Delta
disables under DNA" note on the disabled-chip rule — no gran button has
been disabled since DNA+Delta shipped.
The Context preferences card gains its eighth row — the trend
duration curve — served across both settings generations: the V3
`settings.register` schema and the V4 Config form (a `.volatile()`
field, so an edit never remounts the entry). The client settings store
parses it fail-open like its neighbours; the trend card's Duration
toggle seeds its mount state from it (default on), and in-chart
toggling stays mount-local and never writes back.
The trend card gains a title-adjacent Duration toggle: every request's
step active time (`activeMs`) drawn as a curve over the bars, read off
a right-hand quartile axis; turn granularity sums the turn's steps, and
a bar without the figure breaks the line instead of faking a point.
The fold stamps it at `step/end` onto the step's committed request
record: the whole step window (the model call plus its tool runs, per
the agent-loop's in-step `executeToolCalls`) minus the step's user
waits — approval decision intervals (`approval/asked` →
`approval/decided`, a crash-dangling pair closing at the step's own
end) and `ask_user_question` answer windows (the tool's body is the
wait itself, in both the blocking and the timed schema's foreground
phase). Overlapping waits price their clamped union, never twice.
The wire field is additive-optional and bumps the timeline projection's
stateVersion (25 → 26): requests already folded can never gain the
figure later, so cached rows refold from the durable log on upgrade.
The timing card gains a bottom band that reads where the session's ACTIVE
time went: every completed step's slices — the TTFT wait, the decode blocks
in stream order, the tool-run windows, and the in-step residue — packed
gapless in occurrence order, each band's width its share of the cumulative
active time (idle time between steps takes no track), with ticks at zero,
the quartiles, and the full span.
Host: the fold tiles each step at step/end (clamped into the step window,
first-wins de-overlap so parallel runs paint their union, holes filled with
the residue kind) into a new spans collection — bounded to the newest 2,000,
riding the detail channel and the inline view, never the slim head. The
decode window anchors at the first OBSERVABLE instant (the first token, or
an earlier block marker the token packing could not stamp), so a redacted
reasoning block keeps its span instead of being swallowed by the wait.
stateVersion 22 → 23: the per-step collection cannot be backfilled by later
events, so cached rows refold from the durable log.
Client: the strip joins the card's hover link by KIND — a band hover lights
every span of its kind plus the donut arc and the legend row, and the
floating tip answers only the strip's own pointer (the mirror rule).
In DNA+delta combo mode the tooltip's no-band fallback read
tip.total over the delta record, whose total IS the churn —
the label claimed a total while the figure was the change
magnitude (and a zero-churn bar showed 'Total ≈ 0' anywhere).
The band lookup now sits ahead of the mode branches: a hover
with no band under the pointer falls through to the mode's own
row — the shared delta row (fmtSigned of the net) in delta
modes, the total row otherwise. The delta branch reuses
fmtSigned instead of its inline sign formatting.
The DNA on/off state lifts no further than it already sat: the Context
tab's `dna` state now feeds the Context browser through a controlled
pair of props (dna + onDnaChange), so toggling either card's switch
moves the other — the trend bars and the browser's composition bar
become fingerprints together, and drop back together.
The browser stays self-contained where no parent links it: the pair is
controlled only when BOTH props arrive, a lone `dna` prop is ignored,
and the /context modal keeps its mount-local toggle (the established
optional-seam idiom).
DNA and Delta are orthogonal now: the Total/Delta switch stays live under
DNA, and delta diffs each bar's bands against the previous bar's by key
(deltaBandsOf, dna.ts) instead of diffing category totals. Newcomers and
grown items ride the up arm in read order, removed and shrunk items hang
on the down arm in the previous bar's read order, and unchanged items
vanish from both — the first bar carries no change at all, mirroring the
record delta so the axis stays change-driven.
DnaDeltaBar renders each bar as ONE gradient div spanning both arms
around the zero line (down arm reversed bottom-up, then the up arm),
absolutely positioned like the segmented delta arms, with the entrance
rise anchored at the zero line via a per-bar transform-origin. The
cross-bar lifetime highlight becomes a dumb resolved-slice layer both
DNA flavors feed; the tooltip shows the signed per-item change; band
clicks reveal the item in the Context browser through the same bridge
in both flavors. Adaptive rescaling measures the band arms and
re-measures on any mode flip so the axis never rides a stale scale.
A click inside a DNA bar hit-tests the same pointer-to-band mapping the
hover uses and lifts (bar seq, band) to the view, which forwards it
through the browser's one-shot reveal bridge: the browser switches off
the live surface to the bar's step, opens the item's category and
element row, and scrolls it into view. The bar's own click still pins
the step in the detail panel, and both land on the same seq.
The nodeFocus bridge generalizes from message-only (seq) to the band
keys both DNA surfaces share — 'sys' (system prompt), 'tool:<name>'
(a tool schema), 'n<seq>' (a message) — so header bands and message
nodes reveal through one path, and the step-brief / file-activity
locates convert to the same keys. The reveal now also clears the row
lens, so a stale filter can no longer hide the revealed row (the
brief flow had the same latent trap).
DnaBar's hit-test becomes a total function: the first visible band
starts at off 0 (leading zero-token bands advance nothing) and the
pointer fraction clamps into [0, 1], so a hit always exists.
The Context Dashboard entry now renders the document sheet in the
surrounding text colour instead of the six-colour palette, so it sits
quietly beside the harness's own footer rows. The identity seats (tab
chip, command, sidebar registration, panel head) keep the polychrome
default; the package icon.svg is untouched.
Closes#95
Each bar redraws as that request's context fingerprint — ONE gradient
div paints the assembled items in read order (system prompt, tool
schemas, then messages by seq), coalescing adjacent same-color bands,
so thousands of bars cost the same DOM as the segmented stacks.
Hovering a band lights its lifetime slice in every bar at once (the
items' keys are stable across requests), and the tooltip names the
item via the label core shared with the browser's DNA mode
(dnaBaseLabel). The toggle rides the title's right, left of the
adaptive switch; the Total/Delta switch suspends while DNA is on —
DNA plots absolute composition, and a stale 'delta' state degrades to
total semantics instead of emptying the chart. The browser category
focus is ignored (the bands already are the full composition).
Bands are built per displayed record with the same pure client-side
assemble() reconstruction the Context browser uses, memoized on the
timeline so hover re-renders never reassemble. Host-side is untouched
— no wire or protocol change.
A 'Context Browser · Diff baseline' row leads the tool-sort preference:
'prev step' (default) or 'prev turn', reusing the toolbar's vocabulary.
The browser's baseline toggle mounts from the persisted value; in-toolbar
toggling stays mount-local. Served through the settings namespace schema
and the Config-form generation, degrading to the default on stale values.
Replace the fixed 'vs previous turn' hint with a baseline toggle (default:
previous step). A predecessor-less step or live surface diffs against zero,
so the whole makeup reads as change; on live, 'previous turn' measures this
turn's growth so far. The step picker options drop their time suffix.
Add useTimelineSource cases for the cold read rendering from its own slim
head, the retry after a failed/absent/headless cold read, and a pushed head
taking the shared per-session store back. Surface the retryable failure note
in the Context tab and /context modal specs, isolate the page-lifetime
detail stores between tests, and record the no-stall contract in
docs/compatibility.md.
Align the assistant text section head with the Answer filter capsule
term, matching dsh's finalized-answer vocabulary for the non-thinking
reply content.
A tool-name capsule — a row tag (tool results, call breadcrumbs) or a
call head (→ bash, ← bash) — no longer clicks through to the tool's
definition row in the Tool Schemas category: the schema reveal from
5a62b2b is removed whole (the reveal jump, the onSchema threading, the
Section label click affordance, the hover-link styles, and the tooltip
key in both locales). The ×N call-name grouping stays — one capsule per
distinct call name, repeats folded into a multiplier — as plain tags on
the row button, so a tag click bubbles to the row's own toggle.
The assistant search box scanned the tag, preview, and the node's
stamped text — the reasoning blocks live only on the conversation join,
so thinking text was unsearchable. Derive one concatenated reasoning
face per assistant row (both block vocabularies, malformed blocks drop)
and scan it alongside the other faces; the placeholder names the new
face in both locales.
A call breadcrumb no longer renders one capsule per call joined by '›'.
The names group by identity: one capsule per DISTINCT tool in
first-appearance order, repeats folded into a multiplier ('bash ×3'),
each capsule still clickable to reveal the tool's schema. The row's tag
slot becomes a shrinkable capsule group, and the hover affordance moves
from the inner segment to the whole capsule. The tag's scan text keeps
the raw joined names, so the text filter still matches every call.
A tool-name capsule — a row tag (tool results, call breadcrumbs) or a
call head (→ bash, ← bash) — is now clickable and reveals the tool's
definition row in the Tool Schemas category: the category opens
unfiltered, the named row expands, and a one-shot scroll lands on it
(the same reveal the DNA bands use). A breadcrumb renders one clickable
segment per call; its segment click stops propagation so the row toggle
cannot overwrite the reveal. Tags that do not name tools stay inert.
Hover affordances: tag segments lift to the primary label on the shared
interactive hover face; call heads shift to the brand face + underline.
The fold stamps SurfaceNode.calls only on text-less replies, so a reply
carrying both text and tool calls passed the Tools kind filter (proved
via the conversation join) but showed no tool name on its collapsed row.
Fall back to the join-derived call names for the row tag — the same
recovery the step brief applies — so every row the Tools filter keeps
carries its tool breadcrumb.
The assistant category's search box gains Thinking / Tools / Answer chips, each carrying the shown step's message count; clicking one keeps only the replies carrying that kind (intersected with the text query), clicking again clears, and the pick follows the text filter's lens rules (reset on a category switch, kept across step picks). Kinds classify from the conversation join's blocks (reasoning / tool-call / text, legacy content joins included) and fall back to the surface node's calls/text when the join misses.
When one models.dev provider priced the whole scope, the cost cell
renders as an anchor opening https://models.dev/providers/<pid>/ in a
new tab — the natural "check these rates" follow-up to the tooltip's
listing lines. A multi-provider scope stays unlinked (the tooltip names
each face instead), and the anchor reuses the framed cell chrome with
the link chrome reset.
The cost and subagent-cost tips render a per-model price table: each
billed model lists its non-zero bucket rates at two decimals, closed by
a line naming the registry face — the models.dev provider id and model
id — its rates resolved from, so a gateway route prices visibly against
its actual carrier (the confusion behind issue #93). priceFaceOf
carries that face out of the resolution: resolution candidates keep
their branch model id, the branch lookup returns its hit, and priceOf
delegates to the face. The peak|off pair display gives way to the
peak-window footnote, and the CNY conversion note rides the CNY display
only.
A gateway provider id outside the models.dev registry falls back to the
model-side resolution, where azure's self-named SDK package won
`deepseek-v4-pro` and its flat listing (no cache_read published) billed
cache hits as plain input at 4x the official input rate (issue #93). A
`deepseek-` model id now prices from DeepSeek's own registry branch
first — the same first-party specialness the peak/off-peak split
already grants DeepSeek — and falls back to the model-side tiers when
the first party does not list the id.
Fixes#93.
The KPI band, the aggregate stats row, and the 3:7 body now share one
scroll region (.lc-ov-scroll) under the fixed head; the columns drop
their own scrollers and keep natural heights (align-items: start), and
the folded width's whole-card scroll branch goes away with them.
The dashboard's first row becomes a 1:1 column pair: the KPI band's six
figures (three per row) beside a new last-7-days usage chart whose card
stretches to the same height. Each day pairs a billed-tokens bar (the
heatmap's blue) with its priced fee (green), folded off the merged daily
ledger and priced by the same book and estimator as the KPI cost cell —
per-turn attribution, never the session's last-active day. The plot is
framed like a chart: hairline gridlines at quarters of each series' own
peak, a solid baseline, tick rails on both sides (tokens left, cost
right, dashes while nothing prices), and a day strip mirroring the
columns' flex geometry. The pair folds to one column at 932px of
container and the KPI grid to 2x3 at 640px; the bars' box keeps a
min-height floor so percentage heights survive the folded single-column
row. The cost legend qualifies its scope (price-mapped models only)
while the tooltip line stays bare.
Each metered settlement now books its billed buckets into its day under
the (provider, model, period) in force — the same SessionCostUsage raw
material the timeline's session-cost totals carry, attributed to the day
the open step INITIATED (step/start armed, step/end cleared, settlement
instant as fallback) so fees land on the turn's own calendar day. The
fold tracks the route in force (last request/header wins) and splits
DeepSeek peak/off-peak off the same instant, so its per-day records
price identically to the session-cost totals. stateVersion bumps 1 → 2:
the dashboard's warm-up cold-refolds every stale row from the durable
log on upgrade. The client's activityOf re-proves the additive-optional
record with the same sanitizer the session-cost rides — a malformed
record drops the fee whole and keeps the day's tokens.
dsh provider route keys are arbitrary strings (subscription plugins
register 'claude'/'codex', users declare gateway profiles), while the
models.dev registry is keyed by vendor slugs — so every session billed
through such a route fell to the cross-provider scan, which priced only
when exactly one branch carried the model. The Cost tile dashed
hundreds of sessions whose model ids match models.dev exactly
(issue #91): claude-* resolves under 9-20 resellers, gpt-* under 20.
pricesBookOf now also extracts each provider's registry npm package and
builds a resolution index over the book (cost.ts priceIndexOf): every
model id lands under its lowercased key and its '-'-suffix tails, so a
provider the book does not know prices by model id alone. The vendor
branch is picked from registry data, never a per-model hardcode:
- 'vendor/model' dialects: the org segment names the vendor (exact, or
the registry id behind a variant org — deepseek-ai -> deepseek),
falling back to the bare last segment;
- the model's own vendor: a provider whose @ai-sdk package is named
after itself (@ai-sdk/anthropic -> anthropic) — mirrors and gateways
ride the generic openai-compatible package or someone else's;
- a provider the id itself names (deepseek-v4-flash -> deepseek),
matching '-' and '/' separators;
- a lone carrier, as before.
Each tier takes only a unique or unanimous rate (float-noise tolerant);
disagreeing candidates price nothing rather than guess. A provider the
book DOES know still never falls back — known branches stay
authoritative.
The rename table gains only 'deepseek-account' -> 'deepseek': the
native account route shares DeepSeek's period-based list, so the fold
now splits its peak/off-peak buckets too (stateVersion 21 refolds
cached rows that booked everything under 'peak').
Measured against the live models.dev dump over dsh's real vocabulary —
both native routes, every llm-pi-ai catalog route x its catalog models,
and issue #91's subscription/proxy ledger routes (1397 pairs, 49
routes): priced pairs 931 -> 1157 (+24%), routes with at least one
priced model 35 -> 47, with zero regressions and zero rate drift
against the old resolution; issue #91's routes go 0/35 -> 35/35.
The Cost KPI sub-line now reads 'N of M sessions priced' so unpriced
sessions are visible instead of reading as the full count (also #91).
Bumps @opencode-ai/models consumption onto the refreshed registry.
Fixes#91
The step-granularity chart had no position landmark along its column
grid (the turn strip numbers turns, not steps). Every 5th bar now
plants a flag at its right edge: a 1px pole rising from the baseline
into the top padding band, the bar's cumulative step number boxed at
the pole's top and streaming left over its own bar, so the banner
never contributes to scrollable overflow at any step count. Turn
granularity plants none (the turn strip already numbers that grid);
the flag renders under the scissors glyph on a same-bar collision.
The panel's first row is now the range's six KPI figures in one
full-width line, lifted out of the insight column; the aggregate Token
and Timing Stats pair moves below it, and the 3:7 body keeps the
activity heatmap and the preferences entry beside the session column.
The band folds 6 -> 3x2 at 1000px of container and 2x3 at 640px, so no
cell ever ellipsizes its figure.
The panel's first row folds the range's sessions into the two donut cards
the per-session Context tab opens with. Token Stats splits the billed
volume by the SAME composition categories: each session's billedParts
estimate sums by category (output exact), and the center total stays the
merged billed figure the Tokens Used cell shows. Timing Stats reuses the
per-session card verbatim over a summed TimingTotals whose additive-
optional decode-split and throughput fields sum their carriers only, so
older cached rows keep the un-split shape and no-chip fallbacks. The pair
sits at half width over the 3:7 body and folds to one column at 700px of
panel width.
dsh's 0.1.6 selection refactor moved session selection to the view owner:
uiWorkspace.openSession is the verb the built-in sidebar's row click uses,
and the sessions service no longer carries open(). The plugin's jump (the
Context Dashboard's session cards and the Agent network card's nodes) still
called the retired sessions.open spelling, so on 0.1.7-rc.2 the panel closed
but no session opened — silently, by the verb's fail-soft design.
One shared openSessionVia (services.ts) now serves both call sites: it
re-proves both faces per call, prefers the view owner's verb (invoked bound
to the service instance), and keeps the retired sessions.open as the
degradation path. The per-baseline sessionNav seam pins both spellings
presence-as-declared in the real-code matrix, and the always-on client-face
probe drives the jump through each generation's exact composition (the V4
baseline serves no sessions-side selection at all — the exact shape issue
#90 died on).
The plugin.settingsOpen hint - the Plugin Info card's row value and
tooltip, and now the insights panel's settings row hint too - read
"Open preferences" / 打开偏好设置; both locales now name what the jump
actually opens, this plugin's own settings page ("Open plugin
settings" / 打开插件设置), not the shell's preference center. The two
settingsCard comments quoting the jump's label follow.
The Context Insights panel's insight column now closes with a framed
settings row: the plugin gear plus the localized jump hint, styled as
the column's third card (the session-card hover). The click rides the
same guarded preferences jump as the Plugin Info card's row
(settingsJump.ts) and closes the overlay with it - the jump drives the
shell chrome behind the panel, so staying open would hide its landing.
The panel spec drives the jump end-to-end against a simulated
Plugins-page chrome, from the panel-entry click to the card-open
control.
The supported range is now two baselines - dsh-v0.1.5-rc.1 (session
format V3) and dsh-v0.1.7-rc.2 (V4) - enforced by the runtime gate at
BASELINE_DSH_VERSION = 0.1.5-rc.1; below it the fallback units and the
upgrade modal take over (0.1.2/0.1.3 hosts were previously served by the
real folds).
The pre-V3 log shapes are dead code on every supported harness: the
running harness migrates historical logs on every read path (or refuses
the session wholesale), and 0.1.5-rc.1 rejects a request/header carrying
header.system outright. The fold therefore drops the V0/V2-only paths -
the header.system envelope system prompt, assistant/chunk floods,
{start, end} replacement endpoints, tool/code-dispatch - and the state
fields only they wrote (systemsFromHeader, the stepStart decode
accumulators). stateVersion stays 20: no supported log folds
differently, and pre-V3-era cached rows are discarded by the projection
cache's identity gate (and the strict stepStart sub-schema) instead of
served. The headers unit folds metadata-only epochs (envelope
systemTokens write dropped; the v1 cached-row read-compat and its pinned
stateVersion 1 stay); replaceRangeOf speaks startSeq/endSeq only; the
browser's epoch fetch maps request/header to tools and system/message to
the prompt text.
tests/baselines.ts re-fits to the two entries (the right Sidebar seam is
required on both), the compat matrix drives both tags' REAL sources
(canonical logs re-shaped to V3, the hostile probe renumbered past them),
and the version fixtures pin the new floor. Manifest: dshReleases
{0.1.5-rc.1, 0.1.7-rc.2}, dsh peers >=0.1.5-rc.1 (satisfies the 0.1.7
admission check under includePrerelease), devDeps pinned to the
0.1.5-rc.1 surface plus the ui-primitives runtime closure the client
lane inlines (test-only), and the release workflow fetches the two tags.
docs/compatibility.md is rewritten around the two-line matrix; README,
AGENTS.md and the issue template follow.
Also riding this move, from the audit pass: the fold re-proves an inject
source's form before it books the event record (the harness validates
`kind` but not `form` - a hostile one previously failed the strict
wire/state schemas on every delivery, the #44 freeze class), the headers
unit guards a data-less envelope (it has no try/catch of its own), the
schemastery schema annotations drop the explicit z<T> wrappers the 3.18.4
Mode generics reject, and the version-history note records why
stateVersion is deliberately not bumped.
The placeholder promised less than the user wanted and the matcher
delivered less than the placeholder should: the search box only
substring-matched title and directory. It now also matches the
session's newest own message (timeline.lastUser, the card preview
text), case-insensitively, and a non-string fold shape safely never
matches. The placeholder copy follows in both locales ("Search title /
path / last message..." / "搜索标题 / 目录 / 最后消息...").
The card's depth always rode billed tokens; now a Sessions/Steps toggle
in the card's top-right corner (default steps) picks the metric, and the
day tooltip gains a steps line beside the sessions count. Sessions and
steps shape the window's days on different spans - one long session
drops a step a day, another day stacks a run - so the join tracks an
independent window maximum per metric and never scales one off the
other's peak. A billed day with no settled step (a legal wire shape)
stays gray under steps yet still tips and pins. The toggle rides the
shared lc-gran idiom inside a lc-heat-ctl wrapper - one auto margin for
the hint text plus the group - and specs cover both scales plus the
panel-level switch.
The single-glyph 一/三/五 and M/W/F read as cryptic initials; the gutter
now carries the full names 星期一/星期三/星期五 and Mon/Wed/Fri. The
weekday gutter drops its 10px single-glyph width and self-sizes to its
widest label (the locales differ), with nowrap keeping a full name on
one line.
Two borrowed from GitHub's contribution graph. The grid gains the
Less→More legend under its right edge - the five swatches reuse the
cells' own depth classes, so the key can never drift from the scale it
explains (ov.heat.less/more, 少/多). The month gutter now also names the
window's opening column, so a month already running at the window's
edge is announced instead of left blank; month-start columns keep the
existing rule and text. The component grows a column wrapper to hold
grid and key, and the specs cover the legend's five steps and both
label paths (the wi>0 opens-on-the-1st case guards the short-circuit's
covered branch).
The weekday gutter drew 一/三/五 on rows 1/3/5 while the grid was
Monday-first, so every caption named the row below its day - today's
Thursday cell read as Wednesday. Rows now start on Sunday (row 0 =
Sunday): days.ts renames mondayOfWeek to sundayOfWeek and anchors on
getDay() directly, the grid builds off that anchor, and the labels key
by the row index they name so no row arithmetic can shift them again.
The month gutter is untouched - the label still marks the column
containing the month's 1st, now read off the column's Saturday. Specs
re-derive every pinned date from the Sunday-first calendar.
The Plugins page's package cards draw each bundle's icon from the Host's
package metadata (manifest-relative SVG/PNG/JPEG/WebP, inlined as a data
URI), which we never declared - the cards fell back to the default
artwork. The Context emblem's single graphic source is now the
package-root icon.svg: the package.json "icon" field points the Host's
reader at it, and the client build inlines the same file through a
"*.svg?raw" channel (the bundled ContextIcon re-renders it at every
requested size), so the code carries no second copy of the paths. A spec
pins the file and the component in lockstep; the files list ships the
asset.