feat(settings): replicate the WorkBuddy personalization panel and wire identity, tone and prompts (TODO 13)
Recover the WorkBuddy 5.8.3 个性化 page from REA evidence (PersonalizationPanel-D16rRGjy.css + tokens): 45 module rules plus scoped light/dark tokens, the five-card 1160px layout (identity card 638x100 with inline editors, reply preference card 638x204, three 638x264 long-text cards with 598x160 previews) and the 640x520 editor modal. Persist identity, tone, welcome tips, custom instructions, persona and long-term memory in config.json, expose personalization:get/update over IPC, and append the resulting personalization context to each turn's prompt for both the OMP and built-in kernels. Wire 欢迎语 to the streaming loading tips and keep 显示文件修改过程 visibly disabled because YCode has no incremental file-change renderer yet. Verify with scripts/verify-personalization-settings.cjs: 29 assertions covering geometry, tone options, switches, inline identity editing with blur-discard, editor modal save/cancel, config persistence, reopen, dark surfaces and no renderer errors. Add a pure prompt-builder regression and keep every existing settings regression green.
This commit is contained in:
1 parent
19460f0b13
commit
0dc088f1f7
54 files changed
+2038
-248
No files matched your search
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* TODO 13:个性化提示词拼装的离线回归。
|
||||
* 覆盖默认值不产生多余上下文、身份/风格/指令/人设/长期记忆分别进入提示词、超长截断与非法风格回退。
|
||||
*/
|
||||
import assert from 'node:assert/strict'
|
||||
import { buildPersonalizationPrompt, DEFAULT_PERSONA, normalizePersonalization } from '../src/shared/personalization'
|
||||
|
||||
const base = normalizePersonalization({})
|
||||
assert.equal(base.tone, 'default')
|
||||
assert.equal(base.loadingTips, true)
|
||||
assert.equal(base.showFileChanges, false)
|
||||
assert.equal(base.persona, DEFAULT_PERSONA)
|
||||
assert.equal(buildPersonalizationPrompt(base), '', '默认设置不应把原版人设注入每轮提示词')
|
||||
|
||||
const identity = buildPersonalizationPrompt(normalizePersonalization({ userCallName: ' 小明 ', aiName: ' YCode 助手 ' }))
|
||||
assert.match(identity, /^## 个性化偏好(用户设置,始终遵守)/)
|
||||
assert.ok(identity.includes('- 用户希望被称呼为:小明'))
|
||||
assert.ok(identity.includes('- 你的名字是:YCode 助手;自我介绍时使用这个名字。'))
|
||||
assert.ok(!identity.includes(DEFAULT_PERSONA), '默认人设不应进入提示词')
|
||||
|
||||
const tone = buildPersonalizationPrompt(normalizePersonalization({ tone: 'professional' }))
|
||||
assert.ok(tone.includes('- 回复风格:专业严谨——清晰、准确、值得信赖。'))
|
||||
|
||||
const custom = buildPersonalizationPrompt(normalizePersonalization({ customPrompt: '\n先给结论,再给依据。\n' }))
|
||||
assert.ok(custom.includes('### 用户指令\n先给结论,再给依据。'))
|
||||
|
||||
const persona = buildPersonalizationPrompt(normalizePersonalization({ persona: '你是一位严谨的代码审阅者。' }))
|
||||
assert.ok(persona.includes('### 人设设定\n你是一位严谨的代码审阅者。'))
|
||||
|
||||
const memory = buildPersonalizationPrompt(normalizePersonalization({ longTermMemory: '用户偏好 TypeScript。' }))
|
||||
assert.ok(memory.includes('### 长期记忆\n用户偏好 TypeScript。'))
|
||||
|
||||
const long = buildPersonalizationPrompt(normalizePersonalization({ customPrompt: 'x'.repeat(8100) }))
|
||||
assert.ok(long.includes('(内容过长,已截断)'))
|
||||
assert.ok(long.length < 8100, '超长字段应被截断')
|
||||
|
||||
const fallback = normalizePersonalization({ tone: 'not-a-tone' as never })
|
||||
assert.equal(fallback.tone, 'default')
|
||||
assert.equal(buildPersonalizationPrompt(fallback), '')
|
||||
|
||||
const tips = normalizePersonalization({ loadingTips: false, showFileChanges: true })
|
||||
assert.equal(tips.loadingTips, false)
|
||||
assert.equal(tips.showFileChanges, true)
|
||||
|
||||
console.log('personalization-prompt-regression: all checks passed')
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
".appearance-card--light",
|
||||
".appearance-card--dark"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "wait", "step_id": "dark-theme-stable-after-target-rediscovery", "duration_ms": 800, "frames": 4 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
".appearance-card--light",
|
||||
".appearance-card--dark"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "wait", "step_id": "light-theme-stable-after-target-rediscovery", "duration_ms": 800, "frames": 4 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".local-memory-edit-modal",
|
||||
"#settings-local-memory-body .general-settings-panel__section-title",
|
||||
"#settings-local-memory-body .general-settings-panel__section-action button",
|
||||
"#settings-local-memory-body .local-memory-long__preview"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "cancel-persona-editor-before-memory-test", "selector": ".local-memory-edit-modal .wb-modal__footer button", "index": 0, "frames": 0 },
|
||||
{ "kind": "wait", "step_id": "persona-editor-closed-for-memory-test", "duration_ms": 350, "frames": 0 },
|
||||
{ "kind": "click", "step_id": "open-long-memory-editor", "selector": "#settings-local-memory-body .general-settings-panel__section-action button", "index": 0, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "long-memory-editor-settled", "duration_ms": 350, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".local-memory-edit-modal",
|
||||
".local-memory-edit-modal .wb-modal__footer button",
|
||||
"#settings-local-memory-body .general-settings-panel__section-title"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "cancel-empty-long-memory-editor", "selector": ".local-memory-edit-modal .wb-modal__footer button", "index": 0, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "long-memory-editor-cancel-settled", "duration_ms": 350, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "return-panel-top-from-long-memory", "selector": "#settings-local-memory-body .general-settings-panel__section-title", "delta_y": -1200, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "personalization-top-confirmed-after-memory", "duration_ms": 200, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
".settings-navigation__item",
|
||||
".appearance-card--light",
|
||||
".appearance-card--dark"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "open-appearance-to-restore-light-by-index-3", "selector": ".settings-navigation__item", "index": 3, "frames": 0 },
|
||||
{ "kind": "wait", "step_id": "appearance-dark-before-restore-confirmed", "duration_ms": 450, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
".settings-navigation__item",
|
||||
".appearance-card--light",
|
||||
".appearance-card--dark"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "open-appearance-by-observed-nav-index-3", "selector": ".settings-navigation__item", "index": 3, "frames": 0 },
|
||||
{ "kind": "wait", "step_id": "appearance-page-confirmed-before-dark-theme", "duration_ms": 450, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
".settings-navigation__item",
|
||||
".general-settings-panel.personalization-panel",
|
||||
".local-memory-identity-card"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "open-personalization-in-dark-theme-by-observed-nav-index-5", "selector": ".settings-navigation__item", "index": 5, "frames": 0 },
|
||||
{ "kind": "wait", "step_id": "personalization-dark-navigation-confirmed", "duration_ms": 500, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
".settings-navigation__item",
|
||||
".general-settings-panel.personalization-panel",
|
||||
".local-memory-identity-card"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "return-to-personalization-in-light-theme-by-nav-index-5", "selector": ".settings-navigation__item", "index": 5, "frames": 0 },
|
||||
{ "kind": "wait", "step_id": "personalization-light-final-state-confirmed", "duration_ms": 500, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
"#settings-local-memory-soul .general-settings-panel__section-title",
|
||||
"#settings-local-memory-soul .general-settings-panel__section-action button",
|
||||
"#settings-local-memory-soul .local-memory-long__preview",
|
||||
".local-memory-edit-modal"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "scroll", "step_id": "scroll-persona-heading-to-bottom", "selector": "#settings-local-memory-soul .general-settings-panel__section-title", "delta_y": 300, "frames": 1 },
|
||||
{ "kind": "click", "step_id": "retry-persona-edit-button", "selector": "#settings-local-memory-soul .general-settings-panel__section-action button", "index": 0, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "persona-edit-button-retry-settled", "duration_ms": 350, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
"#settings-local-memory-soul .general-settings-panel__section-title",
|
||||
"#settings-local-memory-soul .general-settings-panel__section-action button",
|
||||
"#settings-local-memory-soul .local-memory-long__preview",
|
||||
".local-memory-edit-modal"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "scroll", "step_id": "scroll-to-persona-button", "selector": ".settings-modal__panel", "delta_y": 300, "frames": 0 },
|
||||
{ "kind": "scroll", "step_id": "scroll-persona-button-visible", "selector": "#settings-local-memory-soul .general-settings-panel__section-title", "delta_y": 300, "frames": 0 },
|
||||
{ "kind": "click", "step_id": "activate-persona-edit-button", "selector": "#settings-local-memory-soul .general-settings-panel__section-action button", "index": 0, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "persona-edit-button-settled", "duration_ms": 350, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-local-memory-soul .general-settings-panel__section-title",
|
||||
"#settings-local-memory-soul .general-settings-panel__section-action button",
|
||||
".local-memory-edit-modal .wb-modal__footer button"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "scroll", "step_id": "scroll-to-persona-editor", "selector": ".settings-modal__panel", "delta_y": 300, "frames": 0 },
|
||||
{ "kind": "scroll", "step_id": "scroll-persona-section-into-view", "selector": "#settings-local-memory-soul .general-settings-panel__section-title", "delta_y": 300, "frames": 0 },
|
||||
{ "kind": "click", "step_id": "open-persona-editor", "selector": "#settings-local-memory-soul .general-settings-panel__section-action button", "index": 0, "frames": 0 },
|
||||
{ "kind": "wait", "step_id": "persona-editor-open", "duration_ms": 350, "frames": 1 },
|
||||
{ "kind": "click", "step_id": "cancel-persona-editor-without-change", "selector": ".local-memory-edit-modal .wb-modal__footer button", "index": 0, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "persona-editor-cancelled", "duration_ms": 350, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "return-personalization-top-after-editor", "selector": "#settings-local-memory-soul .general-settings-panel__section-title", "delta_y": -1200, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "personalization-top-confirmed", "duration_ms": 200, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".local-memory-edit-modal",
|
||||
".local-memory-edit-modal .wb-modal__footer",
|
||||
".local-memory-edit-modal .wb-modal__footer button",
|
||||
"#settings-local-memory-soul .general-settings-panel__section-title"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "cancel-persona-editor", "selector": ".local-memory-edit-modal .wb-modal__footer button", "index": 0, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "persona-editor-cancel-settled", "duration_ms": 300, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "restore-personalization-panel-top", "selector": "#settings-local-memory-soul .general-settings-panel__section-title", "delta_y": -1200, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "personalization-panel-top-after-persona-editor", "duration_ms": 200, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-local-memory-soul .local-memory-long__preview",
|
||||
"#settings-local-memory-soul .general-settings-panel__section-action button"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "click-persona-preview-role-button", "selector": "#settings-local-memory-soul .local-memory-long__preview", "index": 0, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "persona-preview-click-settled", "duration_ms": 350, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-local-memory-ai-name",
|
||||
"#settings-local-memory-ai-name input",
|
||||
"#settings-local-memory-ai-name .local-memory-identity-row__actions"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "open-ai-name-inline-editor", "selector": "#settings-local-memory-ai-name .local-memory-pill", "index": 0, "frames": 1 },
|
||||
{ "kind": "text", "step_id": "enter-temporary-ai-name-draft", "selector": "#settings-local-memory-ai-name input", "text": "CodexTmp", "frames": 1 },
|
||||
{ "kind": "click", "step_id": "cancel-ai-name-draft", "selector": "#settings-local-memory-ai-name .local-memory-identity-row__actions button", "index": 0, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "confirm-ai-name-remains-original-empty", "duration_ms": 200, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".local-memory-edit-modal",
|
||||
".local-memory-edit-modal .wb-modal__footer",
|
||||
".local-memory-edit-modal .wb-modal__footer button"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "cancel-workbuddy-instructions-editor", "selector": ".local-memory-edit-modal .wb-modal__footer button", "index": 0, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-custom-prompt",
|
||||
"#settings-custom-prompt .general-settings-panel__section-action button"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "open-workbuddy-instructions-editor", "selector": "#settings-custom-prompt .general-settings-panel__section-action button", "index": 0, "frames": 1 },
|
||||
{ "kind": "key", "step_id": "close-workbuddy-instructions-editor-with-escape", "key": "Escape", "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".local-memory-edit-modal",
|
||||
".local-memory-edit-modal .local-memory-editor-host",
|
||||
".local-memory-edit-modal .wb-modal__footer button"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "open-empty-workbuddy-instructions-editor", "selector": "#settings-custom-prompt .general-settings-panel__section-action button", "index": 0, "frames": 0 },
|
||||
{ "kind": "wait", "step_id": "empty-instructions-editor-ready", "duration_ms": 300, "frames": 1 },
|
||||
{ "kind": "click", "step_id": "save-unchanged-empty-instructions", "selector": ".local-memory-edit-modal .wb-modal__footer button", "index": 1, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "instructions-save-result", "duration_ms": 300, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
".local-memory-pill",
|
||||
"#settings-local-memory-user-call-name .local-memory-pill",
|
||||
"#settings-local-memory-ai-name .local-memory-pill"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "open-user-call-name-editor", "selector": "#settings-local-memory-user-call-name .local-memory-pill", "index": 0, "frames": 1 },
|
||||
{ "kind": "key", "step_id": "close-user-call-name-editor-with-escape", "key": "Escape", "frames": 1 },
|
||||
{ "kind": "click", "step_id": "open-ai-name-editor", "selector": "#settings-local-memory-ai-name .local-memory-pill", "index": 0, "frames": 1 },
|
||||
{ "kind": "key", "step_id": "close-ai-name-editor-with-escape", "key": "Escape", "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
".general-settings-panel.personalization-panel",
|
||||
"#settings-local-memory-soul .general-settings-panel__section-title",
|
||||
"#settings-local-memory-soul .local-memory-long__preview"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "wait", "step_id": "personalization-full-page-top", "duration_ms": 200, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "scroll-page-to-persona-section", "selector": ".settings-modal__panel", "delta_y": 300, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "scroll-page-to-bottom-on-visible-title", "selector": "#settings-local-memory-soul .general-settings-panel__section-title", "delta_y": 300, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "persona-preview-text-1", "selector": "#settings-local-memory-soul .local-memory-long__preview", "delta_y": 200, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "persona-preview-text-2", "selector": "#settings-local-memory-soul .local-memory-long__preview", "delta_y": 200, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "persona-preview-text-3", "selector": "#settings-local-memory-soul .local-memory-long__preview", "delta_y": 200, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "persona-preview-text-4-bottom", "selector": "#settings-local-memory-soul .local-memory-long__preview", "delta_y": 200, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "persona-preview-return-top", "selector": "#settings-local-memory-soul .local-memory-long__preview", "delta_y": -1600, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "return-page-to-top-from-visible-title", "selector": "#settings-local-memory-soul .general-settings-panel__section-title", "delta_y": -1200, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "personalization-full-page-returned-top", "duration_ms": 200, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-local-memory-user-call-name",
|
||||
"#settings-local-memory-user-call-name .local-memory-identity-row__actions",
|
||||
"#settings-tone-style"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "open-user-call-name-inline-editor", "selector": "#settings-local-memory-user-call-name .local-memory-pill", "index": 0, "frames": 1 },
|
||||
{ "kind": "text", "step_id": "enter-temporary-call-name", "selector": "#settings-local-memory-user-call-name input", "text": "CodexTmp", "frames": 1 },
|
||||
{ "kind": "click", "step_id": "blur-call-name-editor", "selector": "#settings-tone-style .general-settings-panel__section-title", "index": 0, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-local-memory-user-call-name",
|
||||
"#settings-local-memory-user-call-name input",
|
||||
"#settings-local-memory-user-call-name .local-memory-identity-row__actions"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "edit-saved-user-call-name", "selector": "#settings-local-memory-user-call-name .local-memory-pill", "index": 0, "frames": 1 },
|
||||
{ "kind": "click", "step_id": "test-first-identity-action", "selector": "#settings-local-memory-user-call-name .local-memory-identity-row__actions button", "index": 0, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-local-memory-user-call-name",
|
||||
"#settings-local-memory-user-call-name .local-memory-pill",
|
||||
"#settings-local-memory-user-call-name .local-memory-identity-row__actions"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "close-empty-identity-editor", "selector": "#settings-local-memory-user-call-name .local-memory-identity-row__actions button", "index": 0, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-local-memory-user-call-name .local-memory-pill"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "hover", "step_id": "inspect-saved-identity-hover-actions", "selector": "#settings-local-memory-user-call-name .local-memory-pill", "index": 0, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-local-memory-user-call-name",
|
||||
"#settings-local-memory-user-call-name input",
|
||||
"#settings-local-memory-user-call-name .local-memory-identity-row__actions"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "reopen-saved-user-call-name", "selector": "#settings-local-memory-user-call-name .local-memory-pill", "index": 0, "frames": 1 },
|
||||
{ "kind": "text", "step_id": "clear-user-call-name-to-original-empty", "selector": "#settings-local-memory-user-call-name input", "text": "", "frames": 1 },
|
||||
{ "kind": "click", "step_id": "save-original-empty-user-call-name", "selector": "#settings-local-memory-user-call-name .local-memory-identity-row__actions button", "index": 1, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-local-memory-user-call-name",
|
||||
"#settings-local-memory-user-call-name input",
|
||||
"#settings-local-memory-user-call-name .local-memory-identity-row__actions"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "reopen-user-call-name-editor", "selector": "#settings-local-memory-user-call-name .local-memory-pill", "index": 0, "frames": 1 },
|
||||
{ "kind": "text", "step_id": "set-temporary-user-call-name", "selector": "#settings-local-memory-user-call-name input", "text": "CodexTmp", "frames": 1 },
|
||||
{ "kind": "click", "step_id": "save-temporary-user-call-name", "selector": "#settings-local-memory-user-call-name .local-memory-identity-row__actions button", "index": 1, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
".personalization-panel__tone-trigger",
|
||||
"#settings-loading-tips .wb-switch__control",
|
||||
"#settings-file-changes .wb-switch__control"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "wait", "step_id": "control-baseline", "duration_ms": 200, "frames": 1 },
|
||||
{ "kind": "click", "step_id": "open-reply-style-dropdown", "selector": ".personalization-panel__tone-trigger", "index": 0, "frames": 1 },
|
||||
{ "kind": "key", "step_id": "close-reply-style-dropdown", "key": "Escape", "frames": 1 },
|
||||
{ "kind": "click", "step_id": "toggle-welcome-off", "selector": "#settings-loading-tips .wb-switch__control", "index": 0, "frames": 1 },
|
||||
{ "kind": "click", "step_id": "restore-welcome-on", "selector": "#settings-loading-tips .wb-switch__control", "index": 0, "frames": 1 },
|
||||
{ "kind": "click", "step_id": "toggle-file-changes-on", "selector": "#settings-file-changes .wb-switch__control", "index": 0, "frames": 1 },
|
||||
{ "kind": "click", "step_id": "restore-file-changes-off", "selector": "#settings-file-changes .wb-switch__control", "index": 0, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-custom-prompt .general-settings-panel__section-action button"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "open-custom-prompt-editor-for-layout", "selector": "#settings-custom-prompt .general-settings-panel__section-action button", "index": 0, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
"html",
|
||||
".settings-modal__panel",
|
||||
".settings-navigation__item",
|
||||
".personalization-settings-panel"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "select-personalization-by-observed-nav-order", "selector": ".settings-navigation__item", "index": 5, "frames": 0 },
|
||||
{ "kind": "wait", "step_id": "personalization-navigation-confirmation", "duration_ms": 500, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
".general-settings-panel__section-title",
|
||||
"#settings-local-memory-soul .general-settings-panel__section-title",
|
||||
"#settings-local-memory-body .general-settings-panel__section-title"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "scroll", "step_id": "return-panel-to-top-from-visible-heading", "selector": "#settings-local-memory-soul .general-settings-panel__section-title", "delta_y": -1200, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "confirm-personalization-top", "duration_ms": 200, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
".general-settings-panel.personalization-panel",
|
||||
".general-settings-panel__group",
|
||||
".local-memory-long-card"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "wait", "step_id": "personalization-top", "duration_ms": 250, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "personalization-scroll-1", "selector": ".settings-modal__panel", "delta_y": 300, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "personalization-scroll-2", "selector": ".settings-modal__panel", "delta_y": 300, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "personalization-scroll-3-bottom", "selector": ".settings-modal__panel", "delta_y": 300, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "personalization-return-top", "selector": ".settings-modal__panel", "delta_y": -1200, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-custom-prompt .local-memory-long__preview",
|
||||
"#settings-local-memory-soul .general-settings-panel__section-title",
|
||||
"#settings-local-memory-body .local-memory-long__preview",
|
||||
".local-memory-edit-modal"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "click-instructions-preview-role-button", "selector": "#settings-custom-prompt .local-memory-long__preview", "index": 0, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "instructions-preview-editor-open", "duration_ms": 300, "frames": 1 },
|
||||
{ "kind": "click", "step_id": "cancel-instructions-preview-editor", "selector": ".local-memory-edit-modal .wb-modal__footer button", "index": 0, "frames": 0 },
|
||||
{ "kind": "wait", "step_id": "instructions-preview-editor-closed", "duration_ms": 350, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "scroll-to-bottom-memory-card-1", "selector": ".settings-modal__panel", "delta_y": 300, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "scroll-to-bottom-memory-card-2", "selector": "#settings-local-memory-soul .general-settings-panel__section-title", "delta_y": 300, "frames": 1 },
|
||||
{ "kind": "click", "step_id": "click-long-memory-preview-role-button", "selector": "#settings-local-memory-body .local-memory-long__preview", "index": 0, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "long-memory-preview-editor-open", "duration_ms": 300, "frames": 1 },
|
||||
{ "kind": "click", "step_id": "cancel-long-memory-preview-editor", "selector": ".local-memory-edit-modal .wb-modal__footer button", "index": 0, "frames": 0 },
|
||||
{ "kind": "wait", "step_id": "long-memory-preview-editor-closed", "duration_ms": 350, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "return-panel-top-after-preview-checks", "selector": "#settings-local-memory-body .general-settings-panel__section-title", "delta_y": -1200, "frames": 1 },
|
||||
{ "kind": "wait", "step_id": "top-after-preview-checks-confirmed", "duration_ms": 200, "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".settings-modal__panel",
|
||||
"#settings-local-memory-user-call-name",
|
||||
"#settings-local-memory-user-call-name input",
|
||||
"#settings-local-memory-user-call-name .local-memory-pill"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "open-saved-call-name", "selector": "#settings-local-memory-user-call-name .local-memory-pill", "index": 0, "frames": 1 },
|
||||
{ "kind": "text", "step_id": "replace-with-single-space", "selector": "#settings-local-memory-user-call-name input", "text": " ", "frames": 1 },
|
||||
{ "kind": "click", "step_id": "confirm-space-value", "selector": "#settings-local-memory-user-call-name .local-memory-identity-row__actions button", "index": 1, "frames": 2 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "restore-light-theme-for-final-state", "selector": ".appearance-card--light", "index": 0, "frames": 0 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "select-dark-theme-for-personalization-sample", "selector": ".appearance-card--dark", "index": 0, "frames": 0 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"include_fonts": false,
|
||||
"include_message_content": false,
|
||||
"track_selectors": [
|
||||
".personalization-panel__tone-trigger",
|
||||
".personalization-panel__tone-popover",
|
||||
".wb-select__option",
|
||||
".personalization-panel__option-name",
|
||||
".personalization-panel__option-hint"
|
||||
],
|
||||
"actions": [
|
||||
{ "kind": "click", "step_id": "open-reply-style-dropdown-for-all-options", "selector": ".personalization-panel__tone-trigger", "index": 0, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "reply-style-dropdown-lower-options", "selector": ".personalization-panel__tone-popover", "delta_y": 300, "frames": 1 },
|
||||
{ "kind": "scroll", "step_id": "reply-style-dropdown-return-to-top", "selector": ".personalization-panel__tone-popover", "delta_y": -300, "frames": 1 },
|
||||
{ "kind": "key", "step_id": "close-reply-style-dropdown-after-sampling", "key": "Escape", "frames": 1 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Recover only already-retained REA CSS; never access the installed application here.
|
||||
// Source: REA evidence ev_af920edf33af5e811d58f05dad15210b29cd891f28e0fa74f6b18d63212241b1
|
||||
// (WorkBuddy 5.8.3 设置 → 个性化), PersonalizationPanel-D16rRGjy.css.
|
||||
import fs from 'node:fs/promises'
|
||||
import { createHash } from 'node:crypto'
|
||||
import postcss from 'postcss'
|
||||
|
||||
const sha = value => createHash('sha256').update(value).digest('hex')
|
||||
const evidenceId = 'ev_af920edf33af5e811d58f05dad15210b29cd891f28e0fa74f6b18d63212241b1'
|
||||
const evidence = JSON.parse(await fs.readFile(`.tmp/rea-css-evidence/${evidenceId}.json`, 'utf8'))
|
||||
if (evidence.evidence_id !== evidenceId) throw new Error('REA identity mismatch')
|
||||
|
||||
const module = evidence.normalized_result.styles.items.find(item => /PersonalizationPanel-D16rRGjy\.css$/.test(item.file_path))
|
||||
if (!module) throw new Error('Personalization panel module CSS is absent from the evidence')
|
||||
const moduleCss = module.source.artifact.text
|
||||
const moduleSha = sha(moduleCss)
|
||||
if (moduleSha !== '318664f96253adace047d7b1d2d213fbf6139461c10d80ae12135998b7b98f3d') throw new Error('Personalization panel module CSS changed')
|
||||
|
||||
const tokens = await fs.readFile('.tmp/rea-css-recovered/tokens-Df-i-zsO.css', 'utf8')
|
||||
const tokensSha = sha(tokens)
|
||||
if (tokensSha !== '13f2bf82c96e5167a7d7098e36950c6c22c475b3337dbb8bb29ad2fe79cde696') throw new Error('Tokens CSS changed')
|
||||
|
||||
const lightTokens = new Map()
|
||||
const darkTokens = new Map()
|
||||
for (const rule of postcss.parse(tokens).nodes) {
|
||||
if (rule.type !== 'rule') continue
|
||||
const selectors = rule.selectors ?? []
|
||||
const dark = selectors.some(selector => selector.includes('[data-theme=dark]'))
|
||||
const light = selectors.some(selector => selector.trim() === ':root')
|
||||
if (!dark && !light) continue
|
||||
for (const node of rule.nodes ?? []) {
|
||||
if (node.type !== 'decl' || !node.prop.startsWith('--')) continue
|
||||
if (dark) darkTokens.set(node.prop, node.value)
|
||||
if (light) lightTokens.set(node.prop, node.value)
|
||||
}
|
||||
}
|
||||
|
||||
const relatedSelector = /\.(?:personalization-panel|local-memory)(?:[\w-]*)?(?![\w-])/i
|
||||
const darkScope = /^\s*(?:\[data-theme=["']?dark["']?\]|body\[data-vscode-theme-name=["']IDE Night["']\]|body\.vscode-dark|\.vscode-dark|body\.dark|\.dark)(?=\s|$)/i
|
||||
function project(node) {
|
||||
if (node.type === 'rule') {
|
||||
const selectors = []
|
||||
for (const selector of node.selectors) {
|
||||
if (!relatedSelector.test(selector)) continue
|
||||
const mapped = selector.replace(darkScope, '[data-theme=dark]')
|
||||
if (!selectors.includes(mapped)) selectors.push(mapped)
|
||||
}
|
||||
return selectors.length ? node.clone({ selector: selectors.join(',') }) : null
|
||||
}
|
||||
if (node.type === 'atrule' && node.nodes) {
|
||||
const nodes = node.nodes.map(project).filter(Boolean)
|
||||
return nodes.length ? node.clone({ nodes }) : null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const root = postcss.root({ nodes: postcss.parse(moduleCss).nodes.map(project).filter(Boolean) })
|
||||
const referenced = new Set()
|
||||
const collect = text => {
|
||||
for (const match of text.matchAll(/var\(\s*(--[\w-]+)/g)) referenced.add(match[1])
|
||||
}
|
||||
collect(root.toString())
|
||||
for (let depth = 0; depth < 8; depth++) {
|
||||
const before = referenced.size
|
||||
for (const name of [...referenced]) {
|
||||
for (const value of [lightTokens.get(name), darkTokens.get(name)]) {
|
||||
if (typeof value === 'string') collect(value)
|
||||
}
|
||||
}
|
||||
if (referenced.size === before) break
|
||||
}
|
||||
|
||||
const missingLight = [...referenced].filter(name => !lightTokens.has(name))
|
||||
if (missingLight.length) throw new Error(`Tokens missing from light root scope: ${missingLight.join(', ')}`)
|
||||
const pick = map => new Map([...referenced].map(name => [name, map.get(name) ?? lightTokens.get(name)]))
|
||||
const scope = (selector, map) => `${selector}{${[...map].map(([property, value]) => `${property}:${value}`).join(';')}}`
|
||||
const scopedTokens = `${scope('.general-settings-panel.personalization-panel', pick(lightTokens))}\n${scope(':root[data-theme=dark] .general-settings-panel.personalization-panel', pick(darkTokens))}\n`
|
||||
const css = `/* REA ${evidenceId}; PersonalizationPanel-D16rRGjy.css sha256=${moduleSha}; tokens sha256=${tokensSha}. */\n${root.toString()}\n${scopedTokens}`
|
||||
await fs.writeFile('src/renderer/src/components/source-personalization-panel-recovered.css', css)
|
||||
|
||||
const rules = []
|
||||
root.walkRules(rule => rules.push(rule.selector))
|
||||
console.log(`Recovered ${rules.length} personalization rules (${Buffer.byteLength(css)} bytes); scoped ${pick(lightTokens).size} light / ${pick(darkTokens).size} dark tokens`)
|
||||
console.log(rules.slice(0, 16).join('\n'))
|
||||
if (rules.length < 30) throw new Error('Unexpectedly few personalization rules')
|
||||
@@ -0,0 +1,333 @@
|
||||
// TODO 13 验收:设置 → 个性化页的 1:1 结构/几何 + 身份编辑/回复风格/开关/长文本弹窗 + 持久化与主题。
|
||||
// 真实隔离 YCode(真实 preload/IPC/渲染进程/文件系统)+ 原生设置窗口,视口 900×680(与原版一致)。
|
||||
//
|
||||
// 原版依据(REA ev_af920…/ev_88af07…/ev_d6e0a2…/ev_523e07…/ev_4f4f76…):
|
||||
// 面板 [232,40,638,1160];身份卡 638×100(两行 [244,48,614,34]、胶囊 172×34);
|
||||
// 回复偏好卡 638×204(回复风格 select 120×32 @734,174;开关 34×20 @820,248/316);
|
||||
// 指令/人设/长期记忆三张长卡 638×264(y=376/656/936),预览 598×160;编辑弹窗 [130,80,640,520]。
|
||||
const fs = require('node:fs/promises')
|
||||
const path = require('node:path')
|
||||
const assert = require('node:assert/strict')
|
||||
|
||||
const TONE_LABELS = ['默认', '专业严谨', '亲和友善', '直言不讳', '天马行空', '高效务实', '毒舌吐槽', '启发引导']
|
||||
const TONE_HINTS = ['不设定特定风格。', '清晰、准确、值得信赖。', '温暖、平易近人、鼓励支持。', '简明扼要、不废话、直击要点。',
|
||||
'富有想象力、善用比喻类比。', '最少文字、最大信息量。', '犀利吐槽、但绝不伤人。', '用提问引导思考、授人以渔。']
|
||||
|
||||
if (!process.versions.electron) {
|
||||
;(async () => {
|
||||
const directory = await fs.mkdtemp(path.resolve('.tmp/personalization-settings-'))
|
||||
const data = path.join(directory, 'data'), home = path.join(directory, 'home')
|
||||
await fs.mkdir(data, { recursive: true })
|
||||
await fs.mkdir(home, { recursive: true })
|
||||
const env = { ...process.env, YCODE_QA_DIRECTORY: directory, YCODE_USER_DATA_DIR: data, YCODE_OMP_HOME: home,
|
||||
YCODE_DISABLE_GPU: '1', YCODE_DISABLE_SYSTEM_INTEGRATION: '1' }
|
||||
delete env.ELECTRON_RUN_AS_NODE; delete env.ELECTRON_RENDERER_URL
|
||||
const child = require('node:child_process').spawn(require('electron'),
|
||||
['--no-sandbox', '--disable-gpu-sandbox', __filename], { env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
|
||||
let output = ''
|
||||
child.stdout.on('data', bytes => { output += bytes; if (bytes.toString().includes('PERSONALIZATION_SETTINGS_')) process.stdout.write(bytes) })
|
||||
child.stderr.on('data', bytes => { output += bytes })
|
||||
const timer = setTimeout(() => child.kill(), 300000)
|
||||
child.once('exit', async code => {
|
||||
clearTimeout(timer)
|
||||
await fs.writeFile(path.join(directory, 'process.log'), output)
|
||||
console.log(JSON.stringify({ directory, exitCode: code }))
|
||||
if (code) console.error(output.slice(-4000))
|
||||
process.exitCode = code ? 1 : 0
|
||||
})
|
||||
})().catch(error => { console.error(error); process.exitCode = 1 })
|
||||
} else {
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const { pathToFileURL } = require('node:url')
|
||||
const directory = process.env.YCODE_QA_DIRECTORY
|
||||
app.setPath('home', path.join(directory, 'home'))
|
||||
const checks = [], snapshots = {}, rendererErrors = []
|
||||
let main, settings, phase = 'boot'
|
||||
const pause = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
async function read(source, win = main) {
|
||||
let timer
|
||||
try {
|
||||
return await Promise.race([win.webContents.executeJavaScript(source), new Promise((_, reject) => {
|
||||
timer = setTimeout(() => reject(Error('Renderer timeout: ' + source.slice(0, 120))), 15000)
|
||||
})])
|
||||
} finally { clearTimeout(timer) }
|
||||
}
|
||||
async function wait(source, label, win = main) {
|
||||
for (let index = 0; index < 300; index++) { const value = await read(source, win); if (value) return value; await pause(40) }
|
||||
throw Error('Timeout: ' + label)
|
||||
}
|
||||
async function step(label) { phase = label; await fs.writeFile(path.join(directory, 'phase.txt'), label) }
|
||||
function check(label, actual, expected) {
|
||||
const pass = typeof expected === 'function' ? expected(actual) : JSON.stringify(actual) === JSON.stringify(expected)
|
||||
checks.push({ label, pass, actual, expected: typeof expected === 'function' ? expected.toString() : expected })
|
||||
}
|
||||
const near = (value, target, tolerance = 0.75) => Math.abs(value - target) <= tolerance
|
||||
function observe(win) {
|
||||
win.webContents.on('console-message', event => { if (event.level === 'error' || event.level >= 3) rendererErrors.push({ windowId: win.id, message: event.message }) })
|
||||
}
|
||||
async function openSettings(section) {
|
||||
await read(`window.ycode.app.openSettings(${JSON.stringify(section)})`)
|
||||
for (let index = 0; index < 200; index++) {
|
||||
settings = BrowserWindow.getAllWindows().find(win => win !== main)
|
||||
if (settings && !settings.webContents.isLoading()) break
|
||||
await pause(25)
|
||||
}
|
||||
assert.ok(settings, 'settings window')
|
||||
await wait('!!document.querySelector(".settings-window-root")', 'settings root', settings)
|
||||
await pause(400)
|
||||
return settings
|
||||
}
|
||||
const setInput = (selector, value) => `(()=>{const input=document.querySelector(${JSON.stringify(selector)})
|
||||
const proto=input instanceof HTMLTextAreaElement?HTMLTextAreaElement.prototype:HTMLInputElement.prototype
|
||||
Object.getOwnPropertyDescriptor(proto,'value').set.call(input,${JSON.stringify(value)})
|
||||
input.dispatchEvent(new Event('input',{bubbles:true}));return true})()`
|
||||
|
||||
const layoutSource = `(()=>{
|
||||
const round = value => Math.round(value * 1000) / 1000
|
||||
const rect = node => { if (!node) return null; const r = node.getBoundingClientRect()
|
||||
return { x: round(r.x), y: round(r.y), width: round(r.width), height: round(r.height) } }
|
||||
const panel = document.querySelector('.personalization-panel')
|
||||
return {
|
||||
panel: rect(panel),
|
||||
identityCard: rect(document.querySelector('.local-memory-identity-card')),
|
||||
rows: [...document.querySelectorAll('.local-memory-identity-row')].map(row => ({
|
||||
id: row.id,
|
||||
label: row.querySelector('.local-memory-identity-row__label')?.textContent?.trim(),
|
||||
rect: rect(row),
|
||||
pill: (() => { const node = row.querySelector('.local-memory-pill')
|
||||
return node ? { rect: rect(node), text: node.textContent.trim(), placeholder: !!node.querySelector('.local-memory-pill__placeholder') } : null })(),
|
||||
input: (() => { const node = row.querySelector('.local-memory-inline__input')
|
||||
return node ? { rect: rect(node), value: node.value } : null })(),
|
||||
actions: row.querySelectorAll('.local-memory-inline__icon-btn').length,
|
||||
confirmDisabled: row.querySelector('.local-memory-inline__icon-btn--confirm')?.disabled ?? null
|
||||
})),
|
||||
preference: rect(document.querySelector('.general-settings-panel__group-card')),
|
||||
sections: [...document.querySelectorAll('.general-settings-panel__group-card > .general-settings-panel__section')].map(section => ({
|
||||
id: section.id,
|
||||
title: section.querySelector('.general-settings-panel__section-title')?.textContent?.trim(),
|
||||
desc: section.querySelector('.general-settings-panel__section-desc')?.textContent?.trim(),
|
||||
rect: rect(section),
|
||||
select: rect(section.querySelector('.wb-select')),
|
||||
switch: rect(section.querySelector('.wb-switch__track')),
|
||||
switchChecked: section.querySelector('.wb-switch__input')?.checked ?? null,
|
||||
switchDisabled: section.querySelector('.wb-switch__input')?.disabled ?? null,
|
||||
unavailable: section.querySelector('[data-settings-unavailable=true]')?.getAttribute('title')
|
||||
})),
|
||||
longCards: [...document.querySelectorAll('.local-memory-long-card')].map(card => ({
|
||||
id: card.id,
|
||||
rect: rect(card),
|
||||
title: card.querySelector('.general-settings-panel__section-title')?.textContent?.trim(),
|
||||
desc: card.querySelector('.general-settings-panel__section-desc')?.textContent?.trim(),
|
||||
edit: rect(card.querySelector('.general-settings-panel__section-action .wb-button')),
|
||||
preview: rect(card.querySelector('.local-memory-long__preview')),
|
||||
text: card.querySelector('.local-memory-long__value,.local-memory-long__placeholder')?.textContent?.trim(),
|
||||
placeholder: !!card.querySelector('.local-memory-long__placeholder')
|
||||
})),
|
||||
toneLabel: document.querySelector('.personalization-panel__tone-trigger .wb-select__value')?.textContent?.trim(),
|
||||
popover: rect(document.querySelector('.personalization-panel__tone-popover')),
|
||||
options: [...document.querySelectorAll('.personalization-panel__tone-popover [role=option]')].map(option => ({
|
||||
name: option.querySelector('.personalization-panel__option-name')?.textContent?.trim(),
|
||||
hint: option.querySelector('.personalization-panel__option-hint')?.textContent?.trim(),
|
||||
selected: option.getAttribute('aria-selected')
|
||||
})),
|
||||
modal: rect(document.querySelector('.local-memory-edit-modal')),
|
||||
modalTitle: document.querySelector('.local-memory-edit-modal .wb-modal__title')?.textContent?.trim(),
|
||||
editorValue: document.querySelector('.local-memory-editor')?.value ?? null,
|
||||
editorPlaceholder: document.querySelector('.local-memory-editor')?.getAttribute('placeholder') ?? null,
|
||||
modalButtons: [...document.querySelectorAll('.local-memory-edit-modal .wb-modal__footer .wb-button')].map(button => ({ text: button.textContent.trim(), rect: rect(button) })),
|
||||
rowBackground: getComputedStyle(document.querySelector('.local-memory-identity-card')).backgroundColor,
|
||||
rowColor: getComputedStyle(document.querySelector('.local-memory-identity-row__label')).color,
|
||||
loadingTips: localStorage.getItem('ycode:loading-tips'),
|
||||
theme: document.documentElement.dataset.theme ?? 'light'
|
||||
}})()`
|
||||
|
||||
;(async () => {
|
||||
await import(pathToFileURL(path.resolve('out/main/index.mjs')).href); await app.whenReady()
|
||||
for (let index = 0; index < 200; index++) {
|
||||
main = BrowserWindow.getAllWindows().find(win => win.getTitle() === 'YCode')
|
||||
if (main && !main.webContents.isLoading()) break
|
||||
await pause(25)
|
||||
}
|
||||
assert.ok(main, 'main window'); observe(main)
|
||||
await wait('!!window.ycode', 'preload api')
|
||||
|
||||
await step('open-personalization-settings')
|
||||
await openSettings('personalization'); observe(settings)
|
||||
await wait('!!document.querySelector(".personalization-panel")', 'personalization panel', settings)
|
||||
const nav = await read(`(()=>{const item=document.querySelector('[data-settings-tab="personalization"]')
|
||||
return { exists: !!item, disabled: item?.getAttribute('aria-disabled'), active: document.querySelector('.settings-navigation__item--active')?.dataset.settingsTab }})()`, settings)
|
||||
check('个性化 nav entry is enabled and active', [nav.exists, nav.disabled !== 'true', nav.active], [true, true, 'personalization'])
|
||||
|
||||
const light = await read(layoutSource, settings)
|
||||
snapshots.light = light
|
||||
check('panel keeps the recovered 638x1160 box at 232,40',
|
||||
[near(light.panel.x, 232), near(light.panel.y, 40), near(light.panel.width, 638), near(light.panel.height, 1160)], [true, true, true, true])
|
||||
check('identity card keeps 638x100 with two 614x34 rows',
|
||||
[near(light.identityCard.width, 638), near(light.identityCard.height, 100),
|
||||
near(light.rows[0].rect.y, 48), near(light.rows[0].rect.width, 614), near(light.rows[0].rect.height, 34),
|
||||
near(light.rows[1].rect.y, 98)], [true, true, true, true, true, true])
|
||||
check('identity rows keep the recovered labels and empty pills',
|
||||
[light.rows.map(row => row.label), light.rows.every(row => row.pill?.text === '暂无内容,点击编辑添加' && row.pill?.placeholder),
|
||||
light.rows.every(row => row.pill && near(row.pill.rect.x, 686) && near(row.pill.rect.width, 172) && near(row.pill.rect.height, 34))],
|
||||
[['YCode 对你的称呼', 'YCode 的名字'], true, true])
|
||||
check('reply preference card keeps 638x204 with three recovered rows',
|
||||
[near(light.preference.y, 156), near(light.preference.width, 638), near(light.preference.height, 204),
|
||||
light.sections.map(section => section.title)], [true, true, true, ['回复风格', '欢迎语', '显示文件修改过程']])
|
||||
check('reply style select keeps 120x32 and the default tone',
|
||||
[near(light.sections[0].select.x, 734), near(light.sections[0].select.y, 174), near(light.sections[0].select.width, 120),
|
||||
near(light.sections[0].select.height, 32), light.toneLabel], [true, true, true, true, '默认'])
|
||||
check('switches keep 34x20 with 欢迎语 on and 显示文件修改过程 disabled',
|
||||
[[near(light.sections[1].switch.width, 34), near(light.sections[1].switch.height, 20), light.sections[1].switchChecked].every(Boolean),
|
||||
light.sections[2].switchChecked === false && light.sections[2].switchDisabled === true && light.sections[2].unavailable === '暂未接入'], [true, true])
|
||||
check('three long cards keep 638x264 at y=376/656/936',
|
||||
light.longCards.map((card, index) => [near(card.rect.y, [376, 656, 936][index]), near(card.rect.width, 638), near(card.rect.height, 264)].every(Boolean)),
|
||||
[true, true, true])
|
||||
check('long cards keep the recovered titles, descriptions and 598x160 previews',
|
||||
[light.longCards.map(card => card.title), light.longCards.map(card => card.desc),
|
||||
light.longCards.every(card => near(card.preview.width, 598) && near(card.preview.height, 160)),
|
||||
light.longCards.every(card => card.edit && near(card.edit.width, 68) && near(card.edit.height, 32))], [
|
||||
['给 YCode 的指令', '人设设定', '长期记忆'],
|
||||
['写下你希望 YCode 一直遵守的要求,之后每次对话和任务都会默认生效。', '设定 YCode 的身份、性格与表达风格,对所有回复生效。', '跨会话保存的重要信息与结论,可随时查看和修改。'],
|
||||
true, true])
|
||||
check('long previews keep their original content and placeholders',
|
||||
[light.longCards[0].placeholder, light.longCards[0].text, light.longCards[1].text?.startsWith("You're not a chatbot."), light.longCards[2].placeholder],
|
||||
[true, '例如:回答尽量简洁,先给结论', true, true])
|
||||
|
||||
await step('personalization-tone')
|
||||
await read(`document.querySelector('.personalization-panel__tone-trigger').click()`, settings)
|
||||
const popover = await wait(`(()=>{const node=document.querySelector('.personalization-panel__tone-popover')
|
||||
return node?{rect:(()=>{const r=node.getBoundingClientRect();return {x:r.x,width:r.width}})(),options:[...node.querySelectorAll('[role=option]')].length}:null})()`, 'tone popover', settings)
|
||||
check('回复风格 popover keeps the recovered 240px width and 8 options', [near(popover.rect.width, 240), popover.options], [true, 8])
|
||||
const options = await read(`[...document.querySelectorAll('.personalization-panel__tone-popover [role=option]')].map(option => [
|
||||
option.querySelector('.personalization-panel__option-name')?.textContent?.trim(),
|
||||
option.querySelector('.personalization-panel__option-hint')?.textContent?.trim()])`, settings)
|
||||
check('tone options keep every recovered label and hint', [options.map(item => item[0]), options.map(item => item[1])], [TONE_LABELS, TONE_HINTS])
|
||||
await read(`[...document.querySelectorAll('.personalization-panel__tone-popover [role=option]')][1].click()`, settings)
|
||||
await wait(`document.querySelector('.personalization-panel__tone-trigger .wb-select__value')?.textContent?.trim()==='专业严谨'`, 'tone applied', settings)
|
||||
const toneStored = await read(`window.ycode.personalization.get().then(view => view.tone)`, settings)
|
||||
check('choosing 专业严谨 persists the tone', toneStored, 'professional')
|
||||
|
||||
await step('personalization-switches')
|
||||
await read(`document.querySelector('#settings-loading-tips .wb-switch__input').click()`, settings)
|
||||
await wait(`localStorage.getItem('ycode:loading-tips')==='0'`, 'loading tips mirror', settings)
|
||||
check('欢迎语 switches off, persists and mirrors to the renderer',
|
||||
[await read(`window.ycode.personalization.get().then(view => view.loadingTips)`, settings), await read(`localStorage.getItem('ycode:loading-tips')`, settings)], [false, '0'])
|
||||
await read(`document.querySelector('#settings-loading-tips .wb-switch__input').click()`, settings)
|
||||
await wait(`localStorage.getItem('ycode:loading-tips')==='1'`, 'loading tips restored', settings)
|
||||
check('欢迎语 switches back on', await read(`window.ycode.personalization.get().then(view => view.loadingTips)`, settings), true)
|
||||
await read(`document.querySelector('#settings-file-changes .wb-switch__input').click()`, settings)
|
||||
check('显示文件修改过程 stays disabled and keeps the stored default', await read(`window.ycode.personalization.get().then(view => view.showFileChanges)`, settings), false)
|
||||
|
||||
await step('personalization-identity')
|
||||
await read(`document.querySelector('#settings-local-memory-user-call-name .local-memory-pill').click()`, settings)
|
||||
const editing = await wait(`(()=>{const row=document.querySelector('#settings-local-memory-user-call-name')
|
||||
const input=row.querySelector('.local-memory-inline__input')
|
||||
if(!input)return null
|
||||
const r=input.getBoundingClientRect()
|
||||
return { width:r.width, height:r.height, actions:row.querySelectorAll('.local-memory-inline__icon-btn').length,
|
||||
confirmDisabled:row.querySelector('.local-memory-inline__icon-btn--confirm')?.disabled }})()`, 'identity editing', settings)
|
||||
check('clicking the pill opens the recovered 172x34 inline editor with cancel/confirm',
|
||||
[near(editing.width, 172), near(editing.height, 34), editing.actions, editing.confirmDisabled], [true, true, 2, true])
|
||||
await read(setInput('#settings-local-memory-user-call-name .local-memory-inline__input', '小明'), settings)
|
||||
await pause(120)
|
||||
await read(`document.querySelector('#settings-local-memory-user-call-name .local-memory-inline__icon-btn--confirm').click()`, settings)
|
||||
const savedIdentity = await wait(`(()=>{const pill=document.querySelector('#settings-local-memory-user-call-name .local-memory-pill')
|
||||
return pill?pill.textContent.trim():null})()`, 'identity saved', settings)
|
||||
check('confirming a non-empty identity persists and renders it',
|
||||
[savedIdentity, await read(`window.ycode.personalization.get().then(view => view.userCallName)`, settings)], ['小明', '小明'])
|
||||
await read(`document.querySelector('#settings-local-memory-ai-name .local-memory-pill').click()`, settings)
|
||||
await wait(`!!document.querySelector('#settings-local-memory-ai-name .local-memory-inline__input')`, 'ai name editor', settings)
|
||||
await read(setInput('#settings-local-memory-ai-name .local-memory-inline__input', '临时草稿'), settings)
|
||||
// 真实焦点离开本行(点不可聚焦的 div 不会触发 blur,这里把焦点交给回复风格按钮)。
|
||||
await read(`document.querySelector('.personalization-panel__tone-trigger').focus();true`, settings)
|
||||
const afterBlur = await wait(`(()=>{const row=document.querySelector('#settings-local-memory-ai-name')
|
||||
return row.querySelector('.local-memory-inline__input')?null:row.querySelector('.local-memory-pill')?.textContent?.trim()})()`, 'blur discards draft', settings)
|
||||
check('blurring outside the row discards the identity draft like the original',
|
||||
[afterBlur, await read(`window.ycode.personalization.get().then(view => view.aiName)`, settings)], ['暂无内容,点击编辑添加', ''])
|
||||
|
||||
await step('personalization-editors')
|
||||
await read(`document.querySelector('#settings-local-memory-ai-name .local-memory-pill').click()`, settings)
|
||||
await wait(`!!document.querySelector('#settings-local-memory-ai-name .local-memory-inline__input')`, 'ai name editor again', settings)
|
||||
await read(setInput('#settings-local-memory-ai-name .local-memory-inline__input', '小助手'), settings)
|
||||
await pause(100)
|
||||
await read(`document.querySelector('#settings-local-memory-ai-name .local-memory-inline__icon-btn--confirm').click()`, settings)
|
||||
await wait(`document.querySelector('#settings-local-memory-ai-name .local-memory-pill')?.textContent?.trim()==='小助手'`, 'ai name saved', settings)
|
||||
await read(`document.querySelector('#settings-custom-prompt .local-memory-long__preview').click()`, settings)
|
||||
const modal = await wait(`(()=>{const node=document.querySelector('.local-memory-edit-modal')
|
||||
if(!node)return null
|
||||
const r=node.getBoundingClientRect()
|
||||
return { x:r.x, y:r.y, width:r.width, height:r.height,
|
||||
title:node.querySelector('.wb-modal__title')?.textContent?.trim(),
|
||||
placeholder:node.querySelector('.local-memory-editor')?.getAttribute('placeholder'),
|
||||
buttons:[...node.querySelectorAll('.wb-modal__footer .wb-button')].map(button=>({text:button.textContent.trim(),width:button.getBoundingClientRect().width,height:button.getBoundingClientRect().height})) }})()`, 'prompt editor modal', settings)
|
||||
snapshots.modal = modal
|
||||
check('指令 editor keeps the recovered 640x520 modal with 保存/取消 68x32 buttons',
|
||||
[[near(modal.x, 130), near(modal.y, 80), near(modal.width, 640), near(modal.height, 520)].every(Boolean),
|
||||
modal.title, modal.buttons.map(button => button.text), modal.buttons.every(button => near(button.width, 68) && near(button.height, 32))],
|
||||
[true, '给 YCode 的指令', ['取消', '保存'], true])
|
||||
check('empty 指令 editor keeps the recovered placeholder', [modal.placeholder], ['当前内容为空,点击开始输入'])
|
||||
await read(setInput('.local-memory-editor', '先给结论,再给依据。'), settings)
|
||||
await pause(120)
|
||||
await read(`[...document.querySelectorAll('.local-memory-edit-modal .wb-modal__footer .wb-button')].find(button=>button.textContent.trim()==='保存').click()`, settings)
|
||||
await wait(`!document.querySelector('.local-memory-edit-modal')`, 'modal closed after save', settings)
|
||||
const promptCard = await read(`(()=>{const card=document.querySelector('#settings-custom-prompt')
|
||||
return { text:card.querySelector('.local-memory-long__value')?.textContent?.trim(), placeholder:!!card.querySelector('.local-memory-long__placeholder') }})()`, settings)
|
||||
check('saving the 指令 updates the preview and persists',
|
||||
[promptCard.text, promptCard.placeholder, await read(`window.ycode.personalization.get().then(view => view.customPrompt)`, settings)],
|
||||
['先给结论,再给依据。', false, '先给结论,再给依据。'])
|
||||
await read(`document.querySelector('#settings-local-memory-soul .local-memory-long__preview').click()`, settings)
|
||||
const personaOpen = await wait(`(()=>{const node=document.querySelector('.local-memory-edit-modal')
|
||||
return node?{title:node.querySelector('.wb-modal__title')?.textContent?.trim(), value:node.querySelector('.local-memory-editor')?.value}:null})()`, 'persona editor', settings)
|
||||
check('人设 editor opens with the recovered default text',
|
||||
[personaOpen.title, personaOpen.value?.startsWith("You're not a chatbot.")], ['人设设定', true])
|
||||
await read(`[...document.querySelectorAll('.local-memory-edit-modal .wb-modal__footer .wb-button')].find(button=>button.textContent.trim()==='取消').click()`, settings)
|
||||
await wait(`!document.querySelector('.local-memory-edit-modal')`, 'persona modal cancelled', settings)
|
||||
check('cancelling the persona editor keeps the stored default', await read(`window.ycode.personalization.get().then(view => view.persona.startsWith("You're not a chatbot."))`, settings), true)
|
||||
|
||||
await step('personalization-persistence')
|
||||
const config = JSON.parse(await fs.readFile(path.join(directory, 'data', 'config.json'), 'utf8'))
|
||||
snapshots.config = config.personalization
|
||||
check('config.json keeps the identity, tone and prompt values',
|
||||
[config.personalization?.userCallName, config.personalization?.aiName, config.personalization?.tone, config.personalization?.customPrompt,
|
||||
config.personalization?.loadingTips], ['小明', '小助手', 'professional', '先给结论,再给依据。', true])
|
||||
settings.close()
|
||||
await pause(500)
|
||||
await openSettings('personalization')
|
||||
const reopened = await read(layoutSource, settings)
|
||||
check('reopened settings restores the persisted personalization',
|
||||
[reopened.rows[0].pill?.text, reopened.rows[1].pill?.text, reopened.toneLabel, reopened.longCards[0].text],
|
||||
['小明', '小助手', '专业严谨', '先给结论,再给依据。'])
|
||||
|
||||
await step('personalization-dark-theme')
|
||||
await openSettings('appearance')
|
||||
await wait('!!document.querySelector(".appearance-card")', 'appearance panel', settings)
|
||||
await read(`document.querySelector('[aria-label="深色"].appearance-card').click()`, settings)
|
||||
await wait(`document.documentElement.dataset.theme==='dark'`, 'dark theme', settings)
|
||||
await openSettings('personalization')
|
||||
await wait(`document.documentElement.dataset.theme==='dark'&&!!document.querySelector('.personalization-panel')`, 'dark personalization panel', settings)
|
||||
const dark = await read(layoutSource, settings)
|
||||
snapshots.dark = dark
|
||||
check('dark theme keeps the recovered card surface and text',
|
||||
[dark.rowBackground, dark.rowColor], ['rgb(31, 31, 31)', 'rgb(242, 243, 245)'])
|
||||
check('dark theme keeps every recovered geometry',
|
||||
[near(dark.panel.width, 638), near(dark.panel.height, 1160), near(dark.preference.height, 204),
|
||||
dark.longCards.every(card => near(card.preview.width, 598) && near(card.preview.height, 160))], [true, true, true, true])
|
||||
await openSettings('appearance')
|
||||
await wait('!!document.querySelector(".appearance-card")', 'appearance panel again', settings)
|
||||
await read(`document.querySelector('[aria-label="浅色"].appearance-card').click()`, settings)
|
||||
await wait(`document.documentElement.dataset.theme==='light'`, 'light restored', settings)
|
||||
|
||||
await step('personalization-errors')
|
||||
check('no renderer error logs', rendererErrors, [])
|
||||
|
||||
const result = checks.every(item => item.pass) ? 'PASS' : 'FAIL'
|
||||
await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify({ result, phase, checks, snapshots }, null, 2))
|
||||
console.log('PERSONALIZATION_SETTINGS_' + result + ' ' + directory)
|
||||
for (const failure of checks.filter(item => !item.pass)) console.log('PERSONALIZATION_SETTINGS_FAILURE ' + JSON.stringify(failure))
|
||||
app.exit(result === 'PASS' ? 0 : 1)
|
||||
})().catch(async error => {
|
||||
console.error(error)
|
||||
try { await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify({ result: 'ERROR', phase, checks, snapshots, error: String(error?.stack ?? error) }, null, 2)) } catch { /* ignore */ }
|
||||
app.exit(1)
|
||||
})
|
||||
}
|
||||
@@ -125,10 +125,10 @@ if (!process.versions.electron) {
|
||||
// 原版 4 组;about 组按组件约定渲染为分隔线而不是标题。
|
||||
check('nav keeps the four recovered groups', [nav.groups.length, nav.dividers], [3, 1])
|
||||
// 通用(settings) 在 TODO 10 接入后同样可用。
|
||||
const expectedDisabled = EXPECTED_TABS.filter(tab => !['settings', 'models', 'appearance', 'keyboardShortcuts'].includes(tab))
|
||||
const expectedDisabled = EXPECTED_TABS.filter(tab => !['settings', 'models', 'appearance', 'keyboardShortcuts', 'personalization'].includes(tab))
|
||||
check('unwired entries stay disabled', nav.tabs.filter((tab, index) => nav.disabled[index] === 'true'), expectedDisabled)
|
||||
check('unwired entries keep the 暂未接入 hint', nav.titles.filter(title => (title ?? '').includes('暂未接入')).length, expectedDisabled.length)
|
||||
check('settings, appearance, keyboard shortcuts and models stay enabled', nav.tabs.filter((tab, index) => !nav.disabled[index]), ['settings', 'appearance', 'keyboardShortcuts', 'models'])
|
||||
check('settings, appearance, keyboard shortcuts, personalization and models stay enabled', nav.tabs.filter((tab, index) => !nav.disabled[index]), ['settings', 'appearance', 'keyboardShortcuts', 'personalization', 'models'])
|
||||
|
||||
await step('settings-search-excludes-removed')
|
||||
await read(`(()=>{const input=document.querySelector('.settings-navigation input[type="search"]');
|
||||
|
||||
Reference in new issue
Block a user