Commit Graph
90 Commits
Author SHA1 Message Date
imccyu 1b49666cd5 Merge branch 'master' into worktree-slash-split 2026-07-27 08:54:56 +08:00
imccyu ecdaf0dc24 test(web): connect a Workspace in the e2e boot path and refresh goldens
The startup-selection flow leaves a fresh world (no Workspace) in the
locked view state, so every e2e scenario that types into the composer
now connects one first via the shared connectFreshWorkspace helper
(hero picker create-by-name dialog; the default 'workspace' name keeps
the session-header cwd assertions intact).

Golden refreshes carry the current composer chrome: the plan/model
control seats are empty until their owning plugins register (the seats
shipped without occupants on this branch), the sidebar shows the
connected workspace group pre-send, and the bash details material
renders Input/code/Output as separate nodes. The cancel scenario polls
the frozen-partial swap instead of counting synchronously — the abort
frame reaches the browser over SSE after the host settles.
2026-07-27 08:25:00 +08:00
imccyu 250e2415ae test(web): re-anchor snapshot suites to the startup-selection boot flow
The startup Workspace auto-selection (wired in cd8ed43e4) changed the
boot landing: with any Workspace present the client connects its blank
session directly instead of resting in the locked view state.

- workspace-flow: New Session now reuses the blank session in place
  (no locked interlude); the failed-attach scenario asserts the actual
  recovery semantics — the host publishes the session before rejecting
  attachment, so the next connect reuses it into the hero (connect
  failures log to console, there is no view-state alert surface); the
  rejected-prompt scenario anchors on the sidebar New Session row since
  a send attempt leaves the hero for the engaging retry chrome.
- slash-flow: drop the stale i18n PLUGINS row (the package is locale).
2026-07-27 07:48:07 +08:00
imccyu cbe8735d7c feat(web): wire startup Workspace selection and sync docs
- Mount WorkspacesService.startInitialSelection in the runtime apply (the
  one-shot baseline follower shipped in 98633b5aa without a caller): a
  restored current session wins, an explicit clear stays cleared, a failed
  connect retries on the next baseline projection.
- Cover the policy in client-apply and the assembled workspace-flow
  snapshot; startup now lands in the recent Workspace's blank session, so
  the draft-carry scenario starts from the hero directly.
- Bring docs along: startup-selection paragraphs in the session-scope RFC
  note (both languages), bilingual README pairs for the four new client
  packages, doc-graph regeneration with client-declared events exempt from
  the dispatcher requirement (client dispatch sites are structurally
  invisible to the host-side ts.Program), and pairing re-records.
2026-07-27 06:24:09 +08:00
imccyu d1e43fcd8c style: typed queries in slash-flow snapshot, widen chat-apply key union 2026-07-27 04:13:05 +08:00
imccyu f6396f2573 style: fix lint across client packages
eslint --fix autofixes plus manual repairs: max-len line splits
(fake-api handlers, notifier/slots JSDoc, spec signatures), charAt over
non-null-asserted indexing in slash detect/menu cores, Array.from for
code-point capping, typeof assertions for unbound-method in specs,
generic getByRole for the send-button cast, effect disposer void-wrap in
command register, and dropped unused type imports.
2026-07-27 04:13:00 +08:00
imccyu a27be43ac1 feat: slash system / input service / agent scope 2026-07-27 03:28:39 +08:00
Tianyi Cui 72fa31b745 Merge remote-tracking branch 'origin/master' into worktree/packed-chunks-default-rfc 2026-07-27 00:29:58 +08:00
Tianyi Cui d2b15e71df Merge pull request #662 from deepseek-harness/code-mode-ui/shiki
feat(web): shiki syntax highlighting for code surfaces
2026-07-27 00:00:23 +08:00
Tianyi Cui a20cf88928 test(session): canonicalize fixtures as packed rows 2026-07-26 23:45:14 +08:00
Tianyi Cui 4ff496c65c feat(session): default JSONL writes to packed rows 2026-07-26 23:44:52 +08:00
Tianyi Cui e2882f486b fix(review): harden web replay verification
Validate replay sidecars and cross-copy failure facts, make browser console tripwires and macOS temp paths deterministic, and wait for asynchronous TUI resume details. Keep the owning docs, translations, and generated catalog aligned.
2026-07-26 22:38:33 +08:00
Tianyi Cui 430f6441c8 Merge branch 'code-mode-ui/shiki' into code-mode-ui/trajectory-spans 2026-07-26 21:52:41 +08:00
Tianyi Cui 426da32217 Merge branch 'code-mode-ui/dispatch-spill' into code-mode-ui/shiki 2026-07-26 21:44:17 +08:00
Tianyi Cui 709ac6b531 Merge branch 'master' into web-e2e-interactions 2026-07-26 21:29:34 +08:00
Tianyi Cui 0ba1e8320e Merge branch 'master' into web-e2e-interactions 2026-07-26 21:23:10 +08:00
Tianyi Cui fc38f8c813 Merge branch 'master' into code-mode-ui/live-parallel 2026-07-26 21:04:03 +08:00
Tianyi Cui 1dfb38a0b2 Merge remote-tracking branch 'origin/master' into web-e2e-interactions 2026-07-26 20:59:25 +08:00
Tianyi Cui aecc16f2d8 test(web): type the Code Mode fixture group row 2026-07-26 20:53:26 +08:00
Tianyi Cui 8847fb7bd4 Merge branch 'master' into code-mode-ui/web-ui-v1 2026-07-26 20:51:41 +08:00
Tianyi Cui 3698715d88 Merge remote-tracking branch 'origin/master' into web-e2e-interactions 2026-07-26 20:39:20 +08:00
Tianyi Cui f8342b0a8e test(web): cover the settings surface and workspace management
Two new keyless scenarios for the functionality master gained since this
lane's base (#644 websettings, #643 workspace browser rework), both zero
model calls:

- settings-chrome: the modal shell (sidebar-foot trigger aria states,
  role=dialog, aria-current section switch to the deliberately empty
  Models, Escape + close-button paths, dialog aria golden); the Appearance
  row as the REAL theme gesture — retiring lifecycle-chrome's
  TODO(web-theme-gesture): clicking 深色 runs aria-pressed -> persisted
  dsh.theme -> body[data-ds-dark-theme] -> alias-token flip, survives
  reload, and 'system' follows the emulated OS scheme both ways; the
  Language row switches the settings-scoped copy to English (dsh.locale
  persisted, survives reload) and restores zh. Intentional reloads tear
  the SSE stream, so the spec drains exactly its own reconnect warnings —
  the tripwire still fails on unexpected connection loss.
- workspace-management: create-by-name twice through the region-header
  dialog (host-durable via ctx.workspace.list()); rename end to end —
  hover-revealed row menu (the button is display:none until the row
  hovers), duplicate-name pre-check (inline role=alert + disabled primary
  before any wire call), then workspace.rename through the real RPC,
  row update, host durability, reload survival; the flat 'In one list'
  view (section label flips, group headers drop, dsh.workspace.view
  persists across reload, grouped restored); the session hover card
  (dwell to open, closes on pointer leave). The one session row reuses
  seeded-history's committed seed — no new recording. Deliberately not
  driven: the inert menu rows and drag reorder (deferred in the note
  with re-entry triggers).

Agent Note gains scenarios 8-9 and the drag-reorder deferred item in both
languages; llm-replay README's zh side catches up with the { patches }
paragraph; pairings re-recorded.
2026-07-26 20:38:36 +08:00
Tianyi Cui a0672e8ab3 Merge branch 'master' into code-mode-ui/host-foundation 2026-07-26 20:07:43 +08:00
Chinesezjc 9ef532ea34 Merge remote-tracking branch 'origin/master' into fix/intent-draft-sync-echo 2026-07-26 15:22:07 +08:00
Tianyi Cui 1998915630 test(web): refresh the hero golden for the localized settings label
The websettings merge (#644) localized the sidebar foot to 设置; the
lifecycle-chrome hero golden pinned the old English label. Keyless
DSH_SNAPSHOT=refresh rewrite; full lane green twice after.
2026-07-26 14:40:54 +08:00
Tianyi Cui 835156b038 fix(ui-trajectory): timing provenance on sub-span lanes; assembled snapshot for both views
Responding to ds-review-bot on #664:

- SubSpanLane gains a 'timing' discriminant (measured | running | unknown).
  A settle-only replay entry (callTime null, start outside the window) was
  previously indistinguishable from a measured 0 ms span; it now renders
  hollow with a 'duration unknown' hover title, and durationMs stays null
  for anything unmeasured. Pairs with the client-runtime fix that stopped
  fabricating callTime = settle time (826c3696a on the live-parallel PR).

- The built-client Code Mode fixture snapshot now switches to the
  Trajectory and Waterfall tabs and pins the assembled rendering: three
  Sub cells with real +0.8s durations and three measured lanes with their
  hover titles — product-visible coverage through the real bundle graph,
  not just package-level jsdom fixtures.

Agent Note (both languages) updated for the timing contract; pairing
re-recorded.
2026-07-26 14:20:59 +08:00
Tianyi Cui f936fea4a1 Merge remote-tracking branch 'origin/master' into web-e2e-interactions 2026-07-26 14:19:06 +08:00
Tianyi Cui f1b7d52a77 fix review findings: hostile code accessor + swallowed teardown
Both ds-review-bot findings were real:

- markLlmAdapterFailure's carried-facts cross-check read error.code
  directly; a foreign Error with a valid own failure payload but a
  throwing code accessor would replace the original adapter error with
  the accessor exception, breaking the error-identity guarantee. The
  read now goes through foreignErrorCode(), which contains the trap and
  falls back to the normalized snapshot (test: hostile code accessor
  beside a valid failure payload -> original identity kept, UNKNOWN
  facts).
- live-interactions' afterEach caught scaffold.close() into undefined,
  silently disabling ReplayHandle.assertConsumed() — the fixture-drift
  tripwire — and hiding cleanup defects. Teardown now runs every step,
  collects failures, and rethrows (AggregateError when several).
2026-07-26 14:12:52 +08:00
imccyu 0fe9ff888d Merge origin/master into worktree-websettings
ui-sidebar shell landed on master (#643: geometry-only shell, the
browsing region moved to the sidebar.workspaces slot); this branch's
sidebar.settings foot seat re-applies on top — the hardcoded foot row
becomes the seat, the seat rides the railIn crossfade, and the shell
spec's renderSlot stub key-splits region vs settings.
2026-07-26 13:15:57 +08:00
Tianyi Cui 4b06e9df99 Merge remote-tracking branch 'origin/master' into web-e2e-interactions 2026-07-26 13:08:39 +08:00
Tianyi Cui ea1d8d06b3 test(web): pin every scenario end-state with an aria golden
Every spec now commits at least one golden and the interactive ones one
per distinct end-state (nine new .expected.md):

- live-interactions: cancel.expected.md (frozen partial + 已停止 marker),
  error-auth.expected.md (the prompt bubble alone — the committed artifact
  of the web-error-surface gap, the diff that flips when error rendering
  lands), retry.expected.md (indistinguishable from a clean completion —
  retries are deliberately invisible in the transcript).
- question-composer: answered.expected.md (the question resolved into its
  tool round trip plus the final reply, takeover gone) beside the existing
  waiting-state golden.
- steering: mid-steer.expected.md pins the accepted-but-INVISIBLE state
  (the loop drains steering only at the step boundary, so no interjection
  bubble exists while the question still blocks — if the client ever
  renders pending steers eagerly, this golden flips first) and
  settled.expected.md the badged bubble plus obeying reply.
- navigation-panes: waterfall.expected.md and details-open.expected.md
  (tool-name header, Input args, Output result) beside the trajectory one.
- lifecycle-chrome: reloaded.expected.md — rendering the same settled
  transcript from persistence alone IS the recovery claim.

Fixture inventories extended to the new closed sets; the Agent Note's
expected-outputs policy updated in both languages (per-end-state goldens
for interactive scenarios), pairing re-recorded.
2026-07-26 12:45:44 +08:00
imccyu a04a223bab refactor(gui): copy-free settings shell; ui-settings-general owns ownerless copy
The shell is now a pure composition face: no dictionaries, no locale
dependency, and three new chrome content seats (settings.trigger /
settings.header / settings.close) whose slot content also carries the
accessible names (trigger text, dialog aria-labelledby, visually hidden
close label). ui-settings-general returns as the owner of copy that
belongs to no single feature: chrome content, the General section with
its item slot, and the settings dictionaries. Slot types split homes —
trigger/header/close/section live in the shell contract; the
settings.general.item entry moves to the locale package (the common
dependency of every item registrant), with ui-theme consuming it
through a re-export seam; the verbatim duplicate merges are gone and
the dependency graph is a clean DAG.
2026-07-26 12:40:17 +08:00
Tianyi Cui 202ef2fc89 Merge branch 'code-mode-ui/dispatch-spill' into code-mode-ui/shiki 2026-07-26 12:30:26 +08:00
Tianyi Cui 6333b51f2f Merge branch 'code-mode-ui/web-ui-v1' into code-mode-ui/live-parallel 2026-07-26 12:30:03 +08:00
Tianyi Cui a786911293 fix: address review — non-vacuous error assertion, package contracts
ds-review-bot findings: the browser e2e's error-state check now requires
at least one error sub-row (was >= 0); the runtime README documents the
codeDispatches snapshot contract and the ui-conversation README the code
variant + nested sub-row semantics.
2026-07-26 10:36:35 +08:00
Tianyi Cui bb3dc50a4b feat(web): shiki syntax highlighting for code surfaces
One highlighter for the client: a synchronous fine-grained shiki core
(JS regex engine, no WASM) in ui-primitives with an explicit grammar
allowlist (typescript, shellscript, json — aliases resolve, unknown
languages take a geometry-identical plain arm). The shared CodeBlock
component owns both arms; markdown fences, the run_code expanded
program body (typescript), and the details panel Input (json) all
route through it. Token colors live in a new ui-theme shiki.css sheet
as --shiki-* custom properties (light/dark blocks), wired through the
shell's base.css chain — tokens-only styling holds; shiki's generated
span tree is the sanctioned innerHTML path (static output, no user
HTML). jsdom specs pin token spans, aliases, both fallbacks, and the
fence route; the built-bundle snapshot asserts the highlighted program
under the code row.
2026-07-26 09:52:37 +08:00
Tianyi Cui cbb5fc7a51 test(web): lifecycle & chrome scenarios — workspace flow, reload recovery, dark mode
One tiny recorded text turn drives three whole-page concerns:

- workspace flow over the real wire: the empty-state hero's first send
  materializes a real Workspace + Session (the jsdom workspace-flow suite
  pins this state machine over the fixture client; this scenario pins it
  through HTTP RPC + SSE + the gateway). Durable proof: the session
  header's cwd is the create-by-name target <workspaceRoot>/workspace.
  Adds the hero waiting-state aria golden.
- reload recovery: collapse the sidebar (persisted dsh.layout.panels),
  page.reload, and the surface comes back whole from persistence alone —
  layout collapsed, selection restored (dsh.sessions.current), the
  recorded turn re-rendered from session.history with zero model calls
  (the drained replay cursor makes any stray request fail loud at close).
- dark mode: no product control flips the theme yet, so the scenario
  drives the ThemeService's entire DOM contract — body[data-ds-dark-theme]
  — and pins the shipped cascade: the alias token flips, a painted surface
  repaints, and removing the attribute restores the light sample exactly.
  TODO(web-theme-gesture) upgrades to a real settings control; no theme
  golden per the lane's scope ruling (aria is color-blind).

Agent Note scenario list extended in both languages; pairing re-recorded.
2026-07-26 06:08:08 +08:00
Tianyi Cui 8a79679489 feat(tools): live dispatch lifecycle + native-contract parallel sub-calls in Code Mode
The bridge replaces its serialization queue with a pool that reuses the
native concurrency contract: submissions classify through
registry.executionMode (fail-closed isConcurrencySafe), start strictly in
submission order, overlap up to the validated maxParallelSubCalls config
(default 10; 1 restores serial), and exclusive calls drain the pool, run
alone, and bar later calls. Each started sub-call logs a
tool/code-dispatch-start event at pool entry; the existing
tool/code-dispatch settles the pair (started ⇔ settles exactly once;
abandoned queued calls log neither). SDK prompt guidance now states the
true Promise.all contract — re-recorded across every code/both-mode
snapshot (plus the stale cordis-dynamic-toolchain fixture gaining the
required description arg).

Client: CodeSubCall widens to RunningToolCall | ToolResultNode — starts
land the running shape (rows wear the native running ring), settles
replace in place preserving start order, callTime pairs to the start
time. Fixture emits start/settle pairs; jsdom pins the running sub-row;
runtime specs pin in-place settlement and out-of-order completion.
2026-07-26 06:02:36 +08:00
Tianyi Cui 4964e9c729 test(web): navigation & panes scenarios over one rich seeded session
One two-turn seed (turn 1: bash + two parallel reads in a single assistant
message; turn 2: a markdown-heavy reply) rendered cold through the
seeded-history pattern — zero model calls — serving four surfaces:

- sidebar search: client-side title filter; asserted only after the durable
  title lands with the attach baseline (a cold SessionSummary carries no
  title — search matches the displayTitle the user sees). Negative query
  empties the tree, positive narrows to the match + its force-expanded
  group, clear restores.
- Trajectory tab: turn sections, the step group's tool mix ('bash read×2'),
  and a view-area aria golden.
- Waterfall tab: span stats header + one lane per span. The P-I fold counts
  a turn-0 prologue span (only assistant/steering nodes carry a turn
  number) — pinned as-is; real spans are P-III per the view's ledger.
- details column: the bash toolview row routes click to openDetails;
  open/closed is asserted on the frame's data-details-collapsed attribute
  because close collapses the grid column to width 0 without unmounting
  the subtree (hidden, not absent, is the contract).

Agent Note scenario list extended in both languages; pairing re-recorded.
2026-07-26 05:15:47 +08:00
Tianyi Cui 07770f90f9 Merge remote-tracking branch 'origin/master' into web-e2e-interactions
# Conflicts:
#	.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml
#	.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md
#	.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md
#	apps/web/tests/scaffold.ts
2026-07-26 04:10:37 +08:00
Tianyi Cui 13f7c62318 feat(web): render Code Mode sub-calls as native rows nested under the run_code row
The client indexes tool/code-dispatch events into
ConversationSnapshot.codeDispatches (parent callId -> ToolResultNode-shaped
sub-calls; live mux and history replay build the identical index). ChatView
renders each run_code parent as the new code variant (description summary,
program as the expanded monospace body) with its sub-dispatches as
always-visible indented rows — every sub-row dispatches through the SAME
keyed conversation.chat.toolview hole with the same GenericToolCard
fallback, so custom registrations (bash sample) take over sub-rows exactly
as top-level rows. The details panel resolves sub-callIds to full logged
args and complete output through the native path.

Evidence: fixture turn 64 + built-bundle jsdom snapshot, real-machinery
jsdom suites (nesting, error state, details, running parent, reference
stability), and a recorded code-mode browser e2e round (keyless replay +
aria golden). Scaffold gains a toolsMode patch knob.
2026-07-26 04:02:38 +08:00
Tianyi Cui 04b7f517ae test(web): live-turn interaction scenarios — cancel, error, retry, question composer, steering
Five browser e2e scenarios over the existing keyless lane, one recorded
base fixture per spec family:

- live-interactions: one tool-free recorded turn + per-run override
  sidecars authored in the spec (content single-sourced from the fixture
  via deriveReplayScript, minted into a spec-owned temp dir). Cancel uses
  a hang patch with a readyFile marker — the marker proves the stream is
  parked mid-turn before the Stop click, so mid-stream cancellation is
  deterministic by construction (turn/end 'aborted', composer re-enabled).
  AUTH pins the non-retryable path: turn/end 'error', zero llm/retry
  events, composer recovers; FIXME(web-error-surface) marks the found
  product gap (no error copy renders — the client consumes no agent/error
  frames and a pre-chunk failure freezes no partial). SERVER retry appends
  the fixture's own success after an injected throw and proves llm-retry
  end-to-end in the browser via the durable llm/retry record.
- question-composer: the shipped ask_user_question takeover blocks the
  turn mid-step on the real userInteraction seam; the test answers through
  the composer (the one sanctioned model-content-reactive drive step: the
  turn cannot complete without it) and the tool result carries the answer.
  Adds the composer waiting-state aria golden.
- steering: steers mid-turn while the composer blocks the step (the
  deterministic mid-turn window). The steer rides the real wire
  (session.prompt mode:'steer' POSTed from the page; the locked composer
  has no steering gesture yet — TODO(web-steer-composer)); downstream is
  all product: gateway -> Agent.steer -> step-boundary drain -> durable
  steering/message -> SSE -> badged interjection bubble. Record mode
  rejects a fixture whose live reply ignored the steer.

Scaffold gains the replayOverride passthrough; specs register in both
tsconfig planes (client exclude, host include).
2026-07-26 03:36:55 +08:00
imccyu 23a60ade67 refactor(gui): features register their own settings surfaces
Settings collaboration direction (recorded in the note): the shell only
provides composition faces — feature plugins register themselves. The
General section moves into the ui-settings shell (order 0, skeleton
rows) and declares the settings.general.item list slot; locale registers
the Language row and ui-theme the Appearance row (each with its own
store mirror, dictionaries, and ledger-judged deferral); the
ui-settings-general package is gone. ui-settings-models becomes
ui-models — a feature package that contributes its Settings section
rather than a settings-owned satellite. The item-slot SlotMap entry is
authored in the ui-settings contract and repeated verbatim in
locale/ui-theme (reference-cycle avoidance; declaration merging keeps
the copies identical).
2026-07-26 02:51:36 +08:00
Tianyi Cui d60dea9f55 feat(tools): run_code description param + native-parity dispatch logging + web code-mode seam
run_code gains a required bash-style description parameter: presentCall
titles the card with it and moves the program to rawInput, so every
surface gets a readable label. tool/code-dispatch now logs each
sub-call's complete content/isError (the tool/result vocabulary),
replacing the bounded resultSummary and deleting the summarize/cwd
machinery — a UI renders sub-calls through the identical path as native
results. The dsh config tree mounts the worker code runtime and reads
DSH_TOOLS_MODE (temporary seam until per-session mode selection lands).

Session format stays v0 (pre-release churn). Code-mode ACP/TUI fixtures
re-recorded; TUI presenter pin refreshed; catalogs regenerated. Keyless
web smoke pins the code-mode wire contract (tools=[run_code] + SDK
prompt section).
2026-07-26 02:43:34 +08:00
imccyu 6cc68baddf Merge remote-tracking branch 'origin/master' into worktree-ws-more 2026-07-26 02:26:27 +08:00
Chinesezjc 50cf90bf6e fix(web): flush the intent draft into the list snapshot in the same tick
The hero composer renders the frontend Session Intent's retained prompt
from the sessions list snapshot, but updateIntent only reached that
snapshot through the intent watch's microtask-deferred markDirty. React
therefore rolled the controlled textarea back during the change tick,
which corrupted IME composition (Pinyin "nihao" committed fragments
like "nnini hni hani hao你好") and jumped the caret on plain typing.

SessionManager.updateIntent now calls notifyNow after
updatePendingPrompt, per the Notifier channel rule for direct echoes of
user gestures. The workspace-flow snapshot helper asserts the same-tick
echo instead of waiting for it, and a runtime unit test pins the
contract at the manager seam.
2026-07-26 02:21:58 +08:00
imccyu ad419065a0 test(web): stop pre-clicking the fixture group in the title snapshot
The Intent's current-group effect already expands the target workspace;
with intent no longer forcing expansion, the header click collapsed it.
2026-07-26 02:10:59 +08:00
Tianyi Cui f1577d620e test(web): harden replay scaffold lifecycle 2026-07-26 00:33:46 +08:00
imccyu 6e721b9fdd feat(gui): settings panel with locale and theme preferences
Add the browser Settings surface as slot-composed plugins over new
preference services:

- Rename dsh-client-i18n to dsh-client-locale (locale is the domain
  name); LocaleService adds getLocale()/setLocale(id), immutable
  snapshots, a locale/change event, and dsh.locale persistence.
- ThemeService owns the light/dark/system preference (default system),
  resolves system via prefers-color-scheme, publishes theme/change
  snapshots, persists dsh.theme, and no longer touches the DOM;
  ui-layout's ThemePresenter applies resolved snapshots
  (body[data-ds-dark-theme] + alias tokens) and cleans up on dispose.
- ui-sidebar drops the phase-1 settings dropdown/modal; the foot renders
  the new sidebar.settings slot with the column state.
- New ui-settings shell occupies sidebar.settings: foot trigger row and
  the centered 1080x700 panel (figma 501:29947) with 24% mask, close
  button / mask click / Escape all closing, and a 188px nav projected
  from the settings.section list slot it declares. Nav labels are
  registrant-localized; sections re-register on locale change, so the
  ledger version is the shell's only subscription.
- ui-settings-general registers the General section: Permission and
  Tool Call skeletons, live Language (locale menu) and Appearance
  (Light/Dark/System cubes following the persisted preference); its
  slot store mirrors both service snapshots via apply-side listeners.
- ui-settings-models registers the Models nav entry with an empty
  content column.
- Portaled menus pin z-index above modal overlays (a menu anchored
  inside the settings dialog rendered underneath it and was
  unclickable).
- theme/data/list-pen icons in ui-primitives; settings copy ships as
  zh/en dictionaries; fixture manifests gain the settings rows.
2026-07-26 00:28:43 +08:00
Tianyi Cui ee25ccf38b Merge remote-tracking branch 'origin/master' into web-e2e-lane
# Conflicts:
#	vitest.web.config.ts
2026-07-25 18:19:22 +08:00