fix(ui): move the onboarding takeover chrome into the step

The settings shell painted the onboarding overlay (opaque stage, mask,
#root inert) the moment a step was registered and not locally completed,
while every step still had to load its private join before deciding
whether to show — rendering null could not suppress the shell-owned
chrome. Every reload on the hero therefore flashed a full-screen opaque
layer (white in the light palette) for one settings/credential RPC
round-trip after the session list turned ready.

The chrome now belongs to the step: a new zero-cordis OnboardingSurface
primitive (ui-primitives) renders the body-portaled overlay/mask/stage
verbatim from the former SettingsRoot stylesheet and holds #root inert
for exactly its own lifetime. WelcomeNotice and DeepSeekOnboardingDialog
wrap only their visible branch in it, so their existing null branches
paint and block nothing by construction. SettingsRoot keeps the
coordinator unchanged but renders the elected step bare, and the
settings.onboarding contract now names the surface wrap as the
registrant's obligation.

The onboarding e2e gains a held-join reload scenario pinning that a
configured world never mounts the takeover chrome or inerts the app.
This commit is contained in:
imccyu
2026-08-06 20:43:22 +08:00
parent 6a32047e77
commit 63b80956f2
20 changed files with 317 additions and 106 deletions

View File

@@ -4,7 +4,7 @@ English | [中文](README.zh.md)
Settings shell plugin: a pure composition face. It occupies `sidebar.settings` with the trigger chrome and modal settings panel, and declares the slots registrants fill: `settings.trigger` / `settings.header` / `settings.close` (chrome content), `settings.action` (ordered content-header actions), `settings.section` (one page per feature), and `settings.onboarding` (ordered feature-owned pages in a full-viewport stage). The shell ships no copy of its own — all text arrives from registrants (ui-settings-general owns chrome, General, and the product notice; features own their actions, sections, rows, and conditional onboarding pages). Nav labels may be locale-following thunks, so the nav projection resolves them through `resolveSlotLabel` and re-renders on the section ledger bump or the locale revision (an optional `ctx.get('locale')` read; no hard locale dependency).
The shell projects the onboarding ledger into ascending order and mounts exactly one page at a time in a body-level stage while marking the underlying app root inert. The active registrant receives its id, `complete()`, and an `openSection(id)` callback; completing or skipping transfers ownership to the next entry. Registrants own durable completion, capability readiness, copy, and mutations, so independently registered flows cannot stack and the shell does not become a second configuration fact source.
The shell projects the onboarding ledger into ascending order and mounts exactly one page at a time; the takeover chrome (body-level stage, mask, app-root `inert`) belongs to the step itself through ui-primitives' `OnboardingSurface`, so a mounted step still resolving its private facts renders null and neither paints nor blocks anything — the shell shows no empty stage while a step decides. The active registrant receives its id, `complete()`, and an `openSection(id)` callback; completing or skipping transfers ownership to the next entry. Registrants own durable completion, capability readiness, copy, mutations, and the surface wrap, so independently registered flows cannot stack and the shell does not become a second configuration fact source.
## Model Experience