fix(desktop): align onboarding with current work detail modes
This commit is contained in:
1 parent
372c311511
commit
ad86306feb
19 files changed
+66
-63
No files matched your search
@@ -322,7 +322,7 @@ describe.skipIf(MODE === 'record')('web e2e: App-only desktop onboarding', () =>
|
||||
await page.getByRole('button', { name: '继续', exact: true }).click()
|
||||
await page.getByRole('radiogroup').waitFor()
|
||||
await page.getByRole('radio', { name: /聚焦结果/ }).focus()
|
||||
for (const [name, process] of [['关键细节', 'detailed'], ['完整过程', 'expanded']] as const) {
|
||||
for (const [name, process] of [['关键细节', 'standard'], ['完整过程', 'detailed']] as const) {
|
||||
const [saved] = await Promise.all([
|
||||
page.waitForResponse('**/api/settings/mutate'),
|
||||
page.keyboard.press('ArrowRight'),
|
||||
@@ -341,12 +341,12 @@ describe.skipIf(MODE === 'record')('web e2e: App-only desktop onboarding', () =>
|
||||
await snapshot('process')
|
||||
await page.getByRole('button', { name: '进入应用', exact: true }).click()
|
||||
await page.locator('[data-desktop-onboarding]').waitFor({ state: 'detached' })
|
||||
expect(scaffold.ctx.settings.describe().find(row => row.ns === NS)?.value).toMatchObject({ step: 'done', process: 'expanded', usage: 'detailed', developerTools: true })
|
||||
expect(scaffold.ctx.settings.describe().find(row => row.ns === 'ui-chat')?.value).toMatchObject({ transcriptView: 'expanded', performanceUsage: 'detailed' })
|
||||
expect(scaffold.ctx.settings.describe().find(row => row.ns === NS)?.value).toMatchObject({ step: 'done', process: 'detailed', usage: 'detailed', developerTools: true })
|
||||
expect(scaffold.ctx.settings.describe().find(row => row.ns === 'ui-chat')?.value).toMatchObject({ transcriptView: 'detailed', performanceUsage: 'detailed' })
|
||||
expect(scaffold.ctx.settings.describe().find(row => row.ns === 'ui-settings')?.value).toMatchObject({ enabled: true })
|
||||
const persisted = await readFile(join(scaffold.harnessHome, 'profiles', 'scaffold', 'cordis.patch.yml'), 'utf8')
|
||||
expect(persisted).toContain('id: ui-settings-account')
|
||||
expect(persisted).toContain('expanded')
|
||||
expect(persisted).toContain('detailed')
|
||||
const warningsAfter = tripwire.warnings.length
|
||||
await page.reload({ waitUntil: 'load' })
|
||||
acknowledgeReloadConnectionLoss(tripwire, warningsAfter)
|
||||
@@ -409,8 +409,8 @@ describe.skipIf(MODE === 'record')('web e2e: App-only desktop onboarding', () =>
|
||||
expect(await response.json()).toMatchObject({ result: { ok: true } })
|
||||
expect((scaffold.ctx.settings.describe().find(row => row.ns === NS)?.value as { step: string }).step).toBe('done')
|
||||
expect(await page.locator('[data-desktop-onboarding]').count()).toBe(0)
|
||||
expect(scaffold.ctx.settings.describe().find(row => row.ns === NS)?.value).toMatchObject({ completion: 'api-key', process: 'detailed', usage: 'detailed', developerTools: true })
|
||||
expect(scaffold.ctx.settings.describe().find(row => row.ns === 'ui-chat')?.value).toMatchObject({ transcriptView: 'detailed', performanceUsage: 'detailed' })
|
||||
expect(scaffold.ctx.settings.describe().find(row => row.ns === NS)?.value).toMatchObject({ completion: 'api-key', process: 'standard', usage: 'detailed', developerTools: true })
|
||||
expect(scaffold.ctx.settings.describe().find(row => row.ns === 'ui-chat')?.value).toMatchObject({ transcriptView: 'standard', performanceUsage: 'detailed' })
|
||||
expect(scaffold.ctx.settings.describe().find(row => row.ns === 'ui-settings')?.value).toMatchObject({ enabled: true })
|
||||
await scaffold.ctx.credentials.modifyRecord(credentialKey('deepseek-account-platform', 'default'), async () => ({
|
||||
kind: 'grant', payload: { version: 1, issuer: origin, token: 'onboarding-fixture-token' },
|
||||
|
||||
@@ -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 docs/config-catalog.md
|
||||
config-catalog.md: 89487825486c21a2a2fee4c27d09c29aa0349b88
|
||||
config-catalog.zh.md: a9c2ddb817bc9e56cb73272c5e8d4d4f082d6881
|
||||
config-catalog.md: 775f247cfd07ab724e14103a7059a07dda3d76c3
|
||||
config-catalog.zh.md: bcc1d074883f628a72c8093521ba92025d9ead0e
|
||||
@@ -537,7 +537,7 @@ export type OnboardingStep = 'welcome' | 'credit' | 'purpose' | 'process' | 'don
|
||||
export type OnboardingPurpose = 'office' | 'development' | 'both'
|
||||
|
||||
/** Work-detail mode applied to Chat when onboarding completes. */
|
||||
export type OnboardingProcess = 'compact' | 'detailed' | 'expanded'
|
||||
export type OnboardingProcess = 'compact' | 'standard' | 'detailed'
|
||||
```
|
||||
|
||||
Depends on: `Volatile` (`@deepseek-ai/cordis`)
|
||||
|
||||
@@ -539,7 +539,7 @@ export type OnboardingStep = 'welcome' | 'credit' | 'purpose' | 'process' | 'don
|
||||
export type OnboardingPurpose = 'office' | 'development' | 'both'
|
||||
|
||||
/** Work-detail mode applied to Chat when onboarding completes. */
|
||||
export type OnboardingProcess = 'compact' | 'detailed' | 'expanded'
|
||||
export type OnboardingProcess = 'compact' | 'standard' | 'detailed'
|
||||
```
|
||||
|
||||
依赖: `Volatile` (`@deepseek-ai/cordis`)
|
||||
|
||||
@@ -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 docs/event-producer-consumer.md
|
||||
event-producer-consumer.md: ff9fc19ab364e61e3dd1847531bfac3377676a3b
|
||||
event-producer-consumer.zh.md: f1f8d5cd80dd20037d0caba3b3061924c280fb55
|
||||
event-producer-consumer.md: 085f6505ad2575557f94415be1f7dbe22d746f95
|
||||
event-producer-consumer.zh.md: e49bfd16741cb576967bf2b1fc7801f9f95f8258
|
||||
@@ -40,8 +40,8 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
| `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:374`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` |
|
||||
| `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:102`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization), [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform), `remotes` |
|
||||
| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:90`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`credentials`](../packages/credentials/credentials), `remotes` |
|
||||
| `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:59`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` |
|
||||
| `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:55`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` |
|
||||
| `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:90`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` |
|
||||
| `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:86`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` |
|
||||
| `deepseek-account/signed-out` | `emit` | [`packages/credentials/deepseek-account/src/index.ts:12`](../packages/credentials/deepseek-account/src/index.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | [`deepseek-account`](../packages/credentials/deepseek-account) |
|
||||
| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace), `workspace-controller` |
|
||||
| `feedback/committed` | `parallel` | [`packages/feedback/message-feedback/src/index.ts:58`](../packages/feedback/message-feedback/src/index.ts) | [`message-feedback`](../packages/feedback/message-feedback) (`parallel`) | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) |
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
| `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:374`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` |
|
||||
| `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:102`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization), [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform), `remotes` |
|
||||
| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:90`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`credentials`](../packages/credentials/credentials), `remotes` |
|
||||
| `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:59`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` |
|
||||
| `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:55`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` |
|
||||
| `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:90`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` |
|
||||
| `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:86`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` |
|
||||
| `deepseek-account/signed-out` | `emit` | [`packages/credentials/deepseek-account/src/index.ts:12`](../packages/credentials/deepseek-account/src/index.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | [`deepseek-account`](../packages/credentials/deepseek-account) |
|
||||
| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace), `workspace-controller` |
|
||||
| `feedback/committed` | `parallel` | [`packages/feedback/message-feedback/src/index.ts:58`](../packages/feedback/message-feedback/src/index.ts) | [`message-feedback`](../packages/feedback/message-feedback) (`parallel`) | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) |
|
||||
|
||||
@@ -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/client/ui-settings-account/README.md
|
||||
README.md: c5806362bf674c9f7a74077854917a36c4b1b840
|
||||
README.zh.md: 2fd4847cc78edd1ade055eae27bdcacb7a4c3ae0
|
||||
README.md: ee76735cb8f5c1da82d287190540677758c81edd
|
||||
README.zh.md: 77f6357ec72c8c843ae47ce24e3fa6de0fe9fcae
|
||||
@@ -45,16 +45,16 @@ Sign out first queries running account-token tasks and opens a confirmation dial
|
||||
<a id="desktop-onboarding"></a>
|
||||
### Desktop onboarding
|
||||
|
||||
The Desktop preload marker enables the introduction after account credentials are stored. Progress belongs to the local Host settings document, not the Platform account: unfinished steps resume after restart, and completing or skipping the flow prevents another run on that installation. Browser clients do not mount it. API-key presence comes from the native welcome backend through the boolean-only Desktop preload bridge, using the same credential discovery as login. A signed-out installation with a configured model API key bypasses the pages and applies detailed process, detailed usage, and enabled developer tools.
|
||||
The Desktop preload marker enables the introduction after account credentials are stored. Progress belongs to the local Host settings document, not the Platform account: unfinished steps resume after restart, and completing or skipping the flow prevents another run on that installation. Browser clients do not mount it. API-key presence comes from the native welcome backend through the boolean-only Desktop preload bridge, using the same credential discovery as login. A signed-out installation with a configured model API key bypasses the pages and applies standard process, detailed usage, and enabled developer tools.
|
||||
|
||||
The welcome page requires Get started. The credit page always appears. A positive balance confirmed at first entry selects Continue as the primary action and Add credits as the secondary action. This choice stays fixed until the onboarding controller is recreated and is never persisted; late balance results cannot change the buttons. Only a confirmed absence of positive credit adds a confirmation before continuing without recharge; pending or failed queries do not block continuing. Purpose descriptions remain unchanged when selected. Returning from the native top-up view keeps the credit page and refreshes account details in the background, whether loading or payment succeeded. Next confirms only a known zero balance; Skip uses the credit warning on the credit page and the setup warning on the question pages. Office-only use completes with compact presentation; development or both purposes also ask for process detail. Back preserves selections, and explicit skip from any supported step applies detailed process, compact performance/usage, and disabled developer tools. This flow creates no workspace or demonstration task.
|
||||
The welcome page requires Get started. The credit page always appears. A positive balance confirmed at first entry selects Continue as the primary action and Add credits as the secondary action. This choice stays fixed until the onboarding controller is recreated and is never persisted; late balance results cannot change the buttons. Only a confirmed absence of positive credit adds a confirmation before continuing without recharge; pending or failed queries do not block continuing. Purpose descriptions remain unchanged when selected. Returning from the native top-up view keeps the credit page and refreshes account details in the background, whether loading or payment succeeded. Next confirms only a known zero balance; Skip uses the credit warning on the credit page and the setup warning on the question pages. Office-only use completes with compact presentation; development or both purposes also ask for process detail. Back preserves selections, and explicit skip from any supported step applies standard process, compact performance/usage, and disabled developer tools. This flow creates no workspace or demonstration task.
|
||||
|
||||
<a id="understand-the-implementation"></a>
|
||||
## Understand the implementation
|
||||
|
||||
The Account section appears first in Settings only while signed in; it is hidden before account state loads and after sign-out. The plugin owns one Host snapshot stream shared through framework hooks by settings.section and settings.launcher. The launcher opens Settings and offers Sign out only while an account credential is stored. Platform failures leave the menu available for retry. It maintains no independent credential state, so no invariant companion is published.
|
||||
|
||||
The desktop controller writes progress to `ui-settings-account` through Host settings. Completion first applies `ui-chat.transcriptView` and `ui-chat.performanceUsage` together, then calls the shared developer-tools preference to persist `ui-settings.enabled`, and finally saves the done marker; a rejected write keeps the flow available for retry. Successful completion reveals the workspace through a 180ms fade; choices and ordinary navigation preview immediately while writes run in order. A failed final queued write restores saved progress and allows retry. Completion waits for queued choices and retains the page until preferences persist; reduced motion skips the fade. Compact, detailed, and expanded map directly to the corresponding Chat work-detail modes. Complete Figma illustration layers are bundled as transparent, palette-compressed 3× PNG assets; welcome layers are merged with local sidebar blur; recharge uses a 70% opaque foreground window with local blur of the covered illustration, and headings use the bundled Montserrat brand font; English headings and descriptions use Light (300), except the DeepSeek Harness brand within headings uses Medium (500); card titles retain Regular (400) when selected. English footer navigation uses Light (300). Confirmed onboarding sets a 960px minimum window width and enlarges narrower windows; completion releases the minimum without restoring the previous size. Loading or already-completed onboarding does not resize the window. Empty footer navigation space passes pointer events through to the primary action. Cards exclude native window dragging and own the keyboard focus outline for their checkbox. The account package owns its onboarding overlay and control geometry. Each step and the confirmation dialog has a separate component; the flow coordinates navigation, transitions, and recharge. The overlay hides the workspace while initial account and progress state loads. Question headings and cards follow the Figma positions at 1440 × 920 and 960 × 600, independently of their action buttons. On macOS, content clears the body portal’s inherited no-drag region and a first-child drag band leaves an 8px native resize margin; interactive controls and later dialogs exclude their regions, and a native Platform page disables the underlying onboarding band. The upper workspace popup illustration has an opaque background in both languages and themes; the underlying sidebar omits its selected-row highlight. Welcome keeps its illustration at its original size, repositions and clips the artwork in narrow windows, and reveals the complete illustration on wide windows. The [onboarding decision](../../../.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.md) owns persistence and presentation trade-offs.
|
||||
The desktop controller writes progress to `ui-settings-account` through Host settings. Completion first applies `ui-chat.transcriptView` and `ui-chat.performanceUsage` together, then calls the shared developer-tools preference to persist `ui-settings.enabled`, and finally saves the done marker; a rejected write keeps the flow available for retry. Successful completion reveals the workspace through a 180ms fade; choices and ordinary navigation preview immediately while writes run in order. A failed final queued write restores saved progress and allows retry. Completion waits for queued choices and retains the page until preferences persist; reduced motion skips the fade. Onboarding choices compact, standard, and detailed apply directly to the corresponding Chat work-detail modes. API-key entry and explicit skip select standard; onboarding does not select verbose. Complete Figma illustration layers are bundled as transparent, palette-compressed 3× PNG assets; welcome layers are merged with local sidebar blur; recharge uses a 70% opaque foreground window with local blur of the covered illustration, and headings use the bundled Montserrat brand font; English headings and descriptions use Light (300), except the DeepSeek Harness brand within headings uses Medium (500); card titles retain Regular (400) when selected. English footer navigation uses Light (300). Confirmed onboarding sets a 960px minimum window width and enlarges narrower windows; completion releases the minimum without restoring the previous size. Loading or already-completed onboarding does not resize the window. Empty footer navigation space passes pointer events through to the primary action. Cards exclude native window dragging and own the keyboard focus outline for their checkbox. The account package owns its onboarding overlay and control geometry. Each step and the confirmation dialog has a separate component; the flow coordinates navigation, transitions, and recharge. The overlay hides the workspace while initial account and progress state loads. Question headings and cards follow the Figma positions at 1440 × 920 and 960 × 600, independently of their action buttons. On macOS, content clears the body portal’s inherited no-drag region and a first-child drag band leaves an 8px native resize margin; interactive controls and later dialogs exclude their regions, and a native Platform page disables the underlying onboarding band. The upper workspace popup illustration has an opaque background in both languages and themes; the underlying sidebar omits its selected-row highlight. Welcome keeps its illustration at its original size, repositions and clips the artwork in narrow windows, and reveals the complete illustration on wide windows. The [onboarding decision](../../../.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.md) owns persistence and presentation trade-offs.
|
||||
|
||||
<a id="further-exploration"></a>
|
||||
## Further Exploration
|
||||
|
||||
@@ -45,16 +45,16 @@ Desktop 的用量和充值操作在 48px 返回栏下方打开隔离的原生 Pl
|
||||
<a id="desktop-onboarding"></a>
|
||||
### Desktop 引导
|
||||
|
||||
Desktop preload 标记在账号凭证已存储后启用引导。进度属于本地 Host 设置文档,不属于 Platform 账号:重启后恢复未完成的步骤,完成或跳过后不再于当前安装中重复运行。浏览器客户端不挂载此流程。API Key 是否存在由原生欢迎后端通过仅返回布尔值的 Desktop preload 桥接提供,与登录共用凭证发现逻辑。未登录但已配置模型 API Key 的安装会绕过这些页面,应用详细过程、详细用量和开启开发者工具的配置。
|
||||
Desktop preload 标记在账号凭证已存储后启用引导。进度属于本地 Host 设置文档,不属于 Platform 账号:重启后恢复未完成的步骤,完成或跳过后不再于当前安装中重复运行。浏览器客户端不挂载此流程。API Key 是否存在由原生欢迎后端通过仅返回布尔值的 Desktop preload 桥接提供,与登录共用凭证发现逻辑。未登录但已配置模型 API Key 的安装会绕过这些页面,应用标准过程、详细用量和开启开发者工具的配置。
|
||||
|
||||
欢迎页必须点击“开始设置”。额度页始终展示。首次进入时若已确认有正余额,则主操作为“继续”,次操作为“去充值”。该选择在引导控制器重建前保持固定且不持久化,后到的余额结果不会改变按钮。仅在确认不存在正余额时,继续而不充值才需确认;查询中或失败时不阻止继续。用途卡片选中后描述不变。从原生充值视图返回后保留额度页,并在后台刷新账号资料,不受页面加载或支付结果影响。“下一步”仅在确认零余额时提示;额度页的“跳过”使用充值提示,答题页使用设置提示。仅选办公用途时以简洁展示完成;选择开发或两种用途时还会询问过程详情。上一步保留已有选择,任意可跳过步骤均采用详细工作过程、简洁性能与用量以及关闭开发者工具的默认值。此流程不创建工作区或示例任务。
|
||||
欢迎页必须点击“开始设置”。额度页始终展示。首次进入时若已确认有正余额,则主操作为“继续”,次操作为“去充值”。该选择在引导控制器重建前保持固定且不持久化,后到的余额结果不会改变按钮。仅在确认不存在正余额时,继续而不充值才需确认;查询中或失败时不阻止继续。用途卡片选中后描述不变。从原生充值视图返回后保留额度页,并在后台刷新账号资料,不受页面加载或支付结果影响。“下一步”仅在确认零余额时提示;额度页的“跳过”使用充值提示,答题页使用设置提示。仅选办公用途时以简洁展示完成;选择开发或两种用途时还会询问过程详情。上一步保留已有选择,任意可跳过步骤均采用标准工作过程、简洁性能与用量以及关闭开发者工具的默认值。此流程不创建工作区或示例任务。
|
||||
|
||||
<a id="understand-the-implementation"></a>
|
||||
## 理解实现
|
||||
|
||||
仅登录后在设置导航首位显示账号页;账号状态加载完成前以及退出登录后隐藏该页。 插件持有一条 Host 快照流,通过框架 hook 供 settings.section 和 settings.launcher 共享。入口菜单可打开设置,并仅在已存储账号凭证时提供退出登录。Platform 请求失败时保留菜单,以供重试。插件不维护独立凭证状态,因此不发布 invariant。
|
||||
|
||||
桌面控制器通过 Host 设置将进度写入 `ui-settings-account`。完成时先一并应用 `ui-chat.transcriptView` 与 `ui-chat.performanceUsage`,再通过共享开发者工具偏好保存 `ui-settings.enabled`,最后保存完成标记;写入被拒绝时保留引导以供重试。成功完成后通过 180ms 淡出显露工作区;选择和普通翻页立即预览,写入在后台按顺序执行。队列最后一次写入失败时恢复已保存进度,并允许重试。完成操作会等待选择队列及偏好保存成功,再关闭页面;减少动态效果偏好会跳过淡出。compact、detailed 和 expanded 直接对应 Chat 的三个工作过程模式。完整 Figma 示意图层以透明、调色板压缩的 3× PNG 资源打包;欢迎页图层合并并烘焙侧栏局部模糊;充值页前景窗口不透明度为 70%,仅烘焙其覆盖插图区域的模糊,标题使用已打包的 Montserrat 品牌字体,英文标题和描述使用 Light(300),标题中的 DeepSeek Harness 使用 Medium(500);卡片标题选中前后均使用 Regular(400)。英文底部导航使用 Light(300)。确认进入引导后,窗口最小宽度设为 960px,较窄窗口自动放大;完成后解除宽度限制,但不恢复原尺寸。加载中或已完成引导的启动不改变窗口尺寸。底部导航空白区域透过鼠标事件,避免遮挡主操作。卡片区域不参与原生窗口拖动,复选框的键盘焦点仅在卡片外框展示。账号包负责引导覆盖层和控件尺寸样式。每个步骤和确认弹窗各有独立组件,流程组件协调导航、切换动画和充值。初始账号与进度加载期间,覆盖层隐藏工作区。答题标题和卡片独立于操作按钮,位置对齐 Figma 的 1440 × 920 和 960 × 600 断点。macOS 的内容层清除 body 浮层继承的 no-drag,位于内容之前的拖拽层保留 8px 原生缩放边缘;交互控件和后续弹窗排除自身区域;原生平台页面打开时禁用底层引导拖拽层。上方工作区弹窗插画在两种语言和主题下均使用不透明背景,下层侧边栏不绘制选中行高亮。欢迎页保持示意图的原始尺寸,在窄窗口内调整插图位置并裁切,在宽窗口内展示完整插图。[引导决策](../../../.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.zh.md)记录持久化与展示方面的取舍。
|
||||
桌面控制器通过 Host 设置将进度写入 `ui-settings-account`。完成时先一并应用 `ui-chat.transcriptView` 与 `ui-chat.performanceUsage`,再通过共享开发者工具偏好保存 `ui-settings.enabled`,最后保存完成标记;写入被拒绝时保留引导以供重试。成功完成后通过 180ms 淡出显露工作区;选择和普通翻页立即预览,写入在后台按顺序执行。队列最后一次写入失败时恢复已保存进度,并允许重试。完成操作会等待选择队列及偏好保存成功,再关闭页面;减少动态效果偏好会跳过淡出。onboarding 选项 compact、standard、detailed 直接应用到对应的 Chat 工作过程模式。API Key 进入与显式跳过均采用 standard;onboarding 不选择 verbose。完整 Figma 示意图层以透明、调色板压缩的 3× PNG 资源打包;欢迎页图层合并并烘焙侧栏局部模糊;充值页前景窗口不透明度为 70%,仅烘焙其覆盖插图区域的模糊,标题使用已打包的 Montserrat 品牌字体,英文标题和描述使用 Light(300),标题中的 DeepSeek Harness 使用 Medium(500);卡片标题选中前后均使用 Regular(400)。英文底部导航使用 Light(300)。确认进入引导后,窗口最小宽度设为 960px,较窄窗口自动放大;完成后解除宽度限制,但不恢复原尺寸。加载中或已完成引导的启动不改变窗口尺寸。底部导航空白区域透过鼠标事件,避免遮挡主操作。卡片区域不参与原生窗口拖动,复选框的键盘焦点仅在卡片外框展示。账号包负责引导覆盖层和控件尺寸样式。每个步骤和确认弹窗各有独立组件,流程组件协调导航、切换动画和充值。初始账号与进度加载期间,覆盖层隐藏工作区。答题标题和卡片独立于操作按钮,位置对齐 Figma 的 1440 × 920 和 960 × 600 断点。macOS 的内容层清除 body 浮层继承的 no-drag,位于内容之前的拖拽层保留 8px 原生缩放边缘;交互控件和后续弹窗排除自身区域;原生平台页面打开时禁用底层引导拖拽层。上方工作区弹窗插画在两种语言和主题下均使用不透明背景,下层侧边栏不绘制选中行高亮。欢迎页保持示意图的原始尺寸,在窄窗口内调整插图位置并裁切,在宽窗口内展示完整插图。[引导决策](../../../.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.zh.md)记录持久化与展示方面的取舍。
|
||||
|
||||
<a id="further-exploration"></a>
|
||||
## 深入探索
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
.selected::after, .selected:hover::after { opacity: 1; }
|
||||
.selected::after { animation: borderSweep 14s linear infinite; }
|
||||
.officeCard, .card[data-process='compact'] { --card-border-stops: var(--dsw-gradient-onboarding-violet-stops); }
|
||||
.card[data-process='expanded'] { --card-border-stops: var(--dsw-gradient-onboarding-cyan-stops); }
|
||||
.card[data-process='detailed'] { --card-border-stops: var(--dsw-gradient-onboarding-cyan-stops); }
|
||||
|
||||
@property --dsh-onboarding-border-angle {
|
||||
syntax: '<angle>';
|
||||
|
||||
@@ -10,8 +10,8 @@ import css from './DesktopOnboarding.module.css'
|
||||
|
||||
const options = [
|
||||
{ value: 'compact', titleKey: 'onboardingCompact', descriptionKey: 'onboardingCompactDescription', icon: compactIcon, size: 40 },
|
||||
{ value: 'detailed', titleKey: 'onboardingDetailed', descriptionKey: 'onboardingDetailedDescription', icon: detailedIcon, size: 36 },
|
||||
{ value: 'expanded', titleKey: 'onboardingExpanded', descriptionKey: 'onboardingExpandedDescription', icon: expandedIcon, size: 36 },
|
||||
{ value: 'standard', titleKey: 'onboardingDetailed', descriptionKey: 'onboardingDetailedDescription', icon: detailedIcon, size: 36 },
|
||||
{ value: 'detailed', titleKey: 'onboardingExpanded', descriptionKey: 'onboardingExpandedDescription', icon: expandedIcon, size: 36 },
|
||||
] as const
|
||||
|
||||
/** @param props - selected detail, localized copy and persistence callbacks. @returns the process step. */
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/** Desktop account settings registration and reconnecting Remote subscription. */
|
||||
import type { ChatSettings } from '@deepseek-ai/dsh-client-ui-chat/src/chat-settings.ts'
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
||||
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
||||
@@ -145,7 +146,7 @@ export function apply(ctx: Context): void {
|
||||
if ('dshDesktop' in globalThis) {
|
||||
const controller = new DesktopOnboardingController(
|
||||
ctx.configForms.get<OnboardingSettings>(DESKTOP_ONBOARDING_NAMESPACE),
|
||||
ctx.configForms.get<{ transcriptView: 'compact' | 'detailed' | 'expanded'; performanceUsage: 'compact' | 'detailed' }>('ui-chat'),
|
||||
ctx.configForms.get<Pick<ChatSettings, 'transcriptView' | 'performanceUsage'>>('ui-chat'),
|
||||
enabled => ctx.configForms.developerTools.setEnabled(enabled),
|
||||
operations.hooks.account,
|
||||
readOnboardingApiKeyPresence,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/** Resumable desktop onboarding and preference application over Host settings. */
|
||||
import type { ChatSettings } from '@deepseek-ai/dsh-client-ui-chat/src/chat-settings.ts'
|
||||
import { createSnapshotStore, shallowEqual } from '@deepseek-ai/dsh-client-store'
|
||||
import { hasOnboardingCredit } from './onboarding-balance.ts'
|
||||
import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
@@ -40,7 +41,7 @@ export class DesktopOnboardingController {
|
||||
*/
|
||||
constructor(
|
||||
private readonly progress: ConfigForm<OnboardingSettings>,
|
||||
private readonly chat: ConfigForm<{ transcriptView: 'compact' | 'detailed' | 'expanded'; performanceUsage: 'compact' | 'detailed' }>,
|
||||
private readonly chat: ConfigForm<Pick<ChatSettings, 'transcriptView' | 'performanceUsage'>>,
|
||||
private readonly setDeveloperTools: (enabled: boolean) => Promise<void>,
|
||||
private readonly account: HostObservable<AccountSnapshot>,
|
||||
private readonly readApiKeyPresence: () => Promise<boolean>,
|
||||
@@ -118,7 +119,7 @@ export class DesktopOnboardingController {
|
||||
if (development && current.process === null) return false
|
||||
return this.save({
|
||||
...current, step: 'done', completion: reason,
|
||||
process: office ? 'compact' : reason === 'skipped' ? 'detailed' : current.process,
|
||||
process: office ? 'compact' : reason === 'skipped' ? 'standard' : current.process,
|
||||
usage: development ? 'detailed' : 'compact', developerTools: development,
|
||||
}, true)
|
||||
}
|
||||
@@ -226,7 +227,7 @@ export class DesktopOnboardingController {
|
||||
if (done || this.saving || this.completing || this.draft !== undefined || unavailable || host.status !== 'ready' || this.chat.getSnapshot().status !== 'ready'
|
||||
|| account?.status !== 'signed-out') return
|
||||
if (this.hasApiKey === true && this.error === null) {
|
||||
void this.save({ ...progress, step: 'done', process: 'detailed', usage: 'detailed', developerTools: true, completion: 'api-key' }, true)
|
||||
void this.save({ ...progress, step: 'done', process: 'standard', usage: 'detailed', developerTools: true, completion: 'api-key' }, true)
|
||||
} else if (this.hasApiKey === undefined && !this.checkingKeys) {
|
||||
this.checkingKeys = true
|
||||
const generation = this.credentialGeneration
|
||||
|
||||
@@ -9,7 +9,7 @@ export type OnboardingStep = 'welcome' | 'credit' | 'purpose' | 'process' | 'don
|
||||
/** Work scenarios offered by the desktop introduction. */
|
||||
export type OnboardingPurpose = 'office' | 'development' | 'both'
|
||||
/** Work-detail mode applied to Chat when onboarding completes. */
|
||||
export type OnboardingProcess = 'compact' | 'detailed' | 'expanded'
|
||||
export type OnboardingProcess = 'compact' | 'standard' | 'detailed'
|
||||
/** One installation's resumable choices and terminal outcome. */
|
||||
export interface OnboardingProgress {
|
||||
version: 1
|
||||
@@ -33,7 +33,7 @@ export const OnboardingSettingsFields = {
|
||||
version: z.const(1).default(1),
|
||||
step: z.union(['welcome', 'credit', 'purpose', 'process', 'done']).default('welcome'),
|
||||
purpose: z.union(['office', 'development', 'both', z.const(null)]),
|
||||
process: z.union(['compact', 'detailed', 'expanded', z.const(null)]),
|
||||
process: z.union(['compact', 'standard', 'detailed', z.const(null)]),
|
||||
completion: z.union(['completed', 'skipped', 'api-key', z.const(null)]),
|
||||
usage: z.union(['compact', 'detailed']).default('compact'),
|
||||
developerTools: z.boolean().default(false),
|
||||
|
||||
@@ -259,5 +259,5 @@ it('applies API-key defaults through the desktop slot and shared configuration o
|
||||
await vi.waitFor(() => { expect(injected.hooks.onboarding.getSnapshot().progress.step).toBe('done') })
|
||||
expect(hasApiKey).toHaveBeenCalled()
|
||||
expect(values['ui-settings']).toEqual({ enabled: true })
|
||||
expect(values['ui-chat']).toMatchObject({ transcriptView: 'detailed' })
|
||||
expect(values['ui-chat']).toMatchObject({ transcriptView: 'standard' })
|
||||
}, 60_000)
|
||||
@@ -102,7 +102,7 @@ it('supports radio arrow keys and requires a process choice before finishing', a
|
||||
const operations = mount('process')
|
||||
expect(screen.queryByRole('button', { name: zh.onboardingEnter })).toBeNull()
|
||||
await act(async () => { fireEvent.keyDown(screen.getAllByRole('radio')[0]!, { key: 'ArrowRight' }) })
|
||||
expect(operations.update).toHaveBeenCalledWith({ process: 'detailed' })
|
||||
expect(operations.update).toHaveBeenCalledWith({ process: 'standard' })
|
||||
await act(async () => { fireEvent.click(screen.getByRole('button', { name: zh.onboardingEnter })) })
|
||||
expect(operations.complete).toHaveBeenCalledWith('completed')
|
||||
})
|
||||
@@ -231,7 +231,7 @@ it('supports both radio arrow directions, wraps around, and ignores other keys o
|
||||
const radios = screen.getAllByRole('radio')
|
||||
for (const key of ['ArrowLeft', 'ArrowUp', 'ArrowDown', 'ArrowRight']) {
|
||||
await act(async () => { fireEvent.keyDown(radios[0]!, { key }) })
|
||||
expect(h.update).toHaveBeenLastCalledWith({ process: key === 'ArrowLeft' || key === 'ArrowUp' ? 'expanded' : 'detailed' })
|
||||
expect(h.update).toHaveBeenLastCalledWith({ process: key === 'ArrowLeft' || key === 'ArrowUp' ? 'detailed' : 'standard' })
|
||||
}
|
||||
const count = h.update.mock.calls.length
|
||||
fireEvent.keyDown(radios[0]!, { key: 'a' })
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
{
|
||||
"purpose": "office",
|
||||
"process": "detailed",
|
||||
"process": "standard",
|
||||
"chat": {
|
||||
"transcriptView": "compact",
|
||||
"performanceUsage": "compact"
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
{
|
||||
"purpose": "office",
|
||||
"process": "expanded",
|
||||
"process": "detailed",
|
||||
"chat": {
|
||||
"transcriptView": "compact",
|
||||
"performanceUsage": "compact"
|
||||
@@ -45,9 +45,9 @@
|
||||
},
|
||||
{
|
||||
"purpose": "development",
|
||||
"process": "detailed",
|
||||
"process": "standard",
|
||||
"chat": {
|
||||
"transcriptView": "detailed",
|
||||
"transcriptView": "standard",
|
||||
"performanceUsage": "detailed"
|
||||
},
|
||||
"developer": {
|
||||
@@ -56,9 +56,9 @@
|
||||
},
|
||||
{
|
||||
"purpose": "development",
|
||||
"process": "expanded",
|
||||
"process": "detailed",
|
||||
"chat": {
|
||||
"transcriptView": "expanded",
|
||||
"transcriptView": "detailed",
|
||||
"performanceUsage": "detailed"
|
||||
},
|
||||
"developer": {
|
||||
@@ -78,9 +78,9 @@
|
||||
},
|
||||
{
|
||||
"purpose": "both",
|
||||
"process": "detailed",
|
||||
"process": "standard",
|
||||
"chat": {
|
||||
"transcriptView": "detailed",
|
||||
"transcriptView": "standard",
|
||||
"performanceUsage": "detailed"
|
||||
},
|
||||
"developer": {
|
||||
@@ -89,9 +89,9 @@
|
||||
},
|
||||
{
|
||||
"purpose": "both",
|
||||
"process": "expanded",
|
||||
"process": "detailed",
|
||||
"chat": {
|
||||
"transcriptView": "expanded",
|
||||
"transcriptView": "detailed",
|
||||
"performanceUsage": "detailed"
|
||||
},
|
||||
"developer": {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ChatSettings } from '@deepseek-ai/dsh-client-ui-chat/src/chat-settings.ts'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { createSnapshotStore } from '@deepseek-ai/dsh-client-store'
|
||||
@@ -64,8 +65,8 @@ async function harness(options: {
|
||||
namespace: DESKTOP_ONBOARDING_NAMESPACE,
|
||||
decode: value => OnboardingSettingsSchema(value as OnboardingSettings),
|
||||
}, mirror, 'host', schema)
|
||||
const chat = new ConfigFormController<{ transcriptView: 'compact' | 'detailed' | 'expanded'; performanceUsage: 'compact' | 'detailed' }>(ctx, {
|
||||
namespace: 'ui-chat', decode: value => value as { transcriptView: 'compact' | 'detailed' | 'expanded'; performanceUsage: 'compact' | 'detailed' },
|
||||
const chat = new ConfigFormController<Pick<ChatSettings, 'transcriptView' | 'performanceUsage'>>(ctx, {
|
||||
namespace: 'ui-chat', decode: value => value as Pick<ChatSettings, 'transcriptView' | 'performanceUsage'>,
|
||||
}, mirror, 'host', schema)
|
||||
const developerScope = new ConfigFormController<{ enabled: boolean }>(ctx, {
|
||||
namespace: 'ui-settings', decode: value => value as { enabled: boolean },
|
||||
@@ -204,9 +205,9 @@ describe('desktop onboarding lifecycle', () => {
|
||||
it('applies API-key defaults once and does not rerun onboarding after a later account login', async () => {
|
||||
const h = await harness({ status: 'signed-out', keys: async () => true })
|
||||
await vi.waitFor(() => { expect(h.controller.state.getSnapshot().progress.step).toBe('done') })
|
||||
expect(h.data['ui-chat']).toEqual({ transcriptView: 'detailed', performanceUsage: 'detailed' })
|
||||
expect(h.data['ui-chat']).toEqual({ transcriptView: 'standard', performanceUsage: 'detailed' })
|
||||
expect(h.data['ui-settings']).toEqual({ enabled: true })
|
||||
expect(h.data[DESKTOP_ONBOARDING_NAMESPACE]).toMatchObject({ completion: 'api-key', process: 'detailed', usage: 'detailed', developerTools: true })
|
||||
expect(h.data[DESKTOP_ONBOARDING_NAMESPACE]).toMatchObject({ completion: 'api-key', process: 'standard', usage: 'detailed', developerTools: true })
|
||||
h.account.set(accountState('credential-stored'))
|
||||
h.controller.invalidateCredentials()
|
||||
expect(h.controller.state.getSnapshot().visible).toBe(false)
|
||||
@@ -274,8 +275,8 @@ describe('desktop onboarding lifecycle', () => {
|
||||
it.each([
|
||||
['office', null, 'compact', 'compact', false],
|
||||
['development', 'compact', 'compact', 'detailed', true],
|
||||
['development', 'detailed', 'detailed', 'detailed', true],
|
||||
['both', 'expanded', 'expanded', 'detailed', true],
|
||||
['development', 'standard', 'standard', 'detailed', true],
|
||||
['both', 'detailed', 'detailed', 'detailed', true],
|
||||
] as const)('applies %s / %s choices through the preference owners', async (purpose, process, transcriptView, usage, developerTools) => {
|
||||
const h = await harness({ status: 'credential-stored', progress: { step: 'process', purpose, process } })
|
||||
expect(await h.controller.complete('completed')).toBe(true)
|
||||
@@ -285,11 +286,11 @@ describe('desktop onboarding lifecycle', () => {
|
||||
})
|
||||
|
||||
it.each([
|
||||
['purpose', 'detailed'], ['credit', 'detailed'], ['process', 'detailed'],
|
||||
['purpose', 'standard'], ['credit', 'standard'], ['process', 'standard'],
|
||||
] as const)('applies the shared skip defaults from %s', async (step, process) => {
|
||||
const h = await harness({ status: 'credential-stored', progress: { step, purpose: 'office', process: 'expanded' } })
|
||||
const h = await harness({ status: 'credential-stored', progress: { step, purpose: 'office', process: 'detailed' } })
|
||||
expect(await h.controller.complete('skipped')).toBe(true)
|
||||
expect(h.data['ui-chat']).toEqual({ transcriptView: 'detailed', performanceUsage: 'compact' })
|
||||
expect(h.data['ui-chat']).toEqual({ transcriptView: 'standard', performanceUsage: 'compact' })
|
||||
expect(h.data['ui-settings']).toEqual({ enabled: false })
|
||||
expect(h.data[DESKTOP_ONBOARDING_NAMESPACE]).toMatchObject({ step: 'done', completion: 'skipped', process, usage: 'compact', developerTools: false })
|
||||
})
|
||||
@@ -297,7 +298,7 @@ describe('desktop onboarding lifecycle', () => {
|
||||
it('records the preference defaults applied before onboarding completion', async () => {
|
||||
const results = []
|
||||
for (const purpose of ['office', 'development', 'both'] as const) {
|
||||
for (const process of ['compact', 'detailed', 'expanded'] as const) {
|
||||
for (const process of ['compact', 'standard', 'detailed'] as const) {
|
||||
const h = await harness({ status: 'credential-stored', progress: { step: 'process', purpose, process } })
|
||||
expect(await h.controller.complete('completed')).toBe(true)
|
||||
results.push({ purpose, process, chat: h.data['ui-chat'], developer: h.data['ui-settings'] })
|
||||
@@ -324,7 +325,7 @@ describe('desktop onboarding lifecycle', () => {
|
||||
|
||||
it.each(['ui-chat', 'ui-settings', DESKTOP_ONBOARDING_NAMESPACE])('retries completion after %s persistence fails', async (namespace) => {
|
||||
const h = await harness({
|
||||
status: 'credential-stored', progress: { step: 'process', purpose: 'development', process: 'expanded' },
|
||||
status: 'credential-stored', progress: { step: 'process', purpose: 'development', process: 'detailed' },
|
||||
})
|
||||
h.refuse(namespace)
|
||||
expect(await h.controller.complete('completed')).toBe(false)
|
||||
@@ -335,11 +336,11 @@ describe('desktop onboarding lifecycle', () => {
|
||||
expect(h.mutate).toHaveBeenCalledTimes(writesBefore)
|
||||
expect(await h.controller.retry()).toBe(true)
|
||||
expect(h.mutate.mock.calls.slice(writesBefore).map(([ns]) => ns)).toEqual(['ui-chat', 'ui-settings', DESKTOP_ONBOARDING_NAMESPACE])
|
||||
expect(h.data['ui-chat']).toEqual({ transcriptView: 'expanded', performanceUsage: 'detailed' })
|
||||
expect(h.data['ui-chat']).toEqual({ transcriptView: 'detailed', performanceUsage: 'detailed' })
|
||||
expect(h.data['ui-settings']).toEqual({ enabled: true })
|
||||
expect(h.controller.state.getSnapshot()).toMatchObject({
|
||||
status: 'ready', visible: false,
|
||||
progress: { step: 'done', completion: 'completed', process: 'expanded', usage: 'detailed', developerTools: true },
|
||||
progress: { step: 'done', completion: 'completed', process: 'detailed', usage: 'detailed', developerTools: true },
|
||||
})
|
||||
expect(await h.controller.retry()).toBe(false)
|
||||
})
|
||||
@@ -376,7 +377,7 @@ describe('desktop onboarding lifecycle', () => {
|
||||
})
|
||||
|
||||
it('never records completion when Chat preference persistence fails', async () => {
|
||||
const h = await harness({ status: 'credential-stored', progress: { step: 'process', purpose: 'development', process: 'detailed' } })
|
||||
const h = await harness({ status: 'credential-stored', progress: { step: 'process', purpose: 'development', process: 'standard' } })
|
||||
h.refuse('ui-chat')
|
||||
expect(await h.controller.complete('completed')).toBe(false)
|
||||
expect(h.data[DESKTOP_ONBOARDING_NAMESPACE]).toMatchObject({ step: 'process', completion: null })
|
||||
@@ -402,15 +403,15 @@ describe('desktop onboarding lifecycle', () => {
|
||||
const pending = Promise.withResolvers<undefined>()
|
||||
h.wait(pending.promise)
|
||||
const first = h.controller.update({ process: 'compact' })
|
||||
const second = h.controller.update({ process: 'expanded' })
|
||||
const second = h.controller.update({ process: 'detailed' })
|
||||
const completion = h.controller.complete('completed')
|
||||
expect(h.controller.state.getSnapshot().status).toBe('saving')
|
||||
expect(await h.controller.update({ process: 'detailed' })).toBe(false)
|
||||
expect(await h.controller.update({ process: 'standard' })).toBe(false)
|
||||
pending.resolve(undefined)
|
||||
expect(await first).toBe(true)
|
||||
expect(await second).toBe(true)
|
||||
expect(await completion).toBe(true)
|
||||
expect(h.data['ui-chat']).toMatchObject({ transcriptView: 'expanded' })
|
||||
expect(h.data['ui-chat']).toMatchObject({ transcriptView: 'detailed' })
|
||||
})
|
||||
|
||||
it('restores saved progress after queued refusal and retries the latest choice', async () => {
|
||||
|
||||
Reference in new issue
Block a user