-
-
- {renderSlot('settings.onboarding', {
- stepId: onboardingStep.id,
- complete: () => { completeOnboardingStep(onboardingStep.id) },
- openSection,
- }, { only: onboardingStep.id })}
-
-
- ), document.body)}
+ {/* The takeover chrome (OnboardingSurface: mask, opaque stage, `#root`
+ inert) lives inside the step component, wrapped around its visible
+ content — a step still deciding (private facts loading) renders
+ null, so nothing paints or blocks while it decides. */}
+ {onboardingStep !== undefined && renderSlot('settings.onboarding', {
+ stepId: onboardingStep.id,
+ complete: () => { completeOnboardingStep(onboardingStep.id) },
+ openSection,
+ }, { only: onboardingStep.id })}
>
)
}
diff --git a/packages/client/ui-settings/src/client/contract/slots.ts b/packages/client/ui-settings/src/client/contract/slots.ts
index e585fa5b6f..8516ce8860 100644
--- a/packages/client/ui-settings/src/client/contract/slots.ts
+++ b/packages/client/ui-settings/src/client/contract/slots.ts
@@ -57,7 +57,13 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
* Root-scoped onboarding steps contributed by settings features. The
* shell mounts one ordered step at a time; the active registrant either
* completes itself or keeps ownership until the user completes its sole
- * path. Registrants own readiness, copy, and dialog behavior.
+ * path. Registrants own readiness, copy, dialog behavior, AND the
+ * takeover chrome: a step wraps its visible content in the
+ * OnboardingSurface primitive (mask, opaque stage, `#root` inert) and
+ * renders null while its private facts are still loading — the shell
+ * paints no chrome of its own, so a mounted-but-deciding step shows and
+ * blocks nothing (the reload white-flash fix; a bare unwrapped step
+ * would render without mask or stage).
*/
'settings.onboarding': { kind: 'list'; scope: 'root'; owner: SettingsOnboardingOwnerProps }
}
diff --git a/packages/client/ui-settings/tests/settings-root.spec.tsx b/packages/client/ui-settings/tests/settings-root.spec.tsx
index 900c66d381..40b4dc29c2 100644
--- a/packages/client/ui-settings/tests/settings-root.spec.tsx
+++ b/packages/client/ui-settings/tests/settings-root.spec.tsx
@@ -204,14 +204,19 @@ describe('SettingsPanel navigation', () => {
expect(inactive).toHaveLength(0)
})
- it('makes the underlying application inert while onboarding owns the viewport', () => {
+ it('paints no takeover chrome of its own around the mounted step', () => {
+ // The chrome (mask, opaque stage, #root inert) belongs to the step via
+ // the OnboardingSurface primitive — a mounted-but-deciding step that
+ // renders null must show and block nothing (the reload white-flash fix;
+ // onboarding-surface.spec.tsx pins the primitive's half).
const appRoot = document.createElement('div')
appRoot.id = 'root'
document.body.append(appRoot)
const { view } = mount()
- expect(appRoot.inert).toBe(true)
+ expect(view.container.querySelector('[class*="onboarding"]')).toBeNull()
+ expect(document.body.querySelector('[class*="onboarding"]')).toBeNull()
+ expect(appRoot.inert).not.toBe(true)
view.unmount()
- expect(appRoot.inert).toBe(false)
appRoot.remove()
})
diff --git a/packages/context/workspace-context/README.i18n.yaml b/packages/context/workspace-context/README.i18n.yaml
index bd8c55d78c..753836e528 100644
--- a/packages/context/workspace-context/README.i18n.yaml
+++ b/packages/context/workspace-context/README.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/context/workspace-context/README.md
-README.md: 7add269c7a1b38e9624cf6d0368151661bc72a6b
-README.zh.md: 3456c5c36275e8e521b312b1b0b7e1dd102ca23c
+README.md: 82aee27a8fbd6a1ab0f0860226b081e28ba72e6e
+README.zh.md: 9d983f95f4e018cb8fe983d9862cf5f31f83c5f2
diff --git a/packages/context/workspace-context/README.md b/packages/context/workspace-context/README.md
index 7add269c7a..82aee27a8f 100644
--- a/packages/context/workspace-context/README.md
+++ b/packages/context/workspace-context/README.md
@@ -6,7 +6,7 @@ Per-session workspace instruction loading for `AGENTS.md`-compatible files. The
## Lifecycle
-The first eligible `agent/pre-step` of each live session composes the baseline. When the downstream decision enters a nonempty first-step batch, the plugin folds the baseline into that final batch right after the claimed prompt, so the direct prompt and the durable baseline enter step 1 and reach the first request together. A rejected or empty first-step decision leaves the baseline in the agent's `next-step` inbox for a later wakeup. The loader reads `$DSH_HOME/AGENTS.md` followed by, in each directory from the project root to `agent.session.header.cwd`, every existing base candidate and then every existing local-overlay candidate. Within one directory, candidates whose content is byte-identical after trimming leading and trailing whitespace collapse to the earliest candidate in configured order, so a `CLAUDE.md` that merely duplicates its sibling `AGENTS.md` is rendered once. If a previously queued workspace context is still pending, the plugin removes and replaces that exact inbox item instead of accumulating duplicates.
+The first eligible `agent/pre-step` of each live session composes the baseline. When the downstream decision enters a nonempty first-step batch, the plugin folds the baseline into that final batch right after the claimed prompt, so the direct prompt and the durable baseline enter step 1 and reach the first request together. A rejected or empty first-step decision leaves the baseline in the agent's `next-step` inbox for a later wakeup. The loader reads `$DSH_HOME/AGENTS.md` followed by, in each directory from the project root to `agent.session.header.cwd`, every existing base candidate and then every existing local-overlay candidate. Within one directory, candidates whose content is byte-identical after trimming leading and trailing whitespace collapse to the earliest candidate in configured order, so a `CLAUDE.md` that merely duplicates its sibling `AGENTS.md` is rendered once. If a previously queued workspace context is still pending, the plugin removes and replaces that exact inbox item instead of accumulating duplicates. A resumed session retains one compatible visible baseline and appends only current-file transitions; a changed discovery, precedence, project-root, or budget identity instead folds one explicitly superseding complete baseline into the entering batch.
The plugin also listens on `tools/post-execute` for successful first-party `read`, `write`, and `edit` calls. Each touch checks newly reached descendant scopes and every previously loaded scope. Each configured candidate name is an independent scope in its directory: a newly present file is attached through the result's `additionalContexts`; a changed file appends a replacement; a file that disappears or becomes a per-directory duplicate of an earlier candidate appends a removal notice. Native calls and Code Mode sub-dispatches share this path: `run_code` defers each nested context until its outer result, so the loop still appends updates after tool-call/result adjacency is complete. This follows structured filesystem activity rather than shell `cd`, because each local bash call starts a fresh shell and parsing arbitrary shell syntax would be unreliable.
@@ -48,11 +48,11 @@ The plugin owns the complete `