StatsPills buttons carry explicit aria-labels separating segments with
' · '; a log with no timed figure renders the counts pill as a static
reading instead of a button opening an empty dialog; the pills row owns
one exclusive open slot so sibling dialogs never stack. ContextMeter
restores the '~' approximation prefix (usedTokens prefers heuristic
projectedTokens). ModelSelect's jscpd:ignore now names the real gap
(useAnchoredPosition places from the left edge only). The pills Agent
Note's stale facts (locale keys, context occupancy, golden ownership)
are rewritten in place, en+zh. plan-review.e2e.ts parks the pointer
after Approve so the ContextMeter tooltip cannot leak into the aria
captures; affected goldens refreshed.
Master archived three Agent Notes this branch had fact-updated (archived
copies restored to their frozen master content; the pills note now links
the archived path), extended the steer-all placeholder to continuable
children (spec and subagent e2e take master's behavior with this branch's
reworded copy), and dropped the session-sharing sentence from feedback
acks (goldens re-refreshed with the new placeholder).
The A/B winner over the single-line StatsLine: a gauge pill (counts + TPS,
opening the session-stats dialog) and a database pill (billed total +
cache-hit share, opening the exact token-usage dialog), sharing a new
stat-dialog module. StatsLine and its dead locale keys are deleted, the
gauge icon is optically centered, and the composer keeps its 4px clearance
through the data-composer-stats contract. Agent Note added; stale
StatsLine references across implemented notes updated in place.
The ui-conversation.busyEnter setting selected Queue or Steer for plain
Enter only; the running draft's Send button always submitted through the
public InputActions.submit() face, which is fixed to Queue, while labeled
"Send message". A user who chose Steer got Steer from Enter and Queue
from the button beside the same draft with nothing explaining why.
InputBar now resolves the primary click through the same `enter` gesture
and steer-availability predicate as the keyboard path (ordinary Sessions
and continuable children) and submits via ComposerKeyboard.submit(mode).
While that delivery is available the button's tooltip and accessible name
state it ("Queue message" / "Steer message"; 排队发送 / 插话发送), so the
mode is never hidden; idle sessions, one-shot children, and locked
composers keep the plain Send label. The composer bar inject face
publishes the live preference as hooks.busyEnter instead of a resolver
closure, and resolveSubmitMode is a pure function taking the preference
explicitly, so label and delivery derive from one value in one render and
the label follows live Settings changes. The Settings row is retitled
"Send behavior while busy" with a description that names both Enter and
the button; the busyEnter field, its default, and the Host schema are
unchanged.
Tests pin both preferences on the running button for ordinary sessions
and continuable children, the live relabel, the idle Queue path, and the
pure resolver; the settings-chrome ARIA goldens and the live-interactions
running-draft and queue-actions failed goldens carry the new copy. A new
Agent Note owns the decision and reverses the pointer-ignores-preference
clause of the archived 2026-08-20 running-draft note.
The default and hero placeholders drop the ellipsis and name the / commands
and @ references as comma-separated actions; goldens and e2e assertions
refresh with the copy.
Clickable artifact links across the transcript (markdown anchors, prose
file mentions, web source/fetch links, produced-file chips, workflow
member links) now share one language: a dedicated --dsw-alias-link color
(light deepseek-500, dark deepseek-400) decoupled from
state-business-primary, font-weight 500, no underline at rest, and a
dotted 3px-offset underline on hover/focus. A new ui-primitives LinkIcon
leads each link with a currentColor category glyph (url / folder / code /
image / document / other) derived from the path extension by
classifyLinkPath. Produced-file chips drop the grey pill and 96px cap and
render at natural width, shrinking with ellipsis only on row overflow.
ToolRow's grey dotted file links and the grey show-in-folder action stay
as they were. The inline-code chip tint moves to neutral-50 (dark
neutral-800) with its 0.5px border kept.
Coverage: LinkIcon unit spec, refreshed markdown-dom fixtures, and the
clickable-links-gallery web e2e registered in the host compiler face.
Closes#3546
The persistence seam is now create/open/stat/list returning per-session
SessionHandles (read/append/flush/close); every log read and write flows
through the owning handle. The seam package exports only the service and
handle contracts, consumer-visible errors, and pure durable-data
validation helpers; each backend owns its complete storage runtime, and
the shared contract suites pin equivalent observable behavior. The
backend routes published sessions' live events by id into the active
write handle; agent-loop only acquires, seeds, and closes the handle.
Resume appends interruptedTurnClosers through its write handle;
session-query owns the revision-keyed cold cache. Legacy-only surfaces
are removed in the same swap: locate/readRaw/supportsRawArtifacts, the
legacy event-shape read migration, zstd torn-frame salvage,
DSH_SESSION_JSONL, and hook transcript_path population; a torn final
zstd frame is discarded whole; the session-list cold blank probe returns
on stat metadata (eventCount derived from the last physical row,
sizeBytes). The WebUI ZIP export serializes the logical log from a read
handle, so both backends export identically.
Refs #3245
Reconciled with the scope-grouped plugin list: compositionInventory keeps
its additive block over master's RemoteError refactor; the slimmed
settings-store keeps ctx-based signatures while staying a display-only
roster store (no describeFace, no select); dead transport-rejection tests
dropped with the wire that no longer rejects; module graph, catalogs, and
harnesses regenerated against the merged tree.
The settings plugin list projected ctx.loader.entries() alone, hiding the
plugins sessions actually run and rendering the web overlay's deliberate
disabled tombstones (tool-bash, tool-fs, ...) as two dozen plainly disabled
rows while the same modules ran in every standard-preset session.
- dsh-agent-presets: compositionInventory() answers flattened rows per
preset — newest live standing generation when mounted, composition file
otherwise with !!js disabled gates evaluated against the Loader context;
reading never mounts (regression-tested), refusal stays 'conditional',
raced files report broken with the reason.
- dsh-host-plugin-inventory: list() gains an optional agentPresets block,
resolving the roster as an optional peer and mapping fiber states to the
public phase vocabulary.
- ui-settings-plugin-inventory: preset group first behind a display-only
switcher opening on the default preset; global group collapsed with
failures floated; host-disabled modules enabled by >=1 preset fold into a
session-plugins drawer naming providers; search spans scopes and points
at matches in unselected presets.
- ui-agent-preset: the General-settings default-preset row is deleted — the
roster section's make-default and the new-session chip keep the field —
and the settings store slims to the display roster the header label reads.
Docs, catalogs, module graph, settings-chrome goldens, and the bilingual
Agent Note ride along.
conversation-column-overflow existed solely to verify the glow bleed was
clipped; delete it with its golden. The geometry golden picks up the
scroll body losing overflow-x: hidden. reference-composer now waits for
the stale '@' rows to settle before clicking: the menu keeps the
previous query's rows while the next loads, and index-keyed rows swap
content in place.
The turn tail's exposed meta line collapses into two icon pills — Usage
(database glyph, turn total) and Ran-for (clock glyph, wall time) — each
click-opening a details dialog; the calendar clock trails as plain text.
Cache hit, TPS, and TTFT move dialog-only, and narrow viewports collapse
the pills to bare icons. The TEMPORARY flat variant keeps the whole-line
trigger for the A/B test.
Rename the tool-presentation transport from code-mode to ptc everywhere
that is not written into session logs: the mode config value becomes 'ptc',
the preset directory/id becomes ptc, the demo becomes demo:ptc, the
dispatch waterfall becomes tools/ptc-dispatch-log (types PtcDispatch*), the
prompt rule becomes tools:ptc-only, source/test files become ptc.ts etc.,
and prose says PTC mode / PTC 模式. The session-persistent vocabulary
(durable events tool/code-dispatch*, logged plugin name tools-code-mode,
sub-call id segment :code:) intentionally stays and moves in the stacked
persistence PR, which is blocked until the SESSION_FORMAT_VERSION v0→v1
migration lands with it. run_code, its code parameter, CodeSdkLanguage,
CodeRunFailedError, the dsh-code-runtime family, third-party codex names,
and frozen archived notes keep their names.
The whole-line meta trigger read as plain text and hid what was clickable.
The Turn-usage trigger is now a data-icon pill (Usage {total} · Cache hit
{percent}%) seated right of the branch action with the action buttons'
hover chrome, so the one interactive element in the tail is visibly a
button; the timing facts (clock, run time, speed, TTFT) return to plain
non-clickable text behind a dot separator. The details dialog keeps the
Turn-usage title and full token buckets, gains a permanent cache-hit row,
and breathes with wider vertical padding. Narrow columns trim the pill
label to an ellipsis instead of widening the chat column. User rows and
turn tails share the recency gate: only the latest row of each kind keeps
its actions visible without hover.
* feat(ui-conversation): fold turn process before final answer
* fix(ui-chat): polish turn-process control row from review
* test(web): drive preset slash catalog with gestures
* fix(ui-chat): keep turn process order stable
* fix(ui-chat): preserve prompt order after pagination
Co-authored-by: Yif <877193178@qq.com>
A title now comes from an attached session's live projection cut or a cold
one's durable checkpoint, and from nothing else. Attachment is decided by the
session store at read time, so a session that attached after the listing is no
longer answered from a checkpoint its log has moved past — the stale-title case
`api-session.list` already handles this way.
The log fold and its per-log memo are gone. Folding one title costs a whole
log, and this call sits under every keystroke; a session no projection answers
for is labeled by its id and regains its title the first time it is opened.
`lib` leaves the default exclusions: Ruby gems and many npm packages keep
sources there, and the miss would be silent and total. A traversal whose root
is unreadable now rejects instead of publishing an empty index over entries
that are still good, which is what the stale-while-revalidate path claimed but
could not do while every readdir error was swallowed. A drill marks the menu
drilled only when its edit actually reached the draft.
Refs #3154
Refs #3180