Skip to content

Commit fd663cd

Browse files
committed
🔀 Merge latest upstream main into PR rework branch
2 parents 76106ea + 9e31780 commit fd663cd

17 files changed

Lines changed: 1495 additions & 516 deletions

.github/copilot-instructions.md

Lines changed: 0 additions & 30 deletions
This file was deleted.

.github/copilot-instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../AGENTS.md

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ rules.
1717
| add or change localized content | [`docs/translation.md`](docs/translation.md) + matching `docs/references/terminology-<locale>.md` when present |
1818
| add, edit, reorganize, or review tracked contributor Markdown (`AGENTS.md`, `docs/*`, `.github/*.md`, package/source-local READMEs) | [`docs/DOC-MAINTENANCE.md`](docs/DOC-MAINTENANCE.md) — if you can't grep it on this branch, don't claim it |
1919
| open or update a pull request | [`docs/pull-request.md`](docs/pull-request.md) |
20-
| manually confirm a feature works | [`docs/verification.md`](docs/verification.md)use a throwaway scratch script against the built extension, not the committed suite |
20+
| manually confirm a feature works | [`docs/verification.md`](docs/verification.md)drive a throwaway session against the built extension, not the committed suite |
2121

2222
For tasks matching multiple rows, read every applicable owner before that work; do not front-load unrelated
2323
docs. For tasks matching none, inspect `docs/README.md` and nearby implementation/tests before inventing a rule

CLAUDE.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

docs/DOC-MAINTENANCE.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ of sanitization patterns can otherwise look like matches — so don't rely on a
8888

8989
| Doc | Owns |
9090
| --- | --- |
91-
| [`../AGENTS.md`](../AGENTS.md) | Engineering principles, architecture quick-map, and shared agent contract. `CLAUDE.md` imports it; `.github/copilot-instructions.md` is a Copilot-specific router that points to it without duplicating shared policy. |
91+
| [`../AGENTS.md`](../AGENTS.md) | Engineering principles, architecture quick-map, and shared agent contract. `CLAUDE.md` and other compatibility entry points are symlink aliases; they do not own a separate policy. |
9292
| [`develop.md`](./develop.md) | The concrete "how": commands, structure, style, i18n, commit/PR. Testing → [`references/develop-testing.md`](./references/develop-testing.md). |
9393
| [`pull-request.md`](./pull-request.md) | The PR body: structure and evidence rules. The human-facing template stays lightweight. |
9494
| [`design.md`](./design.md) | The design system; tokens, component palette, and layout/motion/state/a11y patterns → the three `references/design-*.md`. |
@@ -99,7 +99,6 @@ of sanitization patterns can otherwise look like matches — so don't rely on a
9999
| [`translation.md`](./translation.md) | Translation / localization single source of truth. |
100100
| [`DOC-MAINTENANCE.md`](./DOC-MAINTENANCE.md) | This guide: organization rules, fact-check / anti-drift discipline, policy-consistency checks — across every tracked contributor Markdown, not just `AGENTS.md` + `docs/*`. |
101101
| [`README.md`](./README.md) | The reader-facing index: what each doc contains and when to read it. |
102-
| [`.github/copilot-instructions.md`](../.github/copilot-instructions.md) | Copilot-specific entry point and tool-specific behavior/router only; shared facts route to `AGENTS.md` instead of being copied. |
103102
| Package-local `README.md` (e.g. `packages/message/README.md`, `packages/filesystem/README.md`) | That package's purpose, boundaries, entry points, and local gotchas — not a duplicate of repo-wide architecture or coding policy. |
104103

105104
This table records **ownership boundaries** — which doc a given fact belongs in. It is deliberately *not* the

docs/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66

77
| 文档 | 说明 |
88
| --- | --- |
9-
| [`../AGENTS.md`](../AGENTS.md) | 工程原则、架构速览与 AI coding agent 的共享约定所有者;`CLAUDE.md` 通过 `@AGENTS.md` 导入,`.github/copilot-instructions.md` 保留 Copilot 专属路由并指向它,不复制共享约定|
9+
| [`../AGENTS.md`](../AGENTS.md) | 工程原则、架构速览与 AI coding agent 的共享约定所有者;`CLAUDE.md` `.github/copilot-instructions.md` 均通过符号链接复用它,不维护独立契约|
1010
| [`develop.md`](./develop.md) | 开发规范:命令、目录结构、编码风格、UI/主题、i18n、提交/PR 流程;测试设计/清理口径与运行机制(含 Vitest 性能)拆到 [`references/develop-testing.md`](./references/develop-testing.md)**写代码前先读。** |
1111
| [`pull-request.md`](./pull-request.md) | PR 描述指南:代理与贡献者使用的详细章节、按变更类型取舍规则、验证与审查信息要求。 |
1212
| [`design.md`](./design.md) | 设计系统参考:主题机制、shadcn 组件选型、新建页面配方总览;令牌完整值拆到 [`references/design-tokens.md`](./references/design-tokens.md),组件清单拆到 [`references/design-components.md`](./references/design-components.md),布局/响应式/动效/状态/无障碍范式拆到 [`references/design-patterns.md`](./references/design-patterns.md)**做页面/对话框/区块前先读。** |
1313
| [`../e2e/README.md`](../e2e/README.md) | E2E 测试台手册:两条赛道(committed smoke / gitignored scratch)、浏览器与 profile 隔离、fixtures 与 helper 清单、协议 mock、`E2E_*` 环境变量、产物与失败排查路径。**跑 / 写 E2E 或一次性验证脚本前先读。** |
14-
| [`verification.md`](./verification.md) | 功能验证指南:用一次性 scratch 脚本驱动真实扩展做端到端验证(不跑全量 E2E、不加永久用例);报告模板拆到 [`references/verification-report-template.md`](./references/verification-report-template.md),调试 FAQ 拆到 [`references/verification-debugging.md`](./references/verification-debugging.md)**验证改动是否真正跑通时读。** |
14+
| [`verification.md`](./verification.md) | 功能验证指南:启一个常驻会话(默认无头、可多 worktree 并发)逐条命令驱动真实扩展,只在需要复现顺序/时序时才写 spec,不跑全量 E2E、不加永久用例;驱动方法(GM API in-page self-test、SW 消息、主题)拆到 [`references/verification-methods.md`](./references/verification-methods.md),报告模板拆到 [`references/verification-report-template.md`](./references/verification-report-template.md),调试 FAQ 拆到 [`references/verification-debugging.md`](./references/verification-debugging.md)**验证改动是否真正跑通时读。** |
1515
| [`architecture.md`](./architecture.md) | 内部原理总览:多进程模型、消息传递;各子系统深入拆到 [`references/architecture-services.md`](./references/architecture-services.md)(服务层)、[`references/architecture-data.md`](./references/architecture-data.md)(数据层)、[`references/architecture-gm-api.md`](./references/architecture-gm-api.md)(GM API)、[`references/architecture-execution.md`](./references/architecture-execution.md)(脚本执行)、[`references/architecture-build.md`](./references/architecture-build.md)(构建管线)、[`references/architecture-agent.md`](./references/architecture-agent.md)(Agent 子系统)。 |
1616
| [`cloud-sync.md`](./cloud-sync.md) | 云同步实现说明:同步文件语义、主流程、状态合并、provider 差异、错误分类、retry 策略和维护注意事项。 |
1717
| [`DOC-MAINTENANCE.md`](./DOC-MAINTENANCE.md) | 文档维护与事实核对指南:组织规则、逐条核对清单、跨文档政策一致性核对、隐私清理、以及在 resolved final tree 上的复核方法,覆盖全部 tracked 的 agent/contributor Markdown(不止 `AGENTS.md` + `docs/*`,还包括 `.github/*.md`、package-local README)。**改/审文档前先读。** |
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Verification methods
2+
3+
[`../verification.md`](../verification.md) chooses the form; this file holds the patterns that reach behaviour the UI does not expose directly. Each is written twice where the two forms differ: driving a session ([`../../e2e/README.md`](../../e2e/README.md#8-verification-sessions)) and authoring a spec. Failures and gotchas are [`verification-debugging.md`](verification-debugging.md)'s.
4+
5+
## Script execution: GM APIs and injection
6+
7+
Making a userscript actually inject and run needs two things: the `userScripts` permission granted, and the permission prompt answered.
8+
9+
A session grants `userScripts` at `start`, so injection works out of the box. It does **not** auto-approve prompts — a GM API that needs a grant opens `confirm.html`, which you answer like any other page:
10+
11+
```bash
12+
node e2e/drive.mjs pages # 找到 confirm.html
13+
node e2e/drive.mjs use <i>
14+
node e2e/drive.mjs click "[data-testid=confirm-duration-permanent]"
15+
node e2e/drive.mjs click "[data-testid=confirm-allow]"
16+
```
17+
18+
In a spec, `testWithUserScripts` and `autoApprovePermissions` solve both ([`../../e2e/README.md`](../../e2e/README.md#3-harness-chain)) — import them rather than re-deriving the launch dance.
19+
20+
### The in-page self-test pattern
21+
22+
A userscript runs assertions in the page and prints a summary line the harness parses from the console. The bundled scripts in [`../../example/tests/`](../../example/tests/) do this; the line varies by script, and each emits a `通过`/`Passed` and a `失败`/`Failed` count:
23+
24+
```
25+
总计: 12 | 通过: 12 | 失败: 0 # inject_content_test.js / sandbox_test.js (combined line)
26+
总测试数: 12 / 通过: 12 / 失败: 0 # gm_api_sync_test.js / gm_api_async_test.js (counts on separate lines)
27+
Total: 12 | Passed: 12 | Failed: 0 # window_message_test.js (English)
28+
```
29+
30+
In a session there is nothing to wire up — the collector already recorded the line, whichever context printed it (a `@background` script prints from `src/sandbox.html`, not from a page):
31+
32+
```bash
33+
node e2e/drive.mjs console 200 | grep -E "(通过|Passed)[::] *[0-9]+"
34+
```
35+
36+
In a spec, collect and assert on it — this regex matches all three layouts:
37+
38+
```ts
39+
const logs: string[] = [];
40+
let passed = -1;
41+
let failed = -1;
42+
page.on("console", (msg) => {
43+
const text = msg.text();
44+
logs.push(text);
45+
const pass = text.match(/(通过|Passed)[::]\s*(\d+)/);
46+
const fail = text.match(/(失败|Failed)[::]\s*(\d+)/);
47+
if (pass) passed = parseInt(pass[2], 10);
48+
if (fail) failed = parseInt(fail[2], 10);
49+
});
50+
// ...navigate to the target page, then:
51+
expect(failed, logs.join("\n")).toBe(0);
52+
expect(passed).toBeGreaterThan(0);
53+
```
54+
55+
For a new GM API, write a small self-test userscript in the same style. In a session, `node e2e/drive.mjs install <file.user.js>` installs it through the Service Worker and `node e2e/drive.mjs console` shows the summary line the script printed; in a spec, use `installScriptByCode`. Keep the script inside the scenario directory — it is verification scaffolding, not a committed example.
56+
57+
## Behaviour fired from extension UI
58+
59+
The self-test pattern covers only what a userscript observes in the page. Some behaviour is fired from extension UI — a `GM_registerMenuCommand` menu is triggered from the popup. Clicking that button is not drivable ([`verification-debugging.md`](verification-debugging.md#common-gotchas)); sending the message it sends is.
60+
61+
Clients talk to the Service Worker via `chrome.runtime.sendMessage({ action, data })`, where `action` is `<client-prefix>/<method>` and the reply is wrapped as `{ code, data }` — payload is `res.data`, a truthy `code` means error ([`../../packages/message/client.ts`](../../packages/message/client.ts)). Read the tab coordinates you need (`tabId`/`frameId`/`documentId`) from a prior `getPopupData` call.
62+
63+
```ts
64+
// from a chrome-extension:// page (e.g. options.html); poll until the async registration shows up
65+
const res = await chrome.runtime.sendMessage({
66+
action: "serviceWorker/popup/getPopupData",
67+
data: { tabId, url },
68+
});
69+
const script = res.data.scriptList.find((s) => s.menus.some((m) => m.name === "your-menu"));
70+
await chrome.runtime.sendMessage({
71+
action: "serviceWorker/popup/menuClick",
72+
data: { uuid: script.uuid, menus: script.menus }, // menus carry the target tabId/frameId/documentId
73+
});
74+
```
75+
76+
From a session the same call is one command, since `eval` already runs on an extension page:
77+
78+
```bash
79+
node e2e/drive.mjs open options
80+
node e2e/drive.mjs eval "const [tab] = await chrome.tabs.query({active:true,lastFocusedWindow:true}); if (!tab?.id || !tab.url) throw new Error('no active tab'); const r = await chrome.runtime.sendMessage({action:'serviceWorker/popup/getPopupData', data:{tabId:tab.id, url:tab.url}}); return r.data.scriptList"
81+
```
82+
83+
This drives the real SW → content → sandbox → callback path, behaviourally identical to the popup button, which discards the DOM event and calls the same message. It is a substitution: the verdict row names it and says the popup's own click path was not covered.
84+
85+
## A UI change across light and dark theme
86+
87+
The theme is stored in `localStorage` under `lightMode` with value `"light"` / `"dark"` / `"auto"` ([`../../src/pages/components/theme-provider.tsx`](../../src/pages/components/theme-provider.tsx), and [`../../src/pages/common.ts`](../../src/pages/common.ts), which reads the same key during pre-render to avoid a theme flash). Setting it before the page's own scripts run — `context.addInitScript` — is what applies the theme on first paint instead of flashing the default.
88+
89+
Confirm that timing for a `chrome-extension://` page in your own setup before relying on it: `addInitScript` timing relative to an extension page's bootstrap can differ from a normal web page. Capture one screenshot per theme as separate evidence; one theme's screenshot does not show the other renders correctly.
90+
91+
A session has no `addInitScript` hook of its own, so set the key and reload — the pre-render read in `common.ts` then picks it up before first paint:
92+
93+
```bash
94+
node e2e/drive.mjs eval "localStorage.setItem('lightMode','dark'); return location.reload()"
95+
node e2e/drive.mjs shot settings-dark
96+
```

0 commit comments

Comments
 (0)