* 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>
Progressive enhancement on body in the shell base sheet so mixed
Chinese/English copy gets consistent spacing without content edits;
engines without support ignore the property.
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
TurnNavigator was an unmemoized component rendering one div and one
button per loaded Turn, so every ChatView render rebuilt the whole rail:
143 button rebuilds per commit in a 300-Turn session against 8.4 in a
4-Turn one, while a streaming answer commits dozens of times.
memo alone would not have helped, because navigateToTurn was rebuilt on
every render and broke prop identity, so it moves into useCallback.
The Host session list already carries each session's `updatedAt`, which is
the number its own rows show; reading it there keeps the two surfaces from
disagreeing and avoids making a context capability depend on the BFF
assembly that owns the `sessionListMetadata` projection. A session the list
does not carry falls back to the candidate's creation time.
Refs #3154
Session candidates labelled from projection checkpoints instead of a full
log fold per keystroke, with the uncheckpointed remainder folded once and
memoized while its log stays cold. The file index keeps answering while an
invalidated traversal rebuilds behind the caret, and its default exclusions
now cover build outputs so deep sources stay reachable.
Rows carry only what distinguishes them: a file names its parent directory,
a session names its workspace only when that workspace is not the current
one, and a drilled listing names none because its new breadcrumb does.
Resolves#3180
Related to #3154
The merge resolution had reverted THIRD_PARTY_NOTICES.md to the SDK 0.3.220
rows; the lockfile pins 0.3.241, so CI regenerated a mismatch. The apply spec
now drives the injected onHover face, closing the per-file coverage gap on
src/client/index.ts.
Candidate rows lead with domain icons instead of localized text
prefixes; pointer and keyboard share one reducer-owned highlight (last
input wins); drillable folder rows reveal a localized Browse-folder +
Tab keycap hint with the library chevron; pending sources render
skeleton bars; the menu spans the composer card. The editable @dir/
text decorates color-only — the domain icon now belongs exclusively to
the settled reference chip. Composer placeholders advertise / and @,
and the zh copy for commands is unified to 指令.
The Agent Note (both languages), the PR prose, and the commit message
claimed a 13px floor for the table variants; the formula has none —
max(13px, setting − 2px) selects the −1 branch at low settings rather
than clamping the result, so the tier bottoms out at 11px at the 12px
setting, matching think text. Rewrite the claim, say so in the axis
comment, and split the README sentence that lumped body-pair and
secondary-pair consumers together.
Assert the engine-resolved secondary size in the settings-chrome e2e
(13px at the default, 13px at the 15px boundary, 14px at 16px,
unchanged across reload), sync the StatsLine and workflow-panel spec
headers with the tier they now pin, and note why memberLabel stays at
the body size.
Derive --dsh-content-font-size-secondary (setting -1 at <=14, setting -2
above; 13px at the default) with --dsh-content-font-delta-secondary in
gradient-shadow-text.css, and move every one-step-under-the-body text
onto it: think text and reasoning summaries, the shared DisclosureRow
title, ToolRow and bash-row summaries and file links, compaction/
context/command/retry/error rows, StatsLine, the workflow-run panel
tiers, reference summaries, the turn-status clock, and the feedback
note trigger. The markdown table variants join the same tier instead of
staying fixed; its 13px floor keeps them legible at the 12px setting.
At the default setting the flow-row titles and summaries render at 13px
(previously 14px) so they match think text at every setting instead of
sitting 2px above it.
The provider-card seat's keyConfigured now derives from the reference the
page would use — the profile's apiKeyEnv, or the page's derived
<ROUTE>_API_KEY while the profile names none — so the add-provider draft
agrees with its own editor about an existing conventional credential (the
store joins the derived describe in the same batched call, as
ProviderRow.derivedCredential). ModelsSectionProps makes the renderSlot seat
required so a direct render that forgets it fails to compile; the one such
render in provider-form.client.spec regained a real mount and the test
boilerplate collapsed to renderSlot={() => null}. The extension-slots Agent
Note now states the keyed cell's real override rule (same priority throws,
a different priority shadows), and docs/subsystems/slots.md carries the two
new seats in its hierarchy, both languages.