diff --git a/docs/superpowers/drift-adjudication/2026-05-07-operation-delay-plan.md b/docs/superpowers/drift-adjudication/2026-05-07-operation-delay-plan.md deleted file mode 100644 index aa13470..0000000 --- a/docs/superpowers/drift-adjudication/2026-05-07-operation-delay-plan.md +++ /dev/null @@ -1,136 +0,0 @@ -# Operation Delay Drift Adjudication Ledger - -Immutable spec path: `/root/projects/resister-codex/codex-oauth-automation-extension-upstream-base-vu8.0/codex-oauth-automation-extension/docs/superpowers/specs/2026-05-07-operation-delay-design.md` - -Immutable plan path: `/root/projects/resister-codex/codex-oauth-automation-extension-upstream-base-vu8.0/codex-oauth-automation-extension/docs/superpowers/plans/2026-05-07-operation-delay-plan.md` - -Controller-only mutation rule: only the P9/controller may create, append, supersede, close, summarize, or otherwise mutate this ledger. Subagents may read it, cite `DRIFT_ID`, report evidence, and request supersession, but may not edit it directly. - -## Active Adjudications Summary - -| DRIFT_ID | STATUS | SCOPE | P9_FINAL_ADJUDICATION | EFFECTIVE_EXECUTION_CONTRACT | -|---|---|---|---|---| -| DRIFT-2026-05-08-T2-001 | ACTIVE | Task 2 sidepanel switch copy | PLAN_DRIFT_CONTINUE_SAFE | Use sidepanel switch copy that communicates input, selection, click, submit, continue, authorization, and 2 seconds; do not regress to the narrower frozen-plan snippet. | -| DRIFT-2026-05-08-T3-001 | ACTIVE | Task 3 2925 provider metadata test scope | PLAN_DRIFT_CONTINUE_SAFE | Treat `tests/background-icloud-mail-provider.test.js` as in scope for Task 3 checkpoint because it asserts the 2925 provider metadata changed by the required operation-delay injection. | -| DRIFT-2026-05-08-T4-001 | ACTIVE | Task 4 Step 3 direct-complete test scope | PLAN_DRIFT_CONTINUE_SAFE | Treat `tests/step3-direct-complete.test.js` as in scope for Task 4 checkpoint because it verifies Step 3 completion-before-navigation plus deferred password-submit operation-delay behavior. | -| DRIFT-2026-05-08-T7-001 | ACTIVE | Task 7 mail-2925 full-suite harness scope | PLAN_DRIFT_CONTINUE_SAFE | Treat `tests/mail-2925-content.test.js` as in scope for Task 7 only for a narrow stale-harness fix providing `performOperationWithDelay` to the existing `ensureAgreementChecked` harness. | - -## DRIFT-2026-05-08-T2-001 - -DRIFT_ID: DRIFT-2026-05-08-T2-001 - -STATUS: ACTIVE - -SUPERSEDES_DRIFT_ID: NONE - -SUPERSEDED_BY_DRIFT_ID: NONE - -SCOPE: Task 2 sidepanel switch copy - -TRIGGER: Task 2 spec reviewer reported that the frozen Task 2 Step 3 HTML copy snippet omitted required operation categories while the implementation used spec-complete copy. - -FROZEN_PLAN_STEP: `docs/superpowers/plans/2026-05-07-operation-delay-plan.md:330-348`, especially line 337 title copy and line 344 caption copy. - -GOVERNING_SPEC_REQUIREMENT: `docs/superpowers/specs/2026-05-07-operation-delay-design.md:36-38` requires the sidepanel boolean operation-delay switch label to communicate waits after page input, selection, click, submit, continue, and authorization operations. - -REPO_OR_WORKTREE_EVIDENCE: `sidepanel/sidepanel.html:578-589` implements the switch; line 582 title copy and line 589 visible caption copy cover input, selection, click, submit, continue, authorization, and 2 seconds. - -INVESTIGATOR_JUDGMENTS: Two fresh read-only investigators returned `PLAN_DRIFT_CONTINUE_SAFE`. Investigator 1 found the implementation aligned with the immutable spec and literal frozen-plan copy would under-satisfy the spec. Investigator 2 found Task 2 Step 3's literal HTML snippet not executable as written for the copy requirement, while the rest of Task 2 remains safely executable. - -P9_FINAL_ADJUDICATION: PLAN_DRIFT_CONTINUE_SAFE - -EFFECTIVE_EXECUTION_CONTRACT: For Task 2 and later reviews, treat the immutable spec's all-six-category sidepanel switch copy requirement as controlling. Preserve copy that communicates input, selection, click, submit, continue, authorization, and 2 seconds. Do not request or implement a regression to the narrower frozen-plan snippet. - -SAFE_TO_CONTINUE: YES - -REOPEN_ONLY_IF: New immutable spec evidence changes the sidepanel switch copy requirement, or repo evidence shows the implementation no longer communicates all six operation categories and the 2 second duration. - -## DRIFT-2026-05-08-T3-001 - -DRIFT_ID: DRIFT-2026-05-08-T3-001 - -STATUS: ACTIVE - -SUPERSEDES_DRIFT_ID: NONE - -SUPERSEDED_BY_DRIFT_ID: NONE - -SCOPE: Task 3 2925 provider metadata test scope - -TRIGGER: Task 3 spec reviewer reported that the frozen Task 3 file list and commit step omitted `tests/background-icloud-mail-provider.test.js`, but full-suite verification required updating that existing 2925 provider metadata expectation after the required 2925 operation-delay injection change. - -FROZEN_PLAN_STEP: `docs/superpowers/plans/2026-05-07-operation-delay-plan.md:421-431` lists Task 3 files and tests without `tests/background-icloud-mail-provider.test.js`; `docs/superpowers/plans/2026-05-07-operation-delay-plan.md:656-660` gives a Task 3 commit step that also omits that test file. - -GOVERNING_SPEC_REQUIREMENT: `docs/superpowers/specs/2026-05-07-operation-delay-design.md:16-22`, `44-52`, and `80-83` require covered page operations to use the centralized content-side operation delay gate and current enabled state, while excluding only disabled/background/excluded work. - -REPO_OR_WORKTREE_EVIDENCE: `background.js:11173-11180` implements the 2925 provider reuse metadata with `content/utils.js`, `content/operation-delay.js`, and `content/mail-2925.js` in order. `background/mail-2925-session.js:32-36` implements the matching session injection order. `tests/background-icloud-mail-provider.test.js:140-152` is an existing assertion over the same 2925 provider metadata and must expect `content/operation-delay.js` for the full suite to remain aligned. `npm test` passed 817/817 with that update. - -INVESTIGATOR_JUDGMENTS: Two fresh read-only investigators returned `PLAN_DRIFT_CONTINUE_SAFE`. Both found the frozen plan's 2925 production requirement spec-correct, but the file list and commit step incomplete because they omitted an existing metadata test naturally affected by the required `background.js` provider metadata change. - -P9_FINAL_ADJUDICATION: PLAN_DRIFT_CONTINUE_SAFE - -EFFECTIVE_EXECUTION_CONTRACT: For Task 3 and later reviews, treat `tests/background-icloud-mail-provider.test.js` as in scope. Include it in the Task 3 checkpoint commit with the frozen Task 3 files. Preserve 2925 injection order as `content/utils.js`, `content/operation-delay.js`, `content/mail-2925.js` in both provider reuse and Mail2925 session paths. Do not treat this test update as unrelated scope creep. - -SAFE_TO_CONTINUE: YES - -REOPEN_ONLY_IF: New repo evidence shows `tests/background-icloud-mail-provider.test.js` no longer asserts the 2925 provider metadata changed by Task 3, or immutable spec evidence removes the 2925/provider reuse operation-delay injection need. - -## DRIFT-2026-05-08-T4-001 - -DRIFT_ID: DRIFT-2026-05-08-T4-001 - -STATUS: ACTIVE - -SUPERSEDES_DRIFT_ID: NONE - -SUPERSEDED_BY_DRIFT_ID: NONE - -SCOPE: Task 4 Step 3 direct-complete test scope - -TRIGGER: Task 4 spec reviewer reported that the frozen Task 4 file list and commit step omitted `tests/step3-direct-complete.test.js`, but Step 3 operation-delay migration must preserve the existing completion-before-navigation contract while routing the deferred password submit through the operation-delay gate. - -FROZEN_PLAN_STEP: `docs/superpowers/plans/2026-05-07-operation-delay-plan.md:667-674` lists Task 4 files and tests without `tests/step3-direct-complete.test.js`; `docs/superpowers/plans/2026-05-07-operation-delay-plan.md:788-790` gives a Task 4 commit step that also omits that test file. - -GOVERNING_SPEC_REQUIREMENT: `docs/superpowers/specs/2026-05-07-operation-delay-design.md:18-20`, `44-52`, `55-60`, and `80-83` require covered page fills/submits/continues to use the centralized content-side operation delay gate. `docs/superpowers/specs/2026-05-07-operation-delay-design.md:111` makes `data/step-definitions.js` canonical for step keys, and `data/step-definitions.js:14` defines Step 3 as `fill-password`. - -REPO_OR_WORKTREE_EVIDENCE: `content/signup-page.js:2463-2580` implements `step3_fillEmailPassword`; `content/signup-page.js:2533-2536` routes password fill through `performOperationWithDelay`; `content/signup-page.js:2559` reports Step 3 completion before deferred submit navigation; `content/signup-page.js:2561-2570` routes the deferred password submit through `performOperationWithDelay`. `tests/step3-direct-complete.test.js:54-235` verifies completion-before-navigation and deferred submit operation-delay behavior. Targeted verification including this test passed 38/38. - -INVESTIGATOR_JUDGMENTS: Two fresh read-only investigators returned `PLAN_DRIFT_CONTINUE_SAFE`. Both found the frozen plan's broad Task 4 requirement covers Step 3 signup-page operations, but its exact file list and commit step are incomplete because they omit an existing regression test materially affected by the required `content/signup-page.js` change. - -P9_FINAL_ADJUDICATION: PLAN_DRIFT_CONTINUE_SAFE - -EFFECTIVE_EXECUTION_CONTRACT: For Task 4 and later reviews, treat `tests/step3-direct-complete.test.js` as in scope. Include it in the Task 4 checkpoint commit with the frozen Task 4 files. Preserve Step 3 completion/report before the deferred navigation-prone submit, and keep both password fill and deferred password submit routed through the shared operation-delay gate. - -SAFE_TO_CONTINUE: YES - -REOPEN_ONLY_IF: New repo evidence shows `tests/step3-direct-complete.test.js` no longer asserts Step 3 direct-completion behavior affected by Task 4, or immutable spec evidence removes Step 3 password fill/submit from covered OpenAI/auth page operations. - -## DRIFT-2026-05-08-T7-001 - -DRIFT_ID: DRIFT-2026-05-08-T7-001 - -STATUS: ACTIVE - -SUPERSEDES_DRIFT_ID: NONE - -SUPERSEDED_BY_DRIFT_ID: NONE - -SCOPE: Task 7 mail-2925 full-suite harness scope - -TRIGGER: Task 7 implementer reported that targeted docs/exclusion tests passed but full `npm test` failed in `tests/mail-2925-content.test.js` because its extracted `ensureAgreementChecked` harness did not provide the new `performOperationWithDelay` dependency introduced by the spec-correct Task 6 `content/mail-2925.js` operation-delay wrapping. - -FROZEN_PLAN_STEP: `docs/superpowers/plans/2026-05-07-operation-delay-plan.md:1031-1039` lists Task 7 files and full existing test verification but omits `tests/mail-2925-content.test.js`; `docs/superpowers/plans/2026-05-07-operation-delay-plan.md:1107-1115` requires full `npm test` to pass; `docs/superpowers/plans/2026-05-07-operation-delay-plan.md:1117-1121` gives a Task 7 commit step that omits that harness file. - -GOVERNING_SPEC_REQUIREMENT: `docs/superpowers/specs/2026-05-07-operation-delay-design.md:18-22`, `44-52`, `53-62`, and `80-83` require covered page clicks/submits/controls to use the centralized content-side operation delay gate, while `64-71` excludes polling/background work, not 2925 login/session UI clicks. - -REPO_OR_WORKTREE_EVIDENCE: `content/mail-2925.js:490-512` has `ensureAgreementChecked()` calling `performOperationWithDelay` for checkbox clicks, `content/mail-2925.js:1057-1060` defines that helper, and `content/mail-2925.js:1144` calls `ensureAgreementChecked()` from `ensureMail2925Session()`. `tests/mail-2925-content.test.js:896-902` extracts `ensureAgreementChecked` without extracting or stubbing `performOperationWithDelay`, causing `node --test tests/mail-2925-content.test.js` to fail 14/1 and full `npm test` to fail 841/1. - -INVESTIGATOR_JUDGMENTS: Two fresh read-only investigators returned `PLAN_DRIFT_CONTINUE_SAFE`. Both found the production `content/mail-2925.js` change spec-correct and the failure a narrow stale test harness issue. Both found Task 7's full-suite requirement makes the harness update necessary despite omission from the frozen Task 7 file list and commit step. - -P9_FINAL_ADJUDICATION: PLAN_DRIFT_CONTINUE_SAFE - -EFFECTIVE_EXECUTION_CONTRACT: For Task 7 and later reviews, treat `tests/mail-2925-content.test.js` as in scope only for a narrow stale-harness fix that provides or extracts `performOperationWithDelay` for the existing `ensureAgreementChecked` test. Include that test update in the Task 7 checkpoint. Do not change runtime `content/mail-2925.js`, do not remove operation-delay wrapping from 2925 session UI, and do not wrap polling/cleanup handlers. - -SAFE_TO_CONTINUE: YES - -REOPEN_ONLY_IF: New evidence shows `tests/mail-2925-content.test.js` no longer exercises `ensureAgreementChecked`, or immutable spec evidence removes 2925 login/session UI checkbox clicks from covered operation-delay behavior. diff --git a/docs/superpowers/drift-adjudication/2026-05-08-icloud-polling-addphone-fix-plan.md b/docs/superpowers/drift-adjudication/2026-05-08-icloud-polling-addphone-fix-plan.md deleted file mode 100644 index 341af2c..0000000 --- a/docs/superpowers/drift-adjudication/2026-05-08-icloud-polling-addphone-fix-plan.md +++ /dev/null @@ -1,12 +0,0 @@ -# iCloud Polling And Add Phone Handoff Drift Adjudication Ledger - -Immutable spec path: `/root/projects/resister-codex/codex-oauth-automation-extension-upstream-base-vu8.0/codex-oauth-automation-extension/.worktrees/fix-icloud-polling-addphone-handoff/docs/superpowers/specs/2026-05-08-icloud-polling-addphone-fix-design.md` - -Immutable plan path: `/root/projects/resister-codex/codex-oauth-automation-extension-upstream-base-vu8.0/codex-oauth-automation-extension/.worktrees/fix-icloud-polling-addphone-handoff/docs/superpowers/plans/2026-05-08-icloud-polling-addphone-fix-plan.md` - -Controller-only mutation rule: only the P9/controller may create, append, supersede, close, summarize, or otherwise mutate this ledger. Subagents may read it, cite `DRIFT_ID`, report evidence, and request supersession, but may not edit it directly. - -## Active Adjudications Summary - -| DRIFT_ID | STATUS | SCOPE | P9_FINAL_ADJUDICATION | EFFECTIVE_EXECUTION_CONTRACT | -|---|---|---|---|---| diff --git a/docs/superpowers/drift-adjudication/2026-05-09-phone-signup-stale-email-verification-plan.md b/docs/superpowers/drift-adjudication/2026-05-09-phone-signup-stale-email-verification-plan.md deleted file mode 100644 index 900fa07..0000000 --- a/docs/superpowers/drift-adjudication/2026-05-09-phone-signup-stale-email-verification-plan.md +++ /dev/null @@ -1,10 +0,0 @@ -# Phone Signup Stale Email Verification Drift Adjudication Ledger - -Immutable spec: `/root/projects/resister-codex/codex-oauth-automation-extension-upstream-base-vu8.0/codex-oauth-automation-extension/.worktrees/fix-icloud-polling-addphone-handoff/docs/superpowers/specs/2026-05-09-phone-signup-stale-email-verification-design.md` - -Immutable plan: `/root/projects/resister-codex/codex-oauth-automation-extension-upstream-base-vu8.0/codex-oauth-automation-extension/.worktrees/fix-icloud-polling-addphone-handoff/docs/superpowers/plans/2026-05-09-phone-signup-stale-email-verification-plan.md` - -## Active Adjudications Summary - -| DRIFT_ID | STATUS | SCOPE | P9_FINAL_ADJUDICATION | EFFECTIVE_EXECUTION_CONTRACT | -| --- | --- | --- | --- | --- | diff --git a/docs/superpowers/plans/2026-05-07-operation-delay-plan.md b/docs/superpowers/plans/2026-05-07-operation-delay-plan.md deleted file mode 100644 index 6c380ff..0000000 --- a/docs/superpowers/plans/2026-05-07-operation-delay-plan.md +++ /dev/null @@ -1,1122 +0,0 @@ -# Operation Delay Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add a default-enabled, sidepanel-controlled 2 second delay after covered page automation operations, without slowing polling, background work, or excluded OAuth/platform verification flows. - -**Architecture:** Keep the product contract in one persisted boolean setting and enforce timing through one shared content-side gate loaded after `content/utils.js`. Background owns setting defaults and broadcast, the sidepanel owns all user-facing switch feedback, and covered content scripts wrap page DOM operations through the shared gate instead of adding scattered sleeps. - -**Tech Stack:** Chrome extension MV3, plain JavaScript, `node:test`, `chrome.storage.local`, `chrome.runtime` messaging, existing content-script `sleep()` stop behavior. - ---- - -## File Structure - -- `background.js`: add `operationDelayEnabled` to `PERSISTED_SETTING_DEFAULTS`, strict boolean normalization, settings payload defaults, `SIGNUP_PAGE_INJECT_FILES` loading, and the `provider === '2925'` dynamic provider injection list for the new content module. -- `background/message-router.js`: keep `SAVE_SETTING` persistence and `DATA_UPDATED` broadcast for `operationDelayEnabled`; do not write operation-delay success logs from the background. -- `sidepanel/sidepanel.html`: add one `操作间延迟` switch near the existing delay settings. -- `sidepanel/sidepanel.js`: bind the switch, restore invalid or missing values as enabled, persist through `SAVE_SETTING`, roll back on save failure, and write exactly one bottom `log-area` entry for each user-visible toggle result. -- `content/operation-delay.js`: new shared content module that caches the persisted setting, defaults unresolved values to enabled, skips excluded step keys, and waits 2000 ms after a covered operation by using the existing stop-aware `sleep()` from `content/utils.js`. -- `manifest.json`: load `content/operation-delay.js` immediately after `content/utils.js` only in manifest content-script bundles that perform covered page operations. -- `background/steps/create-plus-checkout.js`, `background/steps/fill-plus-checkout.js`, `background/steps/paypal-approve.js`, `background/steps/gopay-approve.js`, `background/mail-2925-session.js`: add `content/operation-delay.js` immediately after `content/utils.js` in dynamic injection arrays for covered checkout, payment, and 2925 session pages. -- `content/auth-page-recovery.js`, `content/signup-page.js`, `content/phone-auth.js`: wrap OpenAI/auth page retry clicks, inputs, hidden DOM synchronizations, clicks, submits, continue actions, and grouped split-code entry. -- `content/plus-checkout.js`, `content/paypal-flow.js`, `content/gopay-flow.js`: wrap checkout, payment, and authorization page DOM writes and clicks. -- `content/duck-mail.js`, `content/mail-2925.js`: wrap only non-polling provider UI operations such as Duck address generation and 2925 login/session preparation. -- `content/mail-163.js`, `content/qq-mail.js`, `content/icloud-mail.js`, `content/gmail-mail.js`, `content/inbucket-mail.js`: leave `POLL_EMAIL` handlers and their refresh/navigation/open/delete polling loops unwrapped so mailbox cadence is unchanged. -- `content/mail-2925.js`: leave `POLL_EMAIL` and `DELETE_ALL_EMAILS` handlers unwrapped because they are mailbox polling/cleanup support paths. -- `content/whatsapp-flow.js`: leave WhatsApp code-reading wait loops unwrapped because they are SMS/OTP polling, not covered page operations. -- `background/steps/confirm-oauth.js`, `background/steps/platform-verify.js`, `background/panel-bridge.js`, `content/sub2api-panel.js`: keep these excluded paths free of operation-delay injection and gate calls. -- `README.md`, `docs/使用教程/使用教程.md`: document the new switch, default-enabled behavior, 2 second fixed delay, grouped OTP behavior, and excluded work. -- `tests/*.test.js`: add focused settings, sidepanel, gate, injection, call-site timing, polling-exclusion, docs, and regression tests. - -## Implementation Rules - -- `operationDelayEnabled` normalization is strict: only boolean `false` disables the feature, boolean `true` enables it, and absent or invalid values such as `undefined`, `null`, `''`, `0`, and `'false'` resolve to `true`. -- Sidepanel is the only success-log owner for user toggles. Background broadcasts state but does not write a second success entry. -- The delay gate always runs the page operation first and then waits 2000 ms when active. The first covered operation has no pre-wait. -- The 2000 ms wait must call the existing content-script `sleep()` from `content/utils.js`, or a test-injected equivalent, so Stop interrupts the delay with the same stop error path. -- Split verification-code fields are one grouped operation: fill all fields, delay once, then treat any submit/continue click as a separate covered operation with its own post-action delay. -- `confirm-oauth` and `platform-verify` remain excluded by step key and by keeping their dedicated injection paths free of `content/operation-delay.js`. -- `POLL_EMAIL`, SMS/OTP polling, background retries, timers, storage writes, and network-only API calls remain unwrapped. - -## TASK_GROUP 1: Persisted setting and sidepanel control - -### Task 1: Add the new persisted setting and broadcast contract - -**Files:** -- Modify: `background.js` -- Modify: `background/message-router.js` -- Test: `tests/background-operation-delay-settings.test.js` -- Test: `tests/background-message-router-module.test.js` - -- [ ] **Step 1: Write the failing tests** - -Add `tests/background-operation-delay-settings.test.js` with assertions for the strict persisted setting contract: - -```js -const test = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); - -const source = fs.readFileSync('background.js', 'utf8'); - -function extractFunction(name) { - const start = source.indexOf(`function ${name}(`); - assert.notEqual(start, -1, `missing ${name}`); - const bodyStart = source.indexOf('{', start); - let depth = 0; - for (let i = bodyStart; i < source.length; i += 1) { - if (source[i] === '{') depth += 1; - if (source[i] === '}') { - depth -= 1; - if (depth === 0) return source.slice(start, i + 1); - } - } - throw new Error(`unterminated ${name}`); -} - -test('operationDelayEnabled defaults to enabled and strictly normalizes values', () => { - const defaultsBlock = source.slice( - source.indexOf('const PERSISTED_SETTING_DEFAULTS = {'), - source.indexOf('const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);') - ); - assert.match(defaultsBlock, /operationDelayEnabled:\s*true/); - - const api = new Function(`${extractFunction('normalizePersistentSettingValue')}; return { normalizePersistentSettingValue };`)(); - for (const value of [undefined, null, '', 0, 'false']) { - assert.equal(api.normalizePersistentSettingValue('operationDelayEnabled', value), true); - } - assert.equal(api.normalizePersistentSettingValue('operationDelayEnabled', true), true); - assert.equal(api.normalizePersistentSettingValue('operationDelayEnabled', false), false); -}); - -test('operationDelayEnabled is normalized through the background settings payload path', () => { - const api = new Function(` - const PERSISTED_SETTING_DEFAULTS = { operationDelayEnabled: true }; - const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS); - function resolveLegacyAutoStepDelaySeconds() { return undefined; } - ${extractFunction('normalizePersistentSettingValue')} - ${extractFunction('buildPersistentSettingsPayload')} - return { buildPersistentSettingsPayload }; - `)(); - - assert.equal(api.buildPersistentSettingsPayload({}, { fillDefaults: true }).operationDelayEnabled, true); - for (const value of [undefined, null, '', 0, 'false', true]) { - assert.equal(api.buildPersistentSettingsPayload({ operationDelayEnabled: value }, { fillDefaults: true }).operationDelayEnabled, true); - } - assert.equal(api.buildPersistentSettingsPayload({ operationDelayEnabled: false }).operationDelayEnabled, false); -}); -``` - -Extend `tests/background-message-router-module.test.js` with a broadcast-only router assertion: - -```js -test('SAVE_SETTING broadcasts operation delay setting without background success log', async () => { - const source = fs.readFileSync('background/message-router.js', 'utf8'); - const globalScope = { console }; - const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope); - const broadcasts = []; - const logs = []; - let state = { operationDelayEnabled: true, plusModeEnabled: false, plusPaymentMethod: 'paypal' }; - - const router = api.createMessageRouter({ - addLog: async (message, level = 'info') => logs.push({ message, level }), - buildLuckmailSessionSettingsPayload: () => ({}), - buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'operationDelayEnabled') - ? { operationDelayEnabled: input.operationDelayEnabled === false ? false : true } - : {}, - broadcastDataUpdate: (payload) => broadcasts.push(payload), - getState: async () => ({ ...state }), - setPersistentSettings: async () => {}, - setState: async (updates) => { state = { ...state, ...updates }; }, - }); - - const response = await router.handleMessage({ - type: 'SAVE_SETTING', - source: 'sidepanel', - payload: { operationDelayEnabled: false }, - }); - - assert.equal(response.ok, true); - assert.equal(state.operationDelayEnabled, false); - assert.deepStrictEqual(broadcasts.at(-1), { operationDelayEnabled: false }); - assert.equal(logs.length, 0); -}); -``` - -- [ ] **Step 2: Run the targeted tests and verify they fail** - -Run: `node --test tests/background-operation-delay-settings.test.js tests/background-message-router-module.test.js` - -Expected: FAIL with assertions showing `operationDelayEnabled` is missing from defaults/normalization and no broadcast-only contract exists yet. - -- [ ] **Step 3: Write the minimal implementation** - -Patch `background.js` so the new setting is in the persisted defaults and uses strict boolean normalization: - -```js -const PERSISTED_SETTING_DEFAULTS = { - // existing keys stay in place - operationDelayEnabled: true, -}; - -function normalizePersistentSettingValue(key, value) { - switch (key) { - case 'operationDelayEnabled': - return typeof value === 'boolean' ? value : true; - // existing cases stay unchanged - } -} -``` - -Keep `background/message-router.js` on the existing `SAVE_SETTING` path. The required behavior is that `buildPersistentSettingsPayload(message.payload)` includes `operationDelayEnabled` when present, `setState(stateUpdates)` persists it into runtime state, and `broadcastDataUpdate(stateUpdates)` emits it once. Do not add an `addLog()` branch for operation-delay success feedback in the router. - -- [ ] **Step 4: Run the targeted tests and verify they pass** - -Run: `node --test tests/background-operation-delay-settings.test.js tests/background-message-router-module.test.js` - -Expected: PASS for all tests in both files. - -- [ ] **Step 5: Commit** - -```bash -git add background.js background/message-router.js tests/background-operation-delay-settings.test.js tests/background-message-router-module.test.js -git commit -m "feat: persist operation delay setting" -``` - -### Task 2: Add the sidepanel switch and single-owner tips/log feedback - -**Files:** -- Modify: `sidepanel/sidepanel.html` -- Modify: `sidepanel/sidepanel.js` -- Test: `tests/sidepanel-operation-delay.test.js` - -- [ ] **Step 1: Write the failing test** - -Add `tests/sidepanel-operation-delay.test.js` with structural and behavior-level assertions: - -```js -const test = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); - -const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8'); -const source = fs.readFileSync('sidepanel/sidepanel.js', 'utf8'); - -test('sidepanel exposes one operation delay switch in the existing settings surface', () => { - assert.match(html, /id="row-operation-delay-settings"/); - assert.match(html, /id="input-operation-delay-enabled"/); - assert.match(html, /操作间延迟/); - assert.match(html, /2\s*秒/); -}); - -test('sidepanel owns operation delay toggle feedback exactly once', () => { - assert.match(source, /inputOperationDelayEnabled/); - assert.match(source, /operationDelayEnabled/); - assert.match(source, /appendLog\(\{[\s\S]*操作间延迟/); - assert.doesNotMatch(source, /operationDelayEnabled[\s\S]{0,240}addLog\(/); -}); - -test('sidepanel operation delay restore and save failure contracts are explicit', () => { - assert.match(source, /normalizeOperationDelayEnabled/); - assert.match(source, /lastConfirmedOperationDelayEnabled/); - assert.match(source, /操作间延迟设置读取失败/); - assert.match(source, /操作间延迟设置保存失败/); - assert.match(source, /inputOperationDelayEnabled\.checked\s*=\s*lastConfirmedOperationDelayEnabled/); -}); -``` - -Add a small executable harness in the same file for the new helper behavior. The helper names can be extracted from `sidepanel.js`, but the assertions must prove these contracts: - -```js -function loadOperationDelaySidepanelHarness() { - const source = fs.readFileSync('sidepanel/sidepanel.js', 'utf8'); - const runtimeMessages = []; - const logs = []; - const inputOperationDelayEnabled = { checked: true, disabled: false }; - - function extractFunction(name) { - const start = source.indexOf(`function ${name}(`); - assert.notEqual(start, -1, `missing ${name}`); - let depth = 0; - let signatureEnded = false; - let bodyStart = -1; - for (let i = start; i < source.length; i += 1) { - const ch = source[i]; - if (ch === '(') depth += 1; - if (ch === ')') { - depth -= 1; - if (depth === 0) signatureEnded = true; - } - if (ch === '{' && signatureEnded) { - bodyStart = i; - break; - } - } - let braceDepth = 0; - for (let i = bodyStart; i < source.length; i += 1) { - const ch = source[i]; - if (ch === '{') braceDepth += 1; - if (ch === '}') { - braceDepth -= 1; - if (braceDepth === 0) return source.slice(start, i + 1); - } - } - throw new Error(`unterminated ${name}`); - } - - const chrome = { - runtime: { - sendMessage: async (message) => { - runtimeMessages.push(message); - return { ok: true, state: { operationDelayEnabled: message.payload.operationDelayEnabled } }; - }, - }, - }; - - const harness = new Function('chrome', 'appendLog', 'inputOperationDelayEnabled', 'latestState', ` - let lastConfirmedOperationDelayEnabled = true; - function syncLatestState(nextState) { - latestState = { ...(latestState || {}), ...(nextState || {}) }; - } - ${extractFunction('normalizeOperationDelayEnabled')} - ${extractFunction('appendOperationDelayLog')} - ${extractFunction('applyOperationDelayState')} - ${extractFunction('persistOperationDelayToggle')} - return { - applyOperationDelayState, - persistOperationDelayToggle, - getLastConfirmedOperationDelayEnabled: () => lastConfirmedOperationDelayEnabled, - setLastConfirmedOperationDelayEnabled: (value) => { lastConfirmedOperationDelayEnabled = value; }, - }; - `)(chrome, (entry) => logs.push(entry), inputOperationDelayEnabled, { operationDelayEnabled: true }); - - return { chrome, harness, inputOperationDelayEnabled, logs, runtimeMessages }; -} - -test('operation delay switch logs once, defaults restore failures to enabled, and rolls back save failures', async () => { - const { chrome, harness: helpers, inputOperationDelayEnabled: input, logs, runtimeMessages: sentMessages } = loadOperationDelaySidepanelHarness(); - - helpers.applyOperationDelayState({ operationDelayEnabled: undefined }, { restoreFailed: true }); - assert.equal(input.checked, true); - assert.equal(logs.filter((entry) => entry.level === 'warn').length, 1); - - logs.length = 0; - input.checked = false; - await helpers.persistOperationDelayToggle(); - assert.equal(sentMessages.at(-1).payload.operationDelayEnabled, false); - assert.equal(logs.length, 1); - assert.match(logs[0].message, /关闭|off/i); - - sentMessages.length = 0; - chrome.runtime.sendMessage = async () => { throw new Error('network down'); }; - logs.length = 0; - input.checked = true; - helpers.setLastConfirmedOperationDelayEnabled(false); - await assert.rejects(() => helpers.persistOperationDelayToggle(), /network down/); - assert.equal(input.checked, false); - assert.equal(logs.filter((entry) => entry.level === 'error').length, 1); -}); -``` - -- [ ] **Step 2: Run the targeted test and verify it fails** - -Run: `node --test tests/sidepanel-operation-delay.test.js` - -Expected: FAIL with missing switch/helper/log assertions. - -- [ ] **Step 3: Write the minimal implementation** - -Add this row near `row-auto-delay-settings` in `sidepanel/sidepanel.html`: - -```html -
- 操作间延迟 -
-
- - 页面输入/点击/提交后等待 2 秒 -
-
-
-``` - -Patch `sidepanel/sidepanel.js` with a narrow sidepanel-owned setting handler: - -```js -const inputOperationDelayEnabled = document.getElementById('input-operation-delay-enabled'); -let lastConfirmedOperationDelayEnabled = true; - -function normalizeOperationDelayEnabled(value) { - return typeof value === 'boolean' ? value : true; -} - -function appendOperationDelayLog(enabled, level = 'info', message = '') { - appendLog({ - timestamp: Date.now(), - level, - message: message || (enabled - ? '操作间延迟已开启:页面操作完成后等待 2 秒。' - : '操作间延迟已关闭:页面操作将连续执行。'), - }); -} - -function applyOperationDelayState(state = latestState, options = {}) { - const enabled = normalizeOperationDelayEnabled(state?.operationDelayEnabled); - lastConfirmedOperationDelayEnabled = enabled; - if (inputOperationDelayEnabled) inputOperationDelayEnabled.checked = enabled; - if (options.restoreFailed) { - appendOperationDelayLog(true, 'warn', '操作间延迟设置读取失败,已回退为默认开启。'); - } -} - -async function persistOperationDelayToggle() { - const nextEnabled = Boolean(inputOperationDelayEnabled?.checked); - const previousEnabled = lastConfirmedOperationDelayEnabled; - try { - const response = await chrome.runtime.sendMessage({ - type: 'SAVE_SETTING', - source: 'sidepanel', - payload: { operationDelayEnabled: nextEnabled }, - }); - if (response?.error) throw new Error(response.error); - const confirmed = normalizeOperationDelayEnabled(response?.state?.operationDelayEnabled ?? nextEnabled); - lastConfirmedOperationDelayEnabled = confirmed; - if (inputOperationDelayEnabled) inputOperationDelayEnabled.checked = confirmed; - syncLatestState({ operationDelayEnabled: confirmed }); - appendOperationDelayLog(confirmed); - } catch (error) { - if (inputOperationDelayEnabled) inputOperationDelayEnabled.checked = previousEnabled; - appendOperationDelayLog(previousEnabled, 'error', `操作间延迟设置保存失败,已恢复为上一次确认的状态:${error.message}`); - throw error; - } -} -``` - -Call `applyOperationDelayState(state)` from `applySettingsState(state)` and from the `DATA_UPDATED` branch when `message.payload.operationDelayEnabled !== undefined`. The `DATA_UPDATED` sync path must update the switch but must not call `appendOperationDelayLog`, because the user toggle handler is the single success-log owner. Add `inputOperationDelayEnabled.addEventListener('change', () => { persistOperationDelayToggle().catch(() => {}); });` with the existing event listener block. - -- [ ] **Step 4: Run the targeted test and verify it passes** - -Run: `node --test tests/sidepanel-operation-delay.test.js` - -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add sidepanel/sidepanel.html sidepanel/sidepanel.js tests/sidepanel-operation-delay.test.js -git commit -m "feat: add operation delay sidepanel toggle" -``` - -## TASK_GROUP 2: Shared content-side operation gate and loading - -### Task 3: Add the shared delay gate and load it on covered pages - -**Files:** -- Create: `content/operation-delay.js` -- Modify: `manifest.json` -- Modify: `background.js` -- Modify: `background/steps/create-plus-checkout.js` -- Modify: `background/steps/fill-plus-checkout.js` -- Modify: `background/steps/paypal-approve.js` -- Modify: `background/steps/gopay-approve.js` -- Modify: `background/mail-2925-session.js` -- Test: `tests/content-operation-delay.test.js` -- Test: `tests/operation-delay-injection.test.js` - -- [ ] **Step 1: Write the failing tests** - -Add `tests/content-operation-delay.test.js` for the gate contract: - -```js -const test = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); - -function loadOperationDelayApi(overrides = {}) { - const source = fs.readFileSync('content/operation-delay.js', 'utf8'); - const root = { - console, - sleep: async (ms) => overrides.events?.push(`sleep:${ms}`), - chrome: overrides.chrome || { storage: { local: { get: async () => ({}) }, onChanged: { addListener() {} } } }, - }; - new Function('self', 'globalThis', `${source}; return self.CodexOperationDelay;`)(root, root); - return root.CodexOperationDelay; -} - -test('operation delay runs action first and waits once after completion', async () => { - const events = []; - const api = loadOperationDelayApi({ events }); - await api.performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill' }, async () => { - events.push('operation:start'); - events.push('operation:end'); - }, { sleep: async (ms) => events.push(`sleep:${ms}`), getEnabled: () => true }); - assert.deepStrictEqual(events, ['operation:start', 'operation:end', 'sleep:2000']); -}); - -test('operation delay skips disabled mode and excluded step keys', async () => { - const api = loadOperationDelayApi(); - assert.equal(api.shouldDelayOperation({ enabled: false, stepKey: 'fill-profile', kind: 'click' }), false); - assert.equal(api.shouldDelayOperation({ enabled: true, stepKey: 'confirm-oauth', kind: 'click' }), false); - assert.equal(api.shouldDelayOperation({ enabled: true, stepKey: 'platform-verify', kind: 'submit' }), false); - assert.equal(api.shouldDelayOperation({ enabled: true, stepKey: 'fill-profile', kind: 'fill' }), true); -}); - -test('operation delay defaults unresolved settings to enabled', async () => { - const events = []; - const api = loadOperationDelayApi({ events, chrome: { storage: { local: { get: async () => ({}) }, onChanged: { addListener() {} } } } }); - await api.performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill' }, async () => events.push('operation'), { sleep: async (ms) => events.push(`sleep:${ms}`) }); - assert.deepStrictEqual(events, ['operation', 'sleep:2000']); -}); - -test('invalid persisted values still resolve to enabled', async () => { - const api = loadOperationDelayApi({ chrome: { storage: { local: { get: async () => ({ operationDelayEnabled: 'false' }) }, onChanged: { addListener() {} } } } }); - await api.refreshOperationDelaySetting(); - assert.equal(api.getOperationDelayEnabled(), true); -}); - -test('operation delay uses stop-aware sleep and propagates stop errors', async () => { - const api = loadOperationDelayApi(); - await assert.rejects( - () => api.performOperationWithDelay({ stepKey: 'fill-profile', kind: 'click' }, async () => {}, { - sleep: async () => { throw new Error('流程已被用户停止。'); }, - getEnabled: () => true, - }), - /流程已被用户停止/ - ); -}); - -test('grouped split-code metadata still delays only once', async () => { - const events = []; - const api = loadOperationDelayApi(); - await api.performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'grouped-code' }, async () => { - for (let index = 0; index < 6; index += 1) events.push(`fill:${index}`); - }, { sleep: async (ms) => events.push(`sleep:${ms}`), getEnabled: () => true }); - assert.deepStrictEqual(events, ['fill:0', 'fill:1', 'fill:2', 'fill:3', 'fill:4', 'fill:5', 'sleep:2000']); -}); -``` - -Add `tests/operation-delay-injection.test.js` to enforce load order and excluded paths: - -```js -const test = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); - -function assertOrdered(list, before, after) { - assert.ok(list.includes(before), `missing ${before}`); - assert.ok(list.includes(after), `missing ${after}`); - assert.ok(list.indexOf(before) < list.indexOf(after), `${before} must load before ${after}`); -} - -test('manifest loads operation delay after utils only for covered auth/provider bundles', () => { - const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8')); - const authBundle = manifest.content_scripts.find((entry) => entry.js.includes('content/signup-page.js')).js; - assertOrdered(authBundle, 'content/utils.js', 'content/operation-delay.js'); - assertOrdered(authBundle, 'content/operation-delay.js', 'content/auth-page-recovery.js'); - assertOrdered(authBundle, 'content/operation-delay.js', 'content/signup-page.js'); - - const duckBundle = manifest.content_scripts.find((entry) => entry.js.includes('content/duck-mail.js')).js; - assertOrdered(duckBundle, 'content/utils.js', 'content/operation-delay.js'); - assertOrdered(duckBundle, 'content/operation-delay.js', 'content/duck-mail.js'); - - for (const pollingFile of ['content/qq-mail.js', 'content/mail-163.js', 'content/icloud-mail.js']) { - const bundle = manifest.content_scripts.find((entry) => entry.js.includes(pollingFile))?.js || []; - assert.equal(bundle.includes('content/operation-delay.js'), false, `${pollingFile} polling bundle must not load operation delay`); - } -}); - -test('dynamic covered injections load operation delay after utils', () => { - const expectations = [ - ['background.js', 'SIGNUP_PAGE_INJECT_FILES'], - ['background/steps/create-plus-checkout.js', 'PLUS_CHECKOUT_INJECT_FILES'], - ['background/steps/fill-plus-checkout.js', 'PLUS_CHECKOUT_INJECT_FILES'], - ['background/steps/paypal-approve.js', 'PAYPAL_INJECT_FILES'], - ['background/steps/gopay-approve.js', 'GOPAY_INJECT_FILES'], - ['background/mail-2925-session.js', 'MAIL2925_INJECT'], - ]; - for (const [file, constantName] of expectations) { - const source = fs.readFileSync(file, 'utf8'); - const match = source.match(new RegExp(`const\\s+${constantName}\\s*=\\s*\\[([^\\]]+)\\]`)); - assert.ok(match, `missing ${constantName} in ${file}`); - const block = match[1]; - assert.match(block, /'content\/utils\.js'[\s\S]*'content\/operation-delay\.js'/, `${file} must inject operation delay after utils`); - if (constantName === 'SIGNUP_PAGE_INJECT_FILES') { - assert.match(block, /'content\/operation-delay\.js'[\s\S]*'content\/auth-page-recovery\.js'/, 'auth recovery must load after operation delay'); - } - } -}); - -test('2925 provider reuse path also injects operation delay', () => { - const source = fs.readFileSync('background.js', 'utf8'); - const start = source.indexOf("if (provider === '2925')"); - assert.notEqual(start, -1, 'missing 2925 provider config'); - const end = source.indexOf("return { source: 'qq-mail'", start); - const block = source.slice(start, end); - assert.match(block, /inject:\s*\[[\s\S]*'content\/utils\.js'[\s\S]*'content\/operation-delay\.js'[\s\S]*'content\/mail-2925\.js'[\s\S]*\]/); -}); - -test('excluded platform verification paths do not load operation delay', () => { - for (const file of ['background/steps/platform-verify.js', 'background/panel-bridge.js']) { - const source = fs.readFileSync(file, 'utf8'); - assert.doesNotMatch(source, /content\/operation-delay\.js/); - } -}); -``` - -- [ ] **Step 2: Run the targeted tests and verify they fail** - -Run: `node --test tests/content-operation-delay.test.js tests/operation-delay-injection.test.js` - -Expected: FAIL because `content/operation-delay.js` does not exist and the injection arrays do not include it. - -- [ ] **Step 3: Write the minimal implementation** - -Create `content/operation-delay.js` with this public content-side API shape: - -```js -(function attachOperationDelay(root) { - const OPERATION_DELAY_MS = 2000; - const EXCLUDED_STEP_KEYS = new Set(['confirm-oauth', 'platform-verify']); - let operationDelayEnabled = true; - - function normalizeOperationDelayEnabled(value) { - return typeof value === 'boolean' ? value : true; - } - - function getOperationDelayEnabled() { - return operationDelayEnabled; - } - - async function refreshOperationDelaySetting() { - try { - const data = await root.chrome?.storage?.local?.get?.(['operationDelayEnabled']); - operationDelayEnabled = normalizeOperationDelayEnabled(data?.operationDelayEnabled); - } catch { - operationDelayEnabled = true; - } - return operationDelayEnabled; - } - - function shouldDelayOperation(metadata = {}) { - if (metadata.skipOperationDelay === true) return false; - if (metadata.enabled === false) return false; - if (EXCLUDED_STEP_KEYS.has(String(metadata.stepKey || '').trim())) return false; - return true; - } - - async function performOperationWithDelay(metadata = {}, operation, options = {}) { - const result = await operation(); - const enabled = typeof options.getEnabled === 'function' - ? normalizeOperationDelayEnabled(options.getEnabled()) - : getOperationDelayEnabled(); - if (shouldDelayOperation({ ...metadata, enabled })) { - const wait = options.sleep || root.sleep; - await wait(OPERATION_DELAY_MS); - } - return result; - } - - root.chrome?.storage?.onChanged?.addListener?.((changes, areaName) => { - if (areaName === 'local' && Object.prototype.hasOwnProperty.call(changes, 'operationDelayEnabled')) { - operationDelayEnabled = normalizeOperationDelayEnabled(changes.operationDelayEnabled?.newValue); - } - }); - - refreshOperationDelaySetting().catch(() => { operationDelayEnabled = true; }); - root.CodexOperationDelay = { OPERATION_DELAY_MS, normalizeOperationDelayEnabled, refreshOperationDelaySetting, getOperationDelayEnabled, shouldDelayOperation, performOperationWithDelay }; -})(typeof self !== 'undefined' ? self : globalThis); -``` - -Add `content/operation-delay.js` immediately after `content/utils.js` in these covered load paths: - -```js -['content/utils.js', 'content/operation-delay.js', 'content/auth-page-recovery.js', 'content/phone-country-utils.js', 'content/phone-auth.js', 'content/signup-page.js'] -['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js'] -['content/utils.js', 'content/operation-delay.js', 'content/paypal-flow.js'] -['content/utils.js', 'content/operation-delay.js', 'content/gopay-flow.js'] -['content/utils.js', 'content/operation-delay.js', 'content/duck-mail.js'] -['content/utils.js', 'content/operation-delay.js', 'content/mail-2925.js'] -``` - -For 2925, update both `background/mail-2925-session.js` `MAIL2925_INJECT` and the `provider === '2925'` injection object in `background.js` so an already-ready tab from the provider polling path still has `window.CodexOperationDelay` available before `ENSURE_MAIL2925_SESSION` runs. Do not add `content/operation-delay.js` to the manifest mail polling bundles, `background/steps/platform-verify.js`, or `background/panel-bridge.js`. - -- [ ] **Step 4: Run the targeted tests and verify they pass** - -Run: `node --test tests/content-operation-delay.test.js tests/operation-delay-injection.test.js` - -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add content/operation-delay.js manifest.json background.js background/steps/create-plus-checkout.js background/steps/fill-plus-checkout.js background/steps/paypal-approve.js background/steps/gopay-approve.js background/mail-2925-session.js tests/content-operation-delay.test.js tests/operation-delay-injection.test.js -git commit -m "feat: add shared operation delay gate" -``` - -## TASK_GROUP 3: Covered page-operation call-site migration - -### Task 4: Route OpenAI/auth page operations through the delay gate - -**Files:** -- Modify: `content/auth-page-recovery.js` -- Modify: `content/signup-page.js` -- Modify: `content/phone-auth.js` -- Test: `tests/auth-page-recovery.test.js` -- Test: `tests/step5-direct-complete.test.js` -- Test: `tests/step4-split-code-submit.test.js` -- Test: `tests/step7-phone-login-entry.test.js` - -- [ ] **Step 1: Write the failing tests** - -Extend the representative flow harnesses so they inject a fake `CodexOperationDelay.performOperationWithDelay` that records operation metadata, delegates to the wrapped operation, and records a `delay:2000` event after the operation. Required assertions: - -```js -assert.deepStrictEqual(authRetryEvents, [ - 'operation:auth-retry-click:start', - 'click:retry', - 'operation:auth-retry-click:end', - 'delay:auth-retry-click:2000', - 'poll-sleep:250', -]); -assert.equal(authRetryEvents.filter((event) => event.startsWith('delay:auth-retry-click')).length, 1); -assert.doesNotMatch(extractFunction(source, 'waitForRetryPageRecoveryAfterClick'), /performOperationWithDelay\(/); -``` - -```js -assert.deepStrictEqual(profileEvents, [ - 'operation:fill-name:start', - 'fill:name', - 'operation:fill-name:end', - 'delay:fill-name:2000', - 'operation:fill-birthday:start', - 'fill:birthday', - 'operation:fill-birthday:end', - 'delay:fill-birthday:2000', - 'operation:submit-profile:start', - 'click:complete', - 'operation:submit-profile:end', - 'delay:submit-profile:2000', -]); -assert.ok(profileEvents.indexOf('operation:fill-name:start') < profileEvents.indexOf('delay:fill-name:2000')); -``` - -```js -assert.deepStrictEqual(splitCodeEvents, [ - 'operation:split-code:start', - 'fill-code:0', - 'fill-code:1', - 'fill-code:2', - 'fill-code:3', - 'fill-code:4', - 'fill-code:5', - 'operation:split-code:end', - 'delay:split-code:2000', - 'operation:submit-code:start', - 'click:submit-code', - 'operation:submit-code:end', - 'delay:submit-code:2000', -]); -assert.equal(splitCodeEvents.filter((event) => event.startsWith('delay:split-code')).length, 1); -``` - -```js -assert.deepStrictEqual(runModeEvents.auto.map((event) => event.delayMs), runModeEvents.manual.map((event) => event.delayMs)); -assert.deepStrictEqual(runModeEvents.auto.map((event) => event.kind), runModeEvents.manual.map((event) => event.kind)); -``` - -- [ ] **Step 2: Run the targeted tests and verify they fail** - -Run: `node --test tests/auth-page-recovery.test.js tests/step5-direct-complete.test.js tests/step4-split-code-submit.test.js tests/step7-phone-login-entry.test.js` - -Expected: FAIL because the OpenAI/auth content scripts still call page operations directly and do not emit delay-gate events. - -- [ ] **Step 3: Write the minimal implementation** - -In `content/auth-page-recovery.js`, wrap only the retry button click and keep `waitForRetryPageRecoveryAfterClick` polling sleeps unwrapped: - -```js -const rootScope = typeof self !== 'undefined' ? self : globalThis; -const performOperationWithDelay = deps.performOperationWithDelay - || rootScope.CodexOperationDelay?.performOperationWithDelay; - -await performOperationWithDelay({ stepKey: options.stepKey || '', kind: 'click', label: 'auth-retry-click' }, async () => { - simulateClick(retryState.retryButton); -}); -``` - -In `content/signup-page.js` and `content/phone-auth.js`, add a local wrapper alias and route covered operations through it: - -```js -const performOperationWithDelay = (...args) => window.CodexOperationDelay.performOperationWithDelay(...args); - -await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-name' }, async () => { - fillInput(nameInput, fullName); -}); - -await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'hidden-sync', label: 'profile-dom-sync' }, async () => { - hiddenInput.value = value; - hiddenInput.dispatchEvent(new Event('input', { bubbles: true })); - hiddenInput.dispatchEvent(new Event('change', { bubbles: true })); -}); - -await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'grouped-code', label: 'split-code' }, async () => { - splitInputs.forEach((input, index) => fillInput(input, code[index] || '')); -}); - -await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'submit', label: 'submit-code' }, async () => { - simulateClick(submitButton); -}); -``` - -Apply the same wrapper to visible input fills, hidden DOM sync writes, dropdown selections, `simulateClick`/submit/continue actions, and phone-auth submit/resend/verification operations that mutate the page. Do not wrap wait-only helpers such as `waitForElement`, network retry waits, or polling sleeps. - -- [ ] **Step 4: Run the targeted tests and verify they pass** - -Run: `node --test tests/auth-page-recovery.test.js tests/step5-direct-complete.test.js tests/step4-split-code-submit.test.js tests/step7-phone-login-entry.test.js` - -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add content/auth-page-recovery.js content/signup-page.js content/phone-auth.js tests/auth-page-recovery.test.js tests/step5-direct-complete.test.js tests/step4-split-code-submit.test.js tests/step7-phone-login-entry.test.js -git commit -m "feat: delay OpenAI page operations" -``` - -### Task 5: Route checkout and payment page operations through the delay gate - -**Files:** -- Modify: `content/plus-checkout.js` -- Modify: `content/paypal-flow.js` -- Modify: `content/gopay-flow.js` -- Test: `tests/plus-checkout-create-wait.test.js` -- Test: `tests/paypal-approve-detection.test.js` -- Test: `tests/gopay-flow-content.test.js` -- Test: `tests/plus-checkout-billing-tab-resolution.test.js` - -- [ ] **Step 1: Write the failing tests** - -Extend the existing checkout/payment harnesses with gate event assertions instead of regex-only checks: - -```js -assert.deepStrictEqual(checkoutEvents.filter((event) => event.type === 'operation').map((event) => event.label), [ - 'select-payment-method', - 'fill-billing-address', - 'click-subscribe', -]); -assert.deepStrictEqual(checkoutEvents.filter((event) => event.type === 'delay').map((event) => event.ms), [2000, 2000, 2000]); -``` - -```js -assert.deepStrictEqual(paypalEvents, [ - 'operation:paypal-email:start', - 'fill:paypal-email', - 'operation:paypal-email:end', - 'delay:paypal-email:2000', - 'operation:paypal-approve:start', - 'click:paypal-approve', - 'operation:paypal-approve:end', - 'delay:paypal-approve:2000', -]); -``` - -```js -assert.deepStrictEqual(gopayEvents.filter((event) => event.type === 'delay').map((event) => event.label), [ - 'submit-phone', - 'submit-otp', - 'submit-pin', - 'click-continue', - 'click-pay-now', -]); -assert.equal(gopayEvents.some((event) => event.type === 'delay' && event.ms !== 2000), false); -``` - -- [ ] **Step 2: Run the targeted tests and verify they fail** - -Run: `node --test tests/plus-checkout-create-wait.test.js tests/paypal-approve-detection.test.js tests/gopay-flow-content.test.js tests/plus-checkout-billing-tab-resolution.test.js` - -Expected: FAIL because checkout and payment page operations still bypass the shared gate. - -- [ ] **Step 3: Write the minimal implementation** - -Route each covered DOM write or click in `content/plus-checkout.js`, `content/paypal-flow.js`, and `content/gopay-flow.js` through `window.CodexOperationDelay.performOperationWithDelay`. Required wrapped operation labels include these page actions when present: - -```js -['select-payment-method', 'fill-billing-address', 'fill-address-query', 'select-address-suggestion', 'click-subscribe'] -['paypal-email', 'paypal-password', 'paypal-dismiss-prompt', 'paypal-approve'] -['submit-phone', 'submit-otp', 'submit-pin', 'click-continue', 'click-pay-now'] -``` - -Keep terminal subscribe, approve, continue, and pay-now actions inside the wrapper so each receives a post-operation 2000 ms delay after the click completes. Do not wrap background polling waits, page-load waits, or network-only GPC helper API calls. - -- [ ] **Step 4: Run the targeted tests and verify they pass** - -Run: `node --test tests/plus-checkout-create-wait.test.js tests/paypal-approve-detection.test.js tests/gopay-flow-content.test.js tests/plus-checkout-billing-tab-resolution.test.js` - -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add content/plus-checkout.js content/paypal-flow.js content/gopay-flow.js tests/plus-checkout-create-wait.test.js tests/paypal-approve-detection.test.js tests/gopay-flow-content.test.js tests/plus-checkout-billing-tab-resolution.test.js -git commit -m "feat: delay checkout and payment page operations" -``` - -### Task 6: Wrap non-polling provider UI operations and protect polling cadence - -**Files:** -- Modify: `content/duck-mail.js` -- Modify: `content/mail-2925.js` -- Test: `tests/provider-operation-delay.test.js` -- Test: `tests/mail-polling-operation-delay-exclusion.test.js` - -- [ ] **Step 1: Write the failing tests** - -Add `tests/provider-operation-delay.test.js` for non-polling provider actions: - -```js -const test = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); - -test('Duck address generation routes the generator click through operation delay', () => { - const source = fs.readFileSync('content/duck-mail.js', 'utf8'); - assert.match(source, /performOperationWithDelay\([\s\S]*duck-generate-address/); -}); - -test('2925 session preparation routes through operation delay while cleanup stays delay-free', () => { - const source = fs.readFileSync('content/mail-2925.js', 'utf8'); - assert.match(source, /ENSURE_MAIL2925_SESSION[\s\S]*performOperationWithDelay/); - const deleteAllStart = source.indexOf("message.type === 'DELETE_ALL_EMAILS'"); - assert.notEqual(deleteAllStart, -1, 'missing DELETE_ALL_EMAILS handler'); - const deleteAllBlock = source.slice(deleteAllStart, source.indexOf('return false;', deleteAllStart)); - assert.doesNotMatch(deleteAllBlock, /performOperationWithDelay\(/); -}); -``` - -Add `tests/mail-polling-operation-delay-exclusion.test.js` to preserve polling cadence: - -```js -const test = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); - -function extractPollEmailHandler(source) { - const start = source.indexOf("message.type === 'POLL_EMAIL'"); - assert.notEqual(start, -1, 'missing POLL_EMAIL handler'); - const nextHandler = source.indexOf("message.type === '", start + 1); - return nextHandler === -1 ? source.slice(start) : source.slice(start, nextHandler); -} - -function extractFunction(source, name) { - const markers = [`async function ${name}(`, `function ${name}(`]; - const start = markers.map((marker) => source.indexOf(marker)).find((index) => index >= 0); - assert.notEqual(start, -1, `missing function ${name}`); - let signatureDepth = 0; - let signatureEnded = false; - let bodyStart = -1; - for (let i = start; i < source.length; i += 1) { - const ch = source[i]; - if (ch === '(') signatureDepth += 1; - if (ch === ')') { - signatureDepth -= 1; - if (signatureDepth === 0) signatureEnded = true; - } - if (ch === '{' && signatureEnded) { - bodyStart = i; - break; - } - } - assert.notEqual(bodyStart, -1, `missing body for ${name}`); - let depth = 0; - for (let i = bodyStart; i < source.length; i += 1) { - const ch = source[i]; - if (ch === '{') depth += 1; - if (ch === '}') { - depth -= 1; - if (depth === 0) return source.slice(start, i + 1); - } - } - throw new Error(`unterminated function ${name}`); -} - -test('mail polling handlers and cleanup handlers are not wrapped by operation delay', () => { - const protectedFunctions = { - 'content/mail-163.js': ['handlePollEmail', 'returnToInbox', 'openMailAndGetMessageText', 'deleteEmail', 'refreshInbox'], - 'content/qq-mail.js': ['handlePollEmail', 'refreshInbox'], - 'content/icloud-mail.js': ['openMailItemAndRead', 'refreshInbox', 'handlePollEmail'], - 'content/mail-2925.js': ['handlePollEmail', 'openMailAndGetMessageText', 'deleteCurrentMailboxEmail', 'openMailAndDeleteAfterRead', 'deleteAllMailboxEmails', 'refreshInbox'], - 'content/gmail-mail.js': ['refreshInbox', 'openRowAndGetMessageText', 'handlePollEmail'], - 'content/inbucket-mail.js': ['refreshMailbox', 'openMailboxEntry', 'deleteCurrentMailboxMessage', 'handlePollEmail'], - }; - - for (const [file, functionNames] of Object.entries(protectedFunctions)) { - const source = fs.readFileSync(file, 'utf8'); - const pollHandler = extractPollEmailHandler(source); - assert.doesNotMatch(pollHandler, /performOperationWithDelay\(/, `${file} POLL_EMAIL handler must stay delay-free`); - - for (const functionName of functionNames) { - const functionBody = extractFunction(source, functionName); - assert.doesNotMatch(functionBody, /performOperationWithDelay\(/, `${file} ${functionName} must stay delay-free`); - } - } - - const mail2925Source = fs.readFileSync('content/mail-2925.js', 'utf8'); - const deleteAllStart = mail2925Source.indexOf("message.type === 'DELETE_ALL_EMAILS'"); - assert.notEqual(deleteAllStart, -1, 'missing DELETE_ALL_EMAILS handler'); - const deleteAllBlock = mail2925Source.slice(deleteAllStart, mail2925Source.indexOf('return false;', deleteAllStart)); - assert.doesNotMatch(deleteAllBlock, /performOperationWithDelay\(/, '2925 cleanup must stay delay-free'); -}); - -test('mail polling bundles do not load operation delay module', () => { - const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8')); - for (const file of ['content/mail-163.js', 'content/qq-mail.js', 'content/icloud-mail.js']) { - const bundle = manifest.content_scripts.find((entry) => entry.js.includes(file))?.js || []; - assert.equal(bundle.includes('content/operation-delay.js'), false, `${file} bundle must not include operation delay`); - } -}); - -test('WhatsApp code reader remains polling-only and delay-free', () => { - const source = fs.readFileSync('content/whatsapp-flow.js', 'utf8'); - assert.doesNotMatch(source, /performOperationWithDelay\(/); -}); -``` - -- [ ] **Step 2: Run the targeted tests and verify they fail** - -Run: `node --test tests/provider-operation-delay.test.js tests/mail-polling-operation-delay-exclusion.test.js` - -Expected: FAIL in `tests/provider-operation-delay.test.js` because non-polling provider UI operations are not routed through the gate yet. Polling-exclusion assertions must pass or fail only if a prior task accidentally added delay to polling paths. - -- [ ] **Step 3: Write the minimal implementation** - -In `content/duck-mail.js`, wrap only the generator button click as one covered operation: - -```js -await window.CodexOperationDelay.performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'click', label: 'duck-generate-address' }, async () => { - if (typeof simulateClick === 'function') { - simulateClick(generatorButton); - } else { - generatorButton.click(); - } -}); -``` - -In `content/mail-2925.js`, wrap page DOM writes/clicks that run under `ENSURE_MAIL2925_SESSION`. Do not wrap `DELETE_ALL_EMAILS`, `POLL_EMAIL`, or shared helpers when the same helper is called from those handlers. - -Do not modify `POLL_EMAIL` handlers, `DELETE_ALL_EMAILS`, or their refresh/navigation/open/delete polling and cleanup loops in `content/mail-163.js`, `content/qq-mail.js`, `content/icloud-mail.js`, `content/mail-2925.js`, `content/gmail-mail.js`, or `content/inbucket-mail.js`. - -- [ ] **Step 4: Run the targeted tests and verify they pass** - -Run: `node --test tests/provider-operation-delay.test.js tests/mail-polling-operation-delay-exclusion.test.js` - -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add content/duck-mail.js content/mail-2925.js tests/provider-operation-delay.test.js tests/mail-polling-operation-delay-exclusion.test.js -git commit -m "feat: delay provider UI operations without slowing polling" -``` - -## TASK_GROUP 4: Exclusion checks, docs, and final regression - -### Task 7: Lock excluded flows, update docs, and run full verification - -**Files:** -- Modify: `README.md` -- Modify: `docs/使用教程/使用教程.md` -- Test: `tests/background-operation-delay-exclusions.test.js` -- Test: `tests/operation-delay-docs.test.js` -- Test: all existing `tests/*.test.js` - -- [ ] **Step 1: Write the failing tests** - -Add `tests/background-operation-delay-exclusions.test.js`: - -```js -const test = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); - -test('confirm-oauth and platform-verify stay free of operation delay gate calls', () => { - for (const file of [ - 'background/steps/confirm-oauth.js', - 'background/steps/platform-verify.js', - 'background/panel-bridge.js', - 'content/sub2api-panel.js', - ]) { - const source = fs.readFileSync(file, 'utf8'); - assert.doesNotMatch(source, /performOperationWithDelay\(/, `${file} must not call the operation delay gate`); - assert.doesNotMatch(source, /content\/operation-delay\.js/, `${file} must not inject operation delay`); - } -}); - -test('operation delay gate names exactly the two excluded step keys', () => { - const source = fs.readFileSync('content/operation-delay.js', 'utf8'); - assert.match(source, /confirm-oauth/); - assert.match(source, /platform-verify/); -}); -``` - -Add `tests/operation-delay-docs.test.js`: - -```js -const test = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); - -for (const file of ['README.md', 'docs/使用教程/使用教程.md']) { - test(`${file} documents operation delay`, () => { - const source = fs.readFileSync(file, 'utf8'); - assert.match(source, /操作间延迟/); - assert.match(source, /默认开启/); - assert.match(source, /2\s*秒/); - assert.match(source, /分格|OTP|验证码/); - assert.match(source, /邮箱|短信|轮询/); - assert.match(source, /confirm-oauth|platform-verify/); - }); -} -``` - -- [ ] **Step 2: Run the targeted tests and verify they fail** - -Run: `node --test tests/background-operation-delay-exclusions.test.js tests/operation-delay-docs.test.js` - -Expected: FAIL because docs do not mention the feature yet and the exclusion test file does not exist before this task. - -- [ ] **Step 3: Write the minimal implementation** - -Keep excluded flow code delay-free. Add a concise docs section to both `README.md` and `docs/使用教程/使用教程.md`: - -```md -### 操作间延迟 - -`操作间延迟` 默认开启。开启后,自动流程和手动单步在每个页面输入、选择、点击、提交、继续或授权操作完成后固定等待 2 秒;第一项页面操作不会提前等待。分格 OTP/验证码会先整组填完,然后只等待一次。 - -该开关不同于步间间隔,不影响邮箱轮询、短信/WhatsApp 轮询、后台 API、网络重试、后台定时器或存储持久化,也不影响 `confirm-oauth` 和 `platform-verify` 的交互节奏。 -``` - -- [ ] **Step 4: Run targeted and full verification** - -Run: `node --test tests/background-operation-delay-exclusions.test.js tests/operation-delay-docs.test.js` - -Expected: PASS. - -Run: `npm test` - -Expected: PASS for the full `node --test tests/*.test.js` suite. - -- [ ] **Step 5: Commit** - -```bash -git add README.md docs/使用教程/使用教程.md tests/background-operation-delay-exclusions.test.js tests/operation-delay-docs.test.js -git commit -m "docs: document operation delay behavior" -``` diff --git a/docs/superpowers/plans/2026-05-08-icloud-polling-addphone-fix-plan.md b/docs/superpowers/plans/2026-05-08-icloud-polling-addphone-fix-plan.md deleted file mode 100644 index 8725da4..0000000 --- a/docs/superpowers/plans/2026-05-08-icloud-polling-addphone-fix-plan.md +++ /dev/null @@ -1,85 +0,0 @@ -# iCloud Polling And Add Phone Handoff Fix Plan - -## Goal - -Implement the fix described in `docs/superpowers/specs/2026-05-08-icloud-polling-addphone-fix-design.md` on the Ultra8.0 operation-delay branch. - -## Architecture - -- Keep operation-delay excluded from polling. -- Adjust iCloud polling in the verification flow and/or iCloud polling payload construction so normal no-code polling uses a larger iCloud-specific attempt/time window. -- Reuse the existing shared phone verification flow when Step 7 lands on `/add-phone` and phone verification is enabled. -- Preserve fatal behavior when phone verification is disabled. - -## Implementation Granularity - -- Implementer granularity: TaskGroup. -- Reviewer granularity: TaskGroup. -- One checkpoint commit after both spec and code reviewers approve. - -## TaskGroup 1: iCloud polling and Step 7 add-phone handoff - -### Task 1: Expand iCloud polling window without using operation-delay - -Files likely involved: - -- `background/verification-flow.js` -- `content/icloud-mail.js` -- `tests/verification-flow-polling.test.js` -- `tests/icloud-mail-content.test.js` -- Existing operation-delay exclusion tests as regression coverage. - -Required behavior: - -- Add failing tests proving iCloud Step 4 no-code polling is not configured as one attempt under normal conditions. -- Add failing tests proving iCloud response timeout is large enough to cover the configured attempt count and interval. -- Implement the minimum production change that gives iCloud at least 5 configured polling attempts and a response timeout derived from the iCloud polling window. -- Do not wrap iCloud `POLL_EMAIL` handlers with `performOperationWithDelay`. -- Keep provider-specific fast-failure for actual transport non-response. - -Suggested verification: - -```bash -node --test tests/verification-flow-polling.test.js tests/icloud-mail-content.test.js tests/mail-polling-operation-delay-exclusion.test.js -``` - -### Task 2: Continue Step 7 direct add-phone through shared phone verification when enabled - -Files likely involved: - -- `content/signup-page.js` -- `background/steps/oauth-login.js` -- `background.js` -- `tests/background-step6-retry-limit.test.js` -- `tests/auto-run-step6-restart.test.js` -- `tests/step7-phone-login-entry.test.js` -- `tests/step8-retry-page-recovery.test.js` -- `tests/phone-verification-flow.test.js` - -Required behavior: - -- Add failing tests for Step 7 password submit landing on `/add-phone` with phone verification enabled. -- Add or preserve tests for Step 7 password submit landing on `/add-phone` with phone verification disabled. -- Add or preserve tests proving phone-login entry pages are not misclassified as add-phone. -- Reuse existing shared phone verification helpers; do not implement new phone/SMS submission logic in Step 7. -- Keep existing fatal propagation for shared phone verification failures. - -Suggested verification: - -```bash -node --test tests/background-step6-retry-limit.test.js tests/auto-run-step6-restart.test.js tests/step7-phone-login-entry.test.js tests/step8-retry-page-recovery.test.js tests/phone-verification-flow.test.js -``` - -## Final Verification - -Run these before completion: - -```bash -node --test tests/verification-flow-polling.test.js tests/icloud-mail-content.test.js tests/mail-polling-operation-delay-exclusion.test.js tests/operation-delay-injection.test.js tests/content-operation-delay.test.js -node --test tests/background-step6-retry-limit.test.js tests/auto-run-step6-restart.test.js tests/step7-phone-login-entry.test.js tests/step8-retry-page-recovery.test.js tests/phone-verification-flow.test.js -npm test -git diff --check -git status --short --branch --untracked-files=all -``` - -If `npm test` creates `scripts/__pycache__/`, remove it before final status. diff --git a/docs/superpowers/plans/2026-05-09-phone-signup-stale-email-verification-plan.md b/docs/superpowers/plans/2026-05-09-phone-signup-stale-email-verification-plan.md deleted file mode 100644 index c80543e..0000000 --- a/docs/superpowers/plans/2026-05-09-phone-signup-stale-email-verification-plan.md +++ /dev/null @@ -1,18 +0,0 @@ -# Phone Signup Stale Email Verification Fix Plan - -## Task 1: Detect Stale Email Verification During Phone SMS Wait - -- Add a TDD regression test in the existing phone verification test area. -- Simulate `completeSignupPhoneVerificationFlow()` or its SMS wait helper entering phone-signup Step 4 with a valid signup phone activation while the auth tab is already on `/email-verification`. -- The failing test must show the helper currently continues SMS polling instead of returning/failing fast. -- Implement the smallest background-side page-state check needed to stop SMS waiting when the auth tab is already on email verification before SMS submission. -- Return or throw a distinct stale-phone-signup condition that `executeStep4()` can use to fail fast without treating it as a successful post-SMS handoff. -- Preserve existing successful post-SMS `emailVerificationRequired` handoff behavior. - -## Task 2: Verification And Integration - -- Run targeted tests covering phone verification and Step 4 handoff. -- Run full `PYTHONDONTWRITEBYTECODE=1 npm test`. -- Clean generated `scripts/__pycache__` artifacts. -- Create one checkpoint commit only after spec and code reviewers approve. -- Push the resulting branch to the Git export refs and the existing GitHub PR fork branch after final verification. diff --git a/docs/superpowers/specs/2026-05-07-operation-delay-design.md b/docs/superpowers/specs/2026-05-07-operation-delay-design.md deleted file mode 100644 index 6767c80..0000000 --- a/docs/superpowers/specs/2026-05-07-operation-delay-design.md +++ /dev/null @@ -1,115 +0,0 @@ -# Operation Delay Design Spec - -## Goal - -Add a default-enabled, sidepanel-controlled 2 second delay between individual page automation operations in Ultra7.0. The feature reduces rapid page interaction failures on slow or unstable networks while keeping the behavior modular enough to port across future upstream versions. - -## Non-Goals - -- The feature does not add step-to-step delay as its primary behavior. -- The feature does not slow email polling, SMS polling, backend API calls, network retries, background timers, or storage persistence. -- The feature does not run inside `confirm-oauth` or `platform-verify` flows. -- The feature does not create a second tips or log console. -- The feature does not expose a user-editable delay duration in this version. -- The feature does not become Auto-only; manual single-step execution uses the same behavior. - -## Scope - -The feature covers page-side operations triggered by extension automation during Auto runs and manual single-step runs. A covered operation includes any automation action that writes page DOM state, changes page execution state, selects a value, clicks a page control, submits a form, continues a page flow, or authorizes a page flow. - -The delay duration is fixed at 2000 milliseconds. When the setting is enabled, automation waits 2000 milliseconds after each covered page operation completes. For ordinary chains, that pause separates one covered operation from the next. The first covered operation after a page is ready starts immediately. Terminal submit, continue, and authorization actions also receive the 2000 millisecond pause after they complete, even when no later covered operation follows. - -The feature treats grouped split verification-code entry as one covered operation. A grouped split code operation fills the full group without per-character or per-field pauses, then receives one 2000 millisecond delay after the group is filled. - -## Actors And Entry Points - -- Sidepanel user: views and toggles the `操作间延迟` setting in the existing sidepanel settings surface. -- Auto runner: executes the full automation flow and applies the same operation delay setting. -- Manual step runner: executes a selected step from the sidepanel and applies the same operation delay setting. -- Content scripts: perform covered page operations and enforce the delay gate after those operations complete. -- Bottom tips/log console: reports setting changes and setting recovery failures through the existing `log-area` flow. - -## Functional Behavior - -### Sidepanel Setting - -- The sidepanel displays a boolean `操作间延迟` switch in the existing settings surface. -- The switch defaults to enabled when no valid persisted value exists, including first install and upgrades from versions that did not store this setting. -- The switch label communicates that enabled mode waits 2 seconds after page input, selection, click, submit, continue, and authorization operations. -- Toggling the switch persists the new value through the existing settings channel. -- Toggling the switch writes one bottom tips/log entry that states the new enabled or disabled state. -- The enabled-state log message includes the 2 second delay duration. -- The disabled-state log message states that operation delay is off. - -### Operation Delay Runtime - -- The operation delay gate runs after each covered page operation while the setting is enabled. -- The operation delay gate is skipped while the setting is disabled. -- The operation delay gate is skipped inside steps whose step key is `confirm-oauth` or `platform-verify`. -- The operation delay gate is skipped for background-only work that does not execute page operations. -- A setting change during an active run takes effect after the current covered page operation finishes. A delay already in progress completes unless the user stops the flow. -- A user Stop request interrupts an active 2 second operation delay and propagates the same stop error behavior used by existing content-script waits. - -### Covered Operation Examples - -- Filling a visible input field is one covered operation. -- Filling a hidden synchronization field is one covered operation. -- Updating page execution state through DOM writes or synthetic input/change events is one covered operation. -- Selecting a dropdown value is one covered operation. -- Clicking a button, link, checkbox, or role-based control is one covered operation. -- Submitting a form through click, dispatch, or request-submit behavior is one covered operation. -- Clicking a continue or authorization button is one covered operation unless the current step key is excluded. -- Filling split OTP or split verification-code fields is one grouped covered operation. - -### Excluded Work Examples - -- Waiting for an element to appear is not a covered operation. -- Polling mailbox or SMS provider APIs is not a covered operation. -- Sleeping between retry attempts is not a covered operation. -- Writing logs is not a covered operation. -- Persisting sidepanel settings or run state in extension storage is not a covered operation. -- Background tab bookkeeping is not a covered operation. - -## State And Data Contracts - -- `operationDelayEnabled` is a persisted boolean setting. -- If the persisted value is absent or invalid, the runtime uses `operationDelayEnabled = true`. -- A valid persisted `false` remains `false` across later restores and upgrades. -- The fixed delay duration is 2000 milliseconds. -- The delay duration is not user-editable. -- Runtime page-operation code reads the current enabled state at the start of the delay gate for a covered operation. -- The exclusion set contains exactly these step keys: `confirm-oauth`, `platform-verify`. -- The sidepanel writes setting-change feedback to the existing bottom tips/log stream. It does not write to a separate console. -- Content-side delay enforcement is centralized behind a shared operation delay gate so new page-operation call sites have one integration point. - -## Error Handling And Edge Cases - -- Missing persisted setting value resolves to enabled. -- Invalid persisted setting value resolves to enabled. -- If setting restoration fails in the sidepanel, the sidepanel displays the default enabled state and writes one warning entry to the bottom tips/log stream. -- If saving a user toggle fails, the sidepanel writes one error entry to the bottom tips/log stream and keeps the visible switch aligned with the last confirmed persisted state. -- If content-side code cannot resolve the setting for a covered operation, the operation delay gate uses enabled mode and a 2000 millisecond delay. -- If a Stop request arrives during an operation delay, the delay rejects with the existing stop error and the current flow stops through the existing stop path. -- Split verification-code input remains a single grouped operation even when the page exposes multiple fields. -- `confirm-oauth` and `platform-verify` perform no operation delay even when they click or submit page controls. - -## Acceptance Criteria - -- Opening the sidepanel with no persisted setting shows `操作间延迟` enabled. -- Disabling the setting persists `operationDelayEnabled = false` and writes a bottom tips/log entry stating that operation delay is off. -- Enabling the setting persists `operationDelayEnabled = true` and writes a bottom tips/log entry stating that page operations wait 2 seconds. -- Auto execution and manual single-step execution both honor the same setting. -- With the setting enabled, a profile-fill flow starts the first covered page operation immediately after the page is ready, waits 2 seconds after that operation completes, then starts the next covered operation, waits 2 seconds after that operation completes, then starts the next one, and waits 2 seconds after the terminal submit or complete action completes. -- With the setting enabled, a split verification-code flow fills the full group as one covered operation, then waits 2 seconds after the group is filled. It does not wait 2 seconds between individual split fields. -- With the setting disabled, covered page operations run without the 2 second operation delay. -- Email polling, SMS polling, backend retries, background timers, and storage persistence keep their existing cadence. -- `confirm-oauth` and `platform-verify` keep their existing interaction cadence. -- A Stop request during a 2 second operation delay stops the flow through the existing stop behavior. - -## Locked Assumptions - -- Ultra7.0 uses `data/step-definitions.js` as the canonical source for step keys. -- The existing bottom `log-area` flow is the only tips/log feedback target for this feature. -- The first implementation uses one boolean sidepanel switch and one fixed 2000 millisecond duration. -- The implementation uses a shared content-side operation delay gate instead of scattered business-step sleeps. -- Future page operation call sites use the shared operation delay gate for covered operations. diff --git a/docs/superpowers/specs/2026-05-08-icloud-polling-addphone-fix-design.md b/docs/superpowers/specs/2026-05-08-icloud-polling-addphone-fix-design.md deleted file mode 100644 index 3db92bc..0000000 --- a/docs/superpowers/specs/2026-05-08-icloud-polling-addphone-fix-design.md +++ /dev/null @@ -1,60 +0,0 @@ -# iCloud Polling And Add Phone Handoff Fix Spec - -## Goal - -Fix two Ultra8.0 runtime failures on top of the migrated operation-delay branch: - -- iCloud verification email polling must wait longer and perform more than one check before declaring the code missing. -- Step 7 password login that lands directly on `https://auth.openai.com/add-phone` must continue into the existing phone verification flow when phone verification is enabled, instead of failing immediately. - -## Non-Goals - -- Do not add operation-delay waits to email polling, SMS polling, backend retries, or background timers. -- Do not change non-iCloud provider polling cadence unless required by shared test harness setup. -- Do not duplicate phone verification logic inside Step 7. -- Do not treat ordinary phone-login entry pages as add-phone pages. -- Do not continue add-phone when phone verification is disabled or when the existing phone verification flow reports a fatal error. - -## iCloud Polling Behavior - -The current failure log shows Step 4 iCloud polling using fast timeout protection with a 22 second response timeout and a `最多 1 次` polling payload, then failing after roughly 3 seconds. This is too short for the user's active iCloud-only workflow. - -For iCloud verification-code polling: - -- Step 4 signup-code polling and Step 8 login-code polling must not be reduced to a single attempt under normal no-code conditions. -- The iCloud polling payload must use an iCloud-specific minimum attempt floor of at least 5 attempts. -- The iCloud polling interval must allow delayed mail arrival and may be longer than the current 3 second effective no-code window. -- The tab-message response timeout for iCloud polling must be large enough for the configured iCloud polling attempts and intervals to complete, plus a small transport margin. -- Transport-unresponsive protection must remain: if the iCloud content script itself does not respond, the flow should fail or recover through the existing transport-error path instead of hanging indefinitely. -- Logs must accurately describe the actual iCloud attempt count and wait window. -- Operation-delay remains excluded from all email polling paths. - -## Add Phone Handoff Behavior - -When Step 7 password submission transitions directly to an add-phone page: - -- If phone verification is enabled, Step 7 must not throw the current fatal `提交密码后页面直接进入手机号页面,未经过登录验证码页` error. -- The flow must hand off to the existing shared phone verification implementation used by later steps. -- After successful phone verification, the automation should continue through the existing OAuth/login flow instead of stopping the run. -- If phone verification is disabled, the flow must keep a clear fatal error telling the user that add-phone requires phone verification to be enabled. -- Existing fatal conditions from the shared phone verification flow remain fatal, including unavailable phone numbers, provider failures, phone already exists, and user stop. -- Existing phone-login entry detection must remain separate from add-phone detection. - -## Acceptance Criteria - -- Reproducing the reported iCloud Step 4 path no longer emits `开始轮询 iCloud 邮箱(最多 1 次)` for normal iCloud no-code polling. -- iCloud Step 4 no-code polling performs at least 5 configured attempts before the missing-code failure path. -- iCloud polling response timeout is derived from the configured iCloud attempt count and interval rather than being capped to a too-short single-attempt window. -- Mail polling operation-delay exclusion tests still pass. -- Existing non-iCloud provider tests continue to pass. -- Step 7 password-submit to `/add-phone` with phone verification enabled continues into the shared phone verification flow. -- Step 7 password-submit to `/add-phone` with phone verification disabled still fails clearly. -- Step 7 phone-login entry pages are still treated as login phone input, not add-phone. -- Relevant targeted tests and full `npm test` pass. - -## Verification Targets - -- `node --test tests/verification-flow-polling.test.js tests/icloud-mail-content.test.js` -- `node --test tests/background-step6-retry-limit.test.js tests/auto-run-step6-restart.test.js tests/step7-phone-login-entry.test.js tests/step8-retry-page-recovery.test.js tests/phone-verification-flow.test.js` -- `node --test tests/mail-polling-operation-delay-exclusion.test.js tests/operation-delay-injection.test.js tests/content-operation-delay.test.js` -- `npm test` diff --git a/docs/superpowers/specs/2026-05-09-phone-signup-stale-email-verification-design.md b/docs/superpowers/specs/2026-05-09-phone-signup-stale-email-verification-design.md deleted file mode 100644 index 5b4a999..0000000 --- a/docs/superpowers/specs/2026-05-09-phone-signup-stale-email-verification-design.md +++ /dev/null @@ -1,26 +0,0 @@ -# Phone Signup Stale Email Verification Fix Design - -## Scope - -Fix phone-signup Step 4 behavior when OpenAI has already moved the auth tab to an email verification page before a phone SMS verification code is submitted. - -## Required Behavior - -- If phone-signup Step 4 is waiting for an SMS code and the auth tab is still on the phone verification page, keep polling the configured SMS provider. -- If phone-signup Step 4 is waiting for an SMS code but the auth tab has moved to `/email-verification`, stop waiting for SMS immediately. -- The `/email-verification` transition before SMS submission means the phone number is not usable for a new phone-signup account in this run. The flow must fail fast with a clear, actionable error so the current auto-run attempt can rotate/restart instead of hanging. -- If phone-signup Step 4 submits a valid phone SMS code and then OpenAI moves to `/email-verification`, preserve the existing handoff behavior and continue mailbox verification. -- Do not add operation-delay to SMS polling, email polling, backend retries, or background timers. -- Do not broaden iCloud-specific polling behavior to other mail providers. - -## Error Semantics - -The stale email-verification bailout must be distinguishable from SMS-provider timeout, invalid SMS code, user stop, and normal post-SMS email-verification handoff. - -The user-facing message should explain that OpenAI moved to email verification before phone SMS verification completed, likely because the phone number is already associated with an existing account or login path, and the current number should be replaced. - -## Testing Requirements - -- Add a regression test that simulates phone-signup Step 4 polling while the auth tab is already on `/email-verification`; the test must fail before the implementation and pass after. -- Add or update tests to prove normal post-SMS email-verification handoff still works. -- Run targeted phone Step 4 tests and the full suite before completion.