Skip to content

Commit 2feee83

Browse files
docs(ui): ui/AGENTS.md becomes a normative core; local-dev and flaky-test narrative move to the contributor guides (#27576)
domain:devx#2 · os-justin · session_01QSeKYmTYfaPbLQD4BJfomW Part of #25919 (PR 2: the `ui/AGENTS.md` restructure. PR 1 landed as #26023.) Ruling-ref: 6435359095 · 6485892110 Clause-②: no Metadata-protocol: none skip-changeset: the diff touches only `ui/AGENTS.md` and `ui/.claude/**`. No package ships either. ## 维护者速读 - 改了什么:`ui/AGENTS.md` 收成规范核心。本地开发、端口和多栈流程原样搬进 `console-development.md`,flaky 测试的成因、读数和前情原样搬进新的 `guides/flaky-tests.md`,核心里各留一行指针。新增两条写法规则,不加门禁。修了 5 处过期说法,并按契约复核的 S1 修正了 console 指南里的 MSW 说法。 - 为什么改:裁决 6435359095 和 6485892110 定了这次重构。核心只写规则,叙事放到主题页,过期说法会把 agent 带错路。 - 风险与回滚:只动文档,不动代码。27 行 ⛔ / never 全数留在核心。其中 26 行逐字节不变,第 27 行(#8)只按派发令补了 `packages/` 路径前缀。回滚就是 revert 这个 PR。 - 先看:① `ui/AGENTS.md` §9 的两行新指针,加上 §5 新增的 #12 和 #13;② `guides/console-development.md` 末尾新增的「Local dev and per-agent stacks」节;③ `guides/flaky-tests.md`;④ `ui/CLAUDE.md` 的一句话;⑤ `console-development.md`「Debugging & Browser Simulation Strategy」的 Rule #1(S1)。 - 两分钟验证:在仓根跑 `git diff origin/main...HEAD --color-moved=dimmed-zebra -- ui`。预期搬迁的行显示为 moved。没标 moved 的增删行只有下文列出的修正行和新写的指针、规则行。 - 需要你做的:审批这个 Tier H PR(`ui/AGENTS.md`、`ui/.claude/**`)。 ## What changes `ui/AGENTS.md` goes from 36,140 bytes over 261 lines to **32,591 bytes over 221 lines**, measured at head `dbb5e1cb`. There is no cap (6485892110). **Stays in the core:** §1–§8 in full, plus the rule-bearing parts of §9: Housekeeping, the test-running subsection, and the 测试纪律 rules. Each rule appears once. Where a rule already named its instrument (`pnpm check:*`, a test file, a lint rule), the pointer stays where it was. No pointer was added to an existing line, because that would reword it. **New text:** - §5 gets two writing rules from 6407900763 item 2, written as prose: - **#12**: a comment in a workflow or a script is one line of why plus a link. - **#13**: code comments state invariants, not incident history. Each says that no gate holds it and that existing comments are not swept. Neither contains ⛔ or "never", so the census is unchanged. - `ui/CLAUDE.md`: its one line now reads "AGENTS.md is the source of truth for what differs under ui/ — it and the topic pages it links to are one instruction set, maintained in one place, under the root AGENTS.md" (6407900763 item 1; contract-review S3 6502595750, ruling 6503029051; surface widened by the seat for this file only). No rule is restated there. - One pointer line per topic page, in §9: one under 测试纪律 and one in a new `### Local dev and per-agent stacks` stub. - One row in the `objectui-contributor` SKILL.md topic table for the new guide. ### Moved-block map (byte-preserving) | From `ui/AGENTS.md` | To | |---|---| | §9 `### Local dev — console UI ↔ backend (read before debugging UI)`: the heading and its 7 bullets | `ui/.claude/skills/objectui-contributor/guides/console-development.md`, under a new `## Local dev and per-agent stacks` | | §9 `### 每个 agent 独立测试栈(端口隔离,多 agent 并行的推荐做法)`: the whole subsection, 4 steps and the **纪律** paragraph | same new section, after the block above | | §9 测试纪律, the opening paragraph (the 976 ms / 1000 ms measurement) | new `ui/.claude/skills/objectui-contributor/guides/flaky-tests.md` | | §9 测试纪律, the 「失败现场会直接指认根因」 bullet and the 「顺手体检」 bullet | `guides/flaky-tests.md` | | §9 测试纪律, the 「前情:objectui#3010」 line | `guides/flaky-tests.md` | Nothing went to `docs/postmortems/`. The only incident write-up in the file is the one-line 前情 note, and it went with its section's reasoning into `flaky-tests.md`. **Move proof.** Run `git diff origin/main...HEAD --color-moved=plain -- ui`. Of the 134 changed lines (blank lines included), 103 are marked moved. A second script compared the 57 removed lines against the added lines. Every removed non-blank line reappears byte-identical, except 9, and those 9 are exactly the stale-claim lines below. The full `dimmed-zebra` output is reproducible with the command in 维护者速读. ### Stale claims fixed (the only rewording; reading 6455483141) 1. **The `:3001/_console` claim (moved to `console-development.md`).** It now says the backend serves `/_console` on its own port (`:3000` in the single-stack setup). The built Console is still named as real, by its package: 「仓根 `@objectstack/console` 包的 `dist/`」. The package spelling replaces the literal root path because `ui/scripts/check-skills-paths.mjs` resolves a `packages/` token against `ui/`. Once the line moved under its scan root, that gate went red on the root-relative path, which is unbuilt in a fresh checkout anyway. The package spelling keeps the claim and resolves cleanly. 2. **§7 MSW bullet.** It now says there is no `@objectstack/plugin-msw` package and no `msw` dependency in the workspace. It points to the real-backend stacks in the console guide and keeps the instruction not to hand-roll fetch interceptors. 3. **§2 styling path.** `skills/objectui/rules/styling.md` now says "at the repository root". The file's preamble reads paths relative to `ui/`. 4. **§5 #8.** `app-shell/src/urlParams.ts` → `packages/app-shell/src/urlParams.ts`. This is a census line. The dispatch ordered this fix, so the line differs from before by exactly those 9 bytes. 5. **§6 registry snippet.** The path is now `packages/core/src/registry/Registry.ts`. The invented `registerComponent` / `resolveComponent` Map snippet is replaced by an abridged view of the real `Registry` class (`register`, `registerLazy`, `get`) and the `ComponentRegistry` singleton. The renderer-loop snippet's one call to the removed `resolveComponent` becomes `ComponentRegistry.get(schema.type)`, which is what `SchemaRenderer.tsx` calls. Without that change the snippet would name a function that no longer exists anywhere in the file. 6. **Contract-review S1 (6502595750, ruling 6503029051).** `console-development.md` § Debugging & Browser Simulation Strategy no longer claims a mock mode or an MSW plugin. Rule #1 is retitled «No MSW plugin — debug against a real backend» and points to «Local dev and per-agent stacks». The MSWPlugin Configuration bullet is removed. Rule #2 Alignment now names the `/api` proxy target (`DEV_PROXY_TARGET`) instead of a mock server. The SKILL.md row says «debugging against a real backend». ### ⛔ / never census (case-insensitive `⛔|never`, one count per line) | | lines | bytes of those lines | sha256 of the line set (first 16) | |---|---|---|---| | before, `origin/main` `72b08eec` (identical to `ad3a640e35` for this file) | **27** | 16,789 | `5c6693bd42200de7` | | after, head `261449bc` (byte-identical to `ccc751c5` and `dbb5e1cb` for this file, 32,591 B), all in the core | **27** | 16,798 | `4a9f74261366934b` | 27 = 27: 26 lines byte-identical and in the same order, plus 1 that differs only by the ordered path fix. That line is §5 #8 ("UI state lives where it can survive"): its path `app-shell/src/urlParams.ts` gained the 9-byte prefix `packages/`, which is why the line set grows 16,789 → 16,798 bytes. The fix was ordered by the dispatch (reading 6455483141) because the old path does not resolve from `ui/`, and the seat kept it (OQ2 → A). No ⛔ / never line moved out of the core. ### Dependent reader `scripts/check-agent-test-spelling.mjs` `COUNTER_EXAMPLE_FILES` names `ui/AGENTS.md` for trap two (陷阱二). That bullet stays in the core with the rest of the test-running subsection, so the script and its test need no edit. `pnpm check:agent-test-spelling` passes and reports `1 declared counter-example(s)`. ## Tests - `pnpm --dir ui exec vitest run --maxWorkers=2 scripts/__tests__/check-skills-paths.test.ts scripts/__tests__/check-shell-escape-residue.test.ts scripts/__tests__/check-doc-links.test.ts`: Test Files 3 passed (3), Tests 196 passed (196). The lock printed `VERDICT command-exit 0`. - Under `ui/`, three scripts exit 0: - `node scripts/check-skills-paths.mjs`: 86/87 stated paths resolve, 1 baselined. - `node scripts/check-shell-escape-residue.mjs` - `node scripts/check-doc-links.mjs`: "Links are valid across 14 scan roots." ## Acceptance notes - **The core is still large.** It is about 32.6 KB, because the 27 whole ⛔ / never lines alone are 16.8 KB and §5 carries most of them. The test-running subsection and the i18n-regex bullet stay whole. The first holds the dependent reader's counter-example and a 「上面的形态」 back-reference. The second states a rule inside the incident that proved it, and splitting it would reword a line. - **Stale claims outside this card's list were left alone:** - The published `skills/objectui/guides/data-integration.md` still names `@objectstack/plugin-msw` (the seat files it). The `console-development.md` half was fixed here as S1. - The heading «Which console `:3001/_console` serves» in root-side `docs/agents/build-and-test.md` still names `:3001`. - §7's surviving census line still says "matches the mock server". Census lines stay verbatim. - **Trailers.** The commits carry the model-free pair the root `AGENTS.md` requires. ## Verification <details><summary>dispatch-gates --commands (13 derived) and --ran reconciliation, head ccc751c (re-run after the ui/CLAUDE.md commit and the origin/main merge; same 13 commands, all exit 0)</summary> ``` node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 pnpm check:agent-test-spelling :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:pm-governed-merges :: exit 0 pnpm check:published-files :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:tier-file-adoption :: exit 0 pnpm check:undeclared-dep-imports :: exit 0 pnpm check:watch-hint-literal :: exit 0 ✓ dispatch-gates --ran: 13 derived famil(ies) accounted for — 13 run, 0 NOT-MEASURED (a DERIVED zero — all 13 recorded an exit code and none of them is 3). ``` The first pass ran before `pnpm install` and four families exited 3 (PREREQUISITE NOT MET). All 13 were re-run after the install and are shown above. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01QSeKYoC3JrEqdXJwZ69A8m --- _Generated by [Claude Code](https://claude.ai/code/session_01QSeKYoC3JrEqdXJwZ69A8m)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a5d7098 commit 2feee83

5 files changed

Lines changed: 83 additions & 64 deletions

File tree

‎ui/.claude/skills/objectui-contributor/SKILL.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ so shipping these files cost every customer session tokens it could never use.
1717

1818
| Topic | File |
1919
|---|---|
20-
| Console app: architecture, metadata-admin registry, routing, MSW debugging, retired names | [`guides/console-development.md`](./guides/console-development.md) |
20+
| Console app: architecture, metadata-admin registry, routing, debugging against a real backend, retired names | [`guides/console-development.md`](./guides/console-development.md) |
2121
| Shadcn-synced primitives are overwritten by the sync script — wrap, never edit | [`rules/no-touch-zones.md`](./rules/no-touch-zones.md) |
22+
| Flaky tests under `ui/`: why the cause is a module load inside a bounded window, how the failure names it | [`guides/flaky-tests.md`](./guides/flaky-tests.md) |
2223

2324
Customer-facing ObjectUI authoring knowledge stays in `skills/objectui/`.

‎ui/.claude/skills/objectui-contributor/guides/console-development.md‎

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -498,21 +498,69 @@ function MyWidget({ schema }) {
498498

499499
## Debugging & Browser Simulation Strategy
500500

501-
When debugging the simulated browser environment (e.g., `apps/console` in mock mode), strict adherence to the official toolchain is required.
501+
When debugging the Console in a browser, strict adherence to the official toolchain is required.
502502

503-
### Rule #1: Official MSW Integration
503+
### Rule #1: No MSW plugin — debug against a real backend
504504

505-
- **Startup:** Use `@objectstack/plugin-msw` to initialize the mock API server. Do NOT write custom fetch interceptors or manual mock servers unless absolutely necessary.
506-
- **Configuration:** Ensure the `MSWPlugin` is configured with the correct `baseUrl` (e.g., `/api/v1`) to match the client's expectations.
505+
- **Startup:** There is no `@objectstack/plugin-msw` package and no tracked `package.json` declares `msw`, so there is no mock API server to start. Run the Console against a real backend, as the 「Local dev and per-agent stacks」 section below describes. Do NOT write custom fetch interceptors or manual mock servers unless absolutely necessary.
507506

508507
### Rule #2: Client Data Fetching
509508

510509
- **Data Access:** Always use `@objectstack/client` for data fetching. Do not use raw `fetch` or `axios` directly in components.
511-
- **Alignment:** Verify that the client's `baseUrl` matches the mock server's configuration.
510+
- **Alignment:** Verify that the client's `baseUrl` matches the backend the Console's `/api` proxy targets (`DEV_PROXY_TARGET`, `http://localhost:3000` by default).
512511

513512
### Rule #3: Upstream Fixes First
514513

515514
- **Principle:** If you encounter a bug or limitation in the official packages (`@objectstack/*`):
516515
- **Action 1:** Do NOT rely solely on local workarounds (monkey-patching) in the app.
517516
- **Action 2:** Prompt the user to modify the source code in the official packages (if available in the workspace) or report the issue.
518517
- **Reasoning:** We prioritize fixing the core engine over patching individual apps.
518+
519+
## Local dev and per-agent stacks
520+
521+
Moved out of `ui/AGENTS.md`, which keeps a pointer here.
522+
523+
### Local dev — console UI ↔ backend (read before debugging UI)
524+
525+
- **启动前端**:仓根 `pnpm --filter @object-ui/console dev`(Vite,固定 **:5180**,见 `apps/console/vite.config.ts`)。console 编进去的 `@objectstack/spec`/`client` 就是本 workspace 的构建,不是 npm 上的发布版。
526+
- **后端默认连 `:3000`**:vite `/api` proxy → `DEV_PROXY_TARGET || http://localhost:3000`。**要测哪个后端就把它跑在 :3000**(仓根 `PORT=3000 pnpm dev:crm`,或 `PORT=3000 pnpm dev` = showcase)。经 `pnpm --filter @object-ui/console dev` 传 `DEV_PROXY_TARGET` env **不**可靠(不一定透传到 vite 子进程);要把 console 指向别的后端端口,`cd ui/apps/console` 后内联设 env 才灵(见下「每个 agent 独立测试栈」)。
527+
- 后端在它自己的端口上(单栈即 :3000)以 `/_console` 服务的是**已构建的** console(仓根 `@objectstack/console` 包的 `dist/`),**不是** `ui/` 的 src;改 src 必须用上面的 :5180 dev 服务验证(或在仓根跑 `pnpm console:build` 重新构建 —— 慢)。
528+
- 路由用 app 的 **`name`**(如 `showcase_app`,不是 `showcase`);直接 URL 进对象可能落到 Setup「对象不存在」——先经启动台/应用切换进入该 app 设好 currentApp。
529+
- **清 localStorage 会登出**(session token 存 localStorage;首页应用磁贴也读 localStorage 缓存,跨会话会显示过期的 app 列表)。
530+
- better-auth 用 `localhost`(非 `127.0.0.1`)否则 Invalid origin。
531+
- 浏览器验证:优先用桌面 preview(`preview_*`,配置在 `ui/.claude/launch.json`,如其中的 `console`);chrome-devtools MCP 掉线时切 preview。
532+
533+
### 每个 agent 独立测试栈(端口隔离,多 agent 并行的推荐做法)
534+
535+
上面是**单栈**约定(后端 :3000 + 前端 :5180);多 agent 并行时端口会打架。要彻底隔离,每人起**自己端口**的一整套栈(后端 + console),互不干扰:
536+
537+
1. **后端 —— `--fresh` 临时库 + 自选端口**,数据与端口都隔离、退出自动清(从仓根):
538+
```bash
539+
# showcase(带 showcase_field_zoo / showcase_account 等):
540+
cd examples/app-showcase
541+
pnpm exec objectstack dev --seed-admin --fresh -p 4010
542+
# --fresh 临时 sqlite 库(os.tmpdir()/objectstack-dev-*),SIGINT/SIGTERM 自动删,绝不碰别人的 .objectstack/data/dev.db
543+
# -p <port> 监听端口(等价 OS_PORT / PORT;dev 模式端口被占会自动顺延)
544+
# --seed-admin 默认开;空库播种 admin@objectos.ai / admin123
545+
# CRM: cd examples/app-crm && pnpm exec objectstack dev --seed-admin --fresh -p <port>
546+
# 要持久库(跨重启保留):去掉 --fresh,改用 --database "file:/tmp/agent-<port>.db"(或 OS_DATABASE_URL)
547+
```
548+
549+
2. **Console —— 自选端口 + 指向你的后端**(从仓根):
550+
```bash
551+
cd ui/apps/console
552+
DEV_PROXY_TARGET=http://localhost:4010 pnpm exec vite --port 5190 --strictPort
553+
# 必须 cd 进 ui/apps/console 让 env 直达 vite;用 `pnpm --filter … dev` 传 env 不可靠
554+
# --strictPort 端口被占直接报错,绝不静默顺延撞到别人的端口上
555+
```
556+
自检:`curl 'http://localhost:5190/api/v1/data/showcase_account?$top=2'`(经 console 代理打到你的 :4010,应返回 Northwind/Contoso)。
557+
558+
3. **Live E2E —— 全 env 参数化指向你的端口**(见 `playwright.live.config.ts` / `e2e/live/global-setup.ts`;在 `ui/` 里执行):
559+
```bash
560+
LIVE_APP_URL=http://localhost:5190 LIVE_API_URL=http://localhost:4010 pnpm test:e2e:live
561+
# 凭据用 LIVE_EMAIL / LIVE_PASSWORD 覆盖(默认 admin@objectos.ai / admin123)
562+
```
563+
564+
4. **桌面 preview**:给 `ui/.claude/launch.json` 加一条你自己的 console 配置,仿现成的 `console-build-test`(`cd apps/console && DEV_PROXY_TARGET=http://localhost:<后端> pnpm dev --port <前端> --strictPort`)。
565+
566+
**纪律**:端口自选空闲高位(用前 `lsof -i :PORT` 确认没人占);收工只按**自己记下的 PID** 收(根 `AGENTS.md` Multi-agent discipline 第 8 条),`--fresh` 临时库随进程退出自动清;**绝不动 :3000 / :5180**(通常是别人的单栈)。
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Flaky tests under `ui/` — why, and how the failure names its cause
2+
3+
Moved out of `ui/AGENTS.md` § 测试纪律, which keeps the rules; this page keeps the reasoning, the measurements and the incident behind them.
4+
5+
单跑稳定绿、全量并行下偶发红的测试,**根因几乎总是同一个**:一段**无界的模块加载被计入了一个有界的窗口**。满并行下 Vite 的 transform 管线是饱和的(单 `dom-heavy` 项目就 ~60s transform),实测一次首包 `import()` 可达 **976ms** —— 已吃掉 RTL `findBy`/`waitFor` 默认 **1000ms** 预算的 97.6%。于是断言在和模块加载器抢时间,红绿取决于机器负载而不是被测代码。
6+
7+
- 失败现场会直接指认根因:dump 里若仍是 Suspense fallback(如 `Loading report renderer…`),就是本条;**hook 超时表现为「失败的*文件* + 0 个失败*测试*」**(其余全部 skipped),别误读成断言失败。
8+
- 顺手体检:别把 `keysOf(x)` 这类整体计算写在 `.filter()` 谓词里(每个元素重算一遍)。`all-locales-key-parity` 曾因此 7.51s,提升出谓词后 **25ms**。
9+
10+
前情:objectui#3010(一次修掉五个文件,含一个已被「超时调到 15s」糊过、满负载下依然 15021ms 超时的例子)。

0 commit comments

Comments
 (0)