Each column is labeled when a month begins inside it: the month changes
between its Monday and Sunday, or its Monday is the 1st; the label names
the Sunday's month. Months that began before the window stay unlabeled.
New insightsEntry field ('show' | 'hide', default 'show') in the
dsh-context settings namespace, edited from the Plugin configuration
card. The sidebar entry subscribes to the store and renders null while
hidden; the seat stays registered so flipping back needs no remount.
Fails open everywhere: a schema-less host, an unserved namespace, a
section without the field, a stale persisted value (loose union), or a
write that never persists all resolve to the visible entry.
The sessions/KPI columns' own 8px bar rendered flush against the card
edges, riding on them while scrolling, and a column gaining or losing
the bar reflowed its content by the bar's width. Reserve the track
(scrollbar-gutter: stable) and pad the columns so the thumb stays 8px
clear of the cards; the folded single-column layout resets the padding.
- Page the session grid at 12 cards per page: a filter change re-anchors
the pager at the first page, and clamping keeps a shrunken list's view
in range instead of showing a blank page.
- Redesign the session cards: 3 columns, a project pill, a first-active-
day line under the relative time, wrap-clamped titles with uniform row
heights, a turns-and-steps lead figure, and the cost label shortened
to 费用/Cost.
- Restack the insight band as one folding row — the KPI 2x2 block beside
the activity heatmap — and drop the aggregate composition card with
its now-unused fold, keys, and styles.
useEscapeClose resubscribed on every render — callers pass a fresh
closure each time — and each resubscribe's cleanup re-ran the pre-open
focus restore, yanking focus out of the overlay's own inputs on every
keystroke. Hold the callback in a latest-ref so the subscription and
its restore live on the active transitions alone.
The donut center shows the total alone — the overflowing
"tokens (estimated)" moves to the card's subtitle as plain
"tokens" — and the sort buttons follow the workspace browser's
vocabulary: Last updated / Total Tokens / Current Context.
The card pins to the 252px its fixed grid needs — about half the
previous share — and the composition card takes the rest; wrapped
below the row's fold, the heat card stretches again.
The raw session list carries every session while the workspace browser
hides its archive set, so the dashboard surfaced archived ghosts and
read as duplicates and stale rows. rowsOfSnapshot now drops rows present
in the workspaces snapshot's archivedSessionIds, failing open — an
absent seat or a hostile shape archives nothing.
Default the window to the last 8 weeks, draw fixed-size cells centered
in the card (the block's height now follows the seven cell rows instead
of stretching to the card's width), floor data-less days with a visible
gray, and rebalance the chart row to heat:composition 2:3 so the
composition legend flows into multiple columns.
README gains the dashboard feature entry, a Use-it row, and a section with
the panel screenshot; compatibility.md records the additive contextActivity
key (stateVersion 1, no row invalidation) and the startup warm-up that
cold-reads sessions missing their projection rows.
A cross-session insight surface: the sidebar-foot entry (stacked above
Settings by the harness's own foot layout, with a running-sessions badge)
opens a frame-wide overlay through the shared module store. The panel reads
the root-scope useSessions/useWorkspaces standard kit — every list row's
host-cached projection values — so no session log needs opening:
- KPI band over the picked range (7d / 30d / all): active sessions, billed
tokens, estimated cost (models.dev book), cache-hit rate.
- Activity heatmap (GitHub-style, 12 weeks, square cells filling the card):
a day click pins the session grid to that day.
- Aggregate composition donut over the range's sessions.
- Session cards with a mini composition ring, billed/turns/cost figures and
a workspace-group / project breadcrumb (deduped when both names match);
grid filters by range, pinned day, group chips (incl. Ungrouped), and
search; sorts by recency, tokens, or context size; a card click opens the
session through the harness's own sessions.open and closes the panel.
Subagent-origin rows are excluded, mirroring the workspace browser's own
predicate (plain open() cannot address them). Every row is sanitized in
isolation — a hostile row degrades to a metadata-only card, never an error.
The Context Dashboard's heatmap and session cards read the session list's
projection column, which only serves durable cache rows. The new
contextActivity unit folds each assistant settlement into a per-day ledger
(billed tokens + completed requests, keyed by local day, 400-day retention)
alongside the existing contextTimeline/contextHeaders units.
A session folded before a unit existed — or one that predates the plugin
entirely — has no row until it next goes live, so a one-pass warm-up
(backfill.ts) cold-reads every such session's durable log through the
projection cache's own cold-read ladder (coldSnapshot): each unit seeds
from its cached rows, folds the remainder, and the refreshed checkpoint is
written back. Live sessions are skipped (a throwing liveness probe skips
conservatively too), unreadable logs are logged and skipped per session,
and the whole pass rides a deferred inject across the
sessionQuery/sessionProjectionCache/sessionPersistence/sessions services.
The Context card's cost cell now prices the current agent TOGETHER with
every subagent session's cumulative billed totals, and a new subagent-cost
cell prices the subagents' share alone; both hover bubbles explain their
scope, the merged one listing the per-model rates of every billed side.
The subagents' usage folds out of the session-list snapshot's warm
contextTimeline rows (subagentCostFoldOf, whole descendant subtree,
cycles cut), with cold relatives' slim heads standing in. Those head
fetches leave the Agent network card's factory closure for a shared
page-scope cache (agentHeads.ts) the stats board's seat reads too, so a
relative is fetched once no matter how many cards consume it; the card's
list-snapshot subscription moves there as useSessionsSnapshot.
The fetched composition heads lived in component state while the promise
cache lived in the factory closure, so a remount (tab switch) reset the
composed rings back to green and the cache hit kept every relative from
ever re-fetching. A cached read now replays into the fresh instance, with
an identity bail-out so the settled replay on every snapshot tick cannot
loop. Also drops a redundant route wrapper and aligns the spec helper with
the fetch transport it stubs.
The session-list block serves projection values only from the host's
projection cache, so a relative that never attached since the timeline unit
last changed lists pressure-only (occupancy without composition) and its
ring degraded to the single fallback arc — every subagent ring looked green
until its session was opened. The card now fetches those nodes' slim heads
from the /api detail route (one read per session per page, failures sticky)
and re-folds the forest with the row's missing contextTimeline injected; a
row's own value always wins, and any fetch failure degrades back to the
pressure-only ring.
The split generation's detail transport rode connection.rpc.handle, whose
registration reads webServer off the connection service's own context —
cordis's strict-read guard rejects that for plugin callers, so the channel
never armed and every detail read fell through to the static fallback (405).
Mount the handler through connection.fetch.register instead (the same
exact-route seam the harness's own file-upload and media-reference routes
use), riding the authenticated /api fence; the client POSTs it same-origin.
The detail payload now also carries the slim wire head at the same fold cut
(the composition scalars the Agent network's cold-node ring fetch renders
from); a missing or malformed head drops out alone, never rejecting the
payload the detail cards consume.
injectionSourceName read source.kind/plugin unguarded on its first line,
so a null/undefined source would have thrown the fold's caller (issue #69).
The fold's only call site already gates on isInjection, so the reachable
behavior is unchanged; the guard closes the trap for future callers and
matches isInjection's tolerance for foreign source shapes.
pnpm >=10 blocks a git dependency's prepare until allowlisted, and the
prepare script ran husky only — so even an allowlisted
'dsh plugin add git+https://...dsh-context.git' packed the repo without
lib/ and the bundle had no main entry to load. Building in prepare makes
the packed git dependency complete; registry-tarball installs never run
prepare, so the npm path is unchanged.
The Context browser's category header carried only the hover utility, so
the tint vanished the moment the pointer left an expanded card and the
header melted into its body. The open state now keeps the hover
background via a lc-br-cat-open state class - the same token as the
hover and hover-link tints - on every category card alike.
The slash menu candidate carried only its name, section, and description,
so the row rendered the bare "context" while every built-in command row
showed its icon and localized title. The candidate now carries the plugin
emblem and a cmd.label title (the name renders as the trailing alias on
builds that show titles). Both fields are pure display data: harness
builds that predate their rendering ignore them and keep the bare name.
The Context browser's Injected Context and Skill Injections rows previewed
the raw injected content, while the Context Events card named the producer
(plugin id, reconciled instruction files, durable kind). The fold now
stamps that identity on the surface node alongside the event, and the rows
prefer it for the collapsed preview - the content stays one expand away,
and the row filter still scans the folded text. Nodes folded before the
stamp (additive-optional on the wire and persisted state, no stateVersion
bump) degrade to the previous content preview.
The slash menu renders one source-title row per trigger source, titled
from the harness's own slash.menu dictionary; the plugin's source name
`context` is not a key there, so the row showed the bare raw name and
read as a duplicate command (closes#67). Candidates now carry a
localized `section`, which the menu renders in place of the
source-title row — 上下文 in Chinese, Context in English.
The hover-link's re-light rules are a hand-enumerated CSS list keyed by
category, and the skill bucket joined the vocabulary without one — hovering
the browser's skill section (or the overview's) dimmed every bar with
nothing lit. Add the missing rule and pin the list-must-track-Category
constraint next to it.
The fold buckets all three skill-content shapes the harness injects — the
<available_skills> catalog digest, a user-explicit /name invocation's
instructions message, and the content a skill-tool load returns — into a
new 'skill' composition category instead of burying them in inject/tool
(closes#66). Skill-tool loads keep their tool identity stamped, so the
call tallies, hit counts, and failure marking are unchanged.
stateVersion 18 → 19: the per-category sums change shape, so cached rows
refold from the durable log on upgrade (docs/compatibility.md).
The trend chart, overview, Token donut, stats, and the Context browser all
gain the seventh bucket (orange-500); the browser's skill section pins its
rows like injects, so long sessions keep listing what the sums count.
detail.step/tip.step now read "第 s 步 (共 n 步)" / "Turn t · Step s of n" —
the same per-turn tally a turn-mode bar's stepCount carries, so step and
turn granularities agree on how busy a turn was. A shared turnStepsOf()
getter feeds the trend tooltip, the detail head, the files scope label,
and the browser picker/meta rows.
The Context stats grid's cache-hit cell now carries the chat stats line's
figure two decimals deep and explains itself through the shared stat-tip
bubble (the whole-session cache-read share of billed input), localized in
both locales.
The Tailwind responsive fold (ff10cff) retired the `.lc-col` flex rule and
moved every flex-row child onto per-element `flex-1 min-w-[min(360px,100%)]`
utilities — but the file-activity card's own root was missed. As an lc-card
size container its intrinsic inline-size is zero, so under `flex: 0 1 auto`
the whole card collapsed to its chrome (~18px) in EVERY host at EVERY width,
crushing its content into one-character columns (issue #65). The root now
rides the shared column idiom like its sibling cards.
Two more narrow-pane defects fell out of the same audit:
- A content-box card read the `min-w-[min(360px,100%)]` floor as its content
width, so on sub-360px panes every floored card overflowed its row by its
own padding+border (18px), clipping past the pane edge. The plugin ships no
preflight, so `.lc-root .lc-card` now sets border-box itself.
- The events card's kind chips had no wrap fold of their own, so the English
chip row spilled past the card edge under 380px; it now rides the same
`@max-[380px]/lc-card:flex-wrap` variant as the file card's chips.
Verified in the browser across the right-Sidebar pane at its drag minimum
(~300px), 510px, 822px (the two-across threshold) and 856px, the full-width
tab at 390px and 1440px, and the /context modal, in both locales: the file
card shares its row again, no vertical crush, no right-edge clipping, no
horizontal overflow.
Fixes#65
New 'Tool Schemas - Default sort' row in Settings -> Plugins -> dsh-context
(ahead of the File Activity one): size, call count, or name, persisted per
user through the dsh-context settings namespace. The Context browser reads
it as the mount-time default for its tools toolbar; in-toolbar toggling
stays session-local and never writes back. The default default is now call
count. Degradation mirrors the existing preferences: the host schema is
loose (a stale persisted value falls back to the default), the client drops
values outside the vocabulary, and a host without the settings surface (or
a section missing the field) keeps the schema default, so the tools list
stays fully functional either way.