Skip to content

fix(types): the filter-builder doc reader strips comments before it locates the terminator - #9182

Merged
os-tesla merged 2 commits into
mainfrom
claude/issue-9073-doc-union-members-comment-order
Sep 11, 2026
Merged

os-tesla merged 2 commits into
mainfrom
claude/issue-9073-doc-union-members-comment-order

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Fixes #9073

Clause-②: no — declared from this diff. No accept set moves: doc 14, mirror 14, before and after. The published doc is byte-identical at BASE and at HEAD; the defect was in the reader, never in the surface it reads.

Scope note on the placeholder spelling below: this repository has measured GitHub deleting tag-shaped fragments from a stored body, backticks and fences included, so every placeholder here is written as a WORD (KEY, IFACE) rather than in angle brackets.

Item 1 — the false diagnosis

docUnionMembers located the terminating semicolon in the RAW interface block and stripped line comments only afterwards. A semicolon inside a comment on one of the union's own rows therefore ended the slice early.

Re-derived here rather than taken on trust, on today's bytes: packages/types/src/__tests__/filter-builder-mirror-6939.test.ts lines 290-302 at 4784bb34f match the card, terminator search first, .replace(/\/\/.../g, '') second.

The verbatim false diagnosis, measured before the fix — one comment injected into the published doc's type?: union row, nothing else changed:

AssertionError: this mirror accepts `type` members content/docs/components/complex/filter-builder.mdx never published — the mirror widened past the authority.: expected [ 'boolean', 'select', 'status', …(3) ] to deeply equal []

+ [ "boolean", "select", "status", "lookup", "master_detail", "user" ]

and, underneath it, the member list at 8 instead of 14 (text number currency percent rating date datetime time). The mirror had not widened. The reader had under-read the doc, and then named the mirror.

⭐ That is the whole card: a checker whose purpose is to be believed announced a change nobody made. After the fix the same input reads 14 and the verdict is empty.

The repair, and the trap inside it. Comments come off FIRST and at is computed on the stripped block. Stripping SHORTENS the block, so an index taken before the strip addresses a different place after it; an implementation that strips but carries the old at across starts its slice inside the union and drops the LEADING members instead — a second wrong answer reached from the same one-line reading. Both halves are pinned.

Controlled inputs, said plainly. No declaration in this tree carries a semicolon inside a comment today, so the fixtures are the real doc with exactly one comment rewritten, anchored to a literal row so a doc that moves THROWS instead of quietly testing a straw man.

Item 2 — the logic control named a mode it cannot catch

The floor test's docblock claimed the logic control guards against "a reader that stopped at the first line of a multi-line union". It cannot: logic is itself single-line, so a first-line-only reader reads it correctly.

Measured, not restated — the reader was mutated into a line-bounded one and the file re-run: 6 tests red, every one of them through the zero-members throw raised out of documentedTypes(), and the logic leg itself stayed green (the floor test failed at leg (2), not leg (1)). The docblock now names that mechanism and says what the control does not cover.

Item 3 — two wording corrections

⛔ No existing leg is weakened or deleted. The one expression that moved is the widening verdict, lifted into widenedPastTheDoc() plus a WIDENED_MESSAGE constant so the controlled input travels through the SAME comparison and the SAME sentence the pin renders, rather than a copy that could drift. The pin's assertion and its message are byte-identical in effect.

Ablation — three legs, each with two-way on-disk proof

Every leg: mutate, prove it reached disk (marker counts 1 to 0 and 0 to 1, git hash-object moved, HEAD blob unmoved), run, restore, prove the restore by STATE (git hash-object back to the HEAD blob, git diff HEAD empty) rather than by an exit code. Each script carried trap restore EXIT INT TERM with absolute paths.

leg mutation result
A — the fix itself strip-first reverted to locate-first 2 red, exactly the two intended: the false-diagnosis pin (rendering WIDENED_MESSAGE verbatim) and the unterminated-union pin. Control green.
B — the coordinate trap at taken on the raw block, used on the stripped one 5 red, including the "comment BEFORE the key" leg. ⚠️ Honest reading: that leg is not the sole detector — the pre-existing equality pin and floor test also redden. It is the only one whose comment sits before at by construction, and the only one that names the hazard.
C — first-line-only reader union body cut at the first newline 6 red, all via the zero-members throw. This is the measurement behind item 2.

Blobs: HEAD 40ff1031, leg A 38ce70e8, leg B e32f224c, leg C 45874497; restored to 40ff1031 on every leg with git diff HEAD empty.

⚠️ The CONTROL test is named as a control because it cannot tell leg A's two worlds apart — green before and after. It is not counted as evidence. It does fire under legs B and C, so it is not inert.

One branch whose outcome moves, recorded rather than left to be discovered

A union whose ONLY semicolon lives inside a comment now raises is unterminated (loud) where it previously returned a silently truncated set. The throw's wording is untouched, and the two throws the floor test pins at lines 476-477 (no longer declares, no interface IFACE block) are unaffected — both still pass. The other two branches are unchanged: at cannot be created or destroyed by stripping (a comment at that indentation begins with //, so it can never spell a \n KEY: match), and parsed to ZERO members now fires strictly LESS often.

Verification — all at final commit 54a2fb796

what command result
the pin file + its render half pnpm exec vitest run packages/types/ examples/schema-catalog/test/filter-builder-mirror-6939.test.tsx scripts/__tests__/markdown-test-inputs.test.ts exit 0 — 177 files, 3498 tests passed (through os-verify-lock.sh, VERDICT command-exit 0)
types type-check pnpm --filter @object-ui/types type-check exit 0. It runs tsconfig.test.json, and --listFiles confirms the edited file is inside that project — so this is a measurement, not a NOT-MEASURED
lint eslint . in packages/types (what turbo run lint invokes) exit 0 — 245 files, 0 errors
changeset presence node scripts/check-changeset-presence.mjs exit 0, empty-frontmatter declaration accepted
changeset no-major / claims check:changeset-no-major, check:changeset-claims exit 0
control bytes pnpm check:control-bytes exit 0, 7372 files
comment-mask corpus pnpm check:comment-mask-corpus exit 0, within the objectui#7882 ceiling (1 file, pre-existing)
doc-example shared reader pnpm check:doc-example-readers exit 0
new cross-file line citations pnpm check:new-line-citations exit 0, 0 new citations
unreferenced sources pnpm check:unreferenced-sources exit 0
markdown-test-inputs registry node scripts/markdown-test-inputs.mjs --audit exit 0 before and after — the edited file is a declared reader of the filter-builder mdx and still reads exactly that
governed surface node scripts/check-governed-queue-guard.mjs --test ... NOT GOVERNED, 2 paths against 5 surfaces

Every exit code above was captured by redirecting to a file first and reading $? on the next statement, never across a pipe.

Declared narrowing. Lint ran for @object-ui/types only, not all 39 packages. Three pieces of evidence, not two: (1) the population is eslint's own — eslint . inside the package, the exact script turbo run lint runs; (2) the count is read from --format json: 245 files; (3) invariance — eslint.config.js configures no type-aware linting (no parserOptions.project, no projectService), so a file's verdict is a function of that file plus the shared config, and this diff touches one file inside this package and no config. It therefore cannot move the verdict of any untouched file. The whole farm is CI's run.

Acceptance notes

  • Six sites in this tree hand-roll a private line-comment stripper (/\/\/[^\n]*/g), two of them also hand-rolling block-comment removal, while scripts/js-comment-mask.mjs declares itself "the ONE answer" to that question and names both hand-rolled families as having drifted with silent, opposite failure modes. Two package tests already import maskComments from it. This diff MOVES one of those six but does not convert it: converting is a different defect from the ordering one this card names, and it would add a cross-tree import to a package that has none. Filed separately — see the report.
  • eslint . run from the repository ROOT reports 95 errors on a byte-clean origin/main. That is not a defect: pnpm lint is turbo run lint, which runs eslint . per package, and no one runs the root form. Noted so the next reader who tries it is not misled.

Owed, deliberately not patched

Card item 2 and item 3 each have a half that lives in the body of merged PR objectui#9069, which this diff cannot reach:

  • its line 56 repeats the same false claim about the logic control;
  • its line 78 says "the three new tests" where the count went 21 to 23 — two new plus one rewritten. Reproduced exactly: grep -c 'it(' gives 21 at that PR's base 7f27bc54 and 23 at its head db804c0c.

⛔ Not patched, and the reason is measured rather than preferential: AGENTS.md records that a body PATCH unconditionally appends a second attribution footer and downgrades the session-URL form. On a merged historical record that trades one wrong sentence for a visibly damaged artifact, and the repository's own guidance is that a rewrite destroys a correct card. Left for the maintainer to decide; both sentences are quoted above so no one has to re-derive them.

维护者速读(草稿)

改了什么 —— 一个测试内嵌的文档读取器,先找分号、后剥注释,顺序反了。改成先剥注释再定位,并保证下标在同一坐标系里。另外修正该文件对自己的两处错误描述。

为什么改 —— 这个缺陷的要害不是"数少了",而是假阳:联合体被注释里的一个分号截断成 8 个成员后,断言报的是"mirror 变宽了",而 mirror 一个字节都没动。一个专门用来被相信的检查器,自信地指错了人 —— 比它沉默更糟。

风险与代价(含回滚) —— 改动只在一个测试文件加一份空 frontmatter 的 changeset,不发布任何包,已发布的文档一个字节未动,接受集合前后都是 14 = 14。唯一行为变化:只有注释里带分号、块内再无分号的联合体,现在会响亮地抛 is unterminated,而不是静默返回一个被截断的集合 —— 方向是从"静默错"到"响亮错"。回滚即 revert 这两个 commit,无迁移、无数据、无下游。

席位意见 —— (留空,待席位定稿)

你要做的 —— 这是 draft,按仓库规矩等 CI;Clause-② 为 no,不需要 contract review。上面"Owed, deliberately not patched"一节有两句话住在已合并的 PR objectui#9069 正文里,是否要去改那份历史记录,请您定。


Generated by Claude Code

…ocates the terminator

objectui#9073. `docUnionMembers` located the terminating `;` of a union in the
RAW interface block and stripped line comments only afterwards, so a `;` inside
a comment on one of the union's own rows ended the slice early: the published
doc's fourteen-member `type?:` union read as eight, and the mirror/doc pin then
announced "the mirror widened past the authority" for six members the doc does
publish.

The defect is that false positive, not the under-count — the verdict sends a
reader looking for a widening nobody made.

Comments now come off first and `at` is computed on the stripped block: the
strip shortens it, so an index taken before it addresses a different place
after it, and carrying one across drops the union's LEADING members instead.
Both hazards are pinned, plus a control that is green in both worlds and a
characterisation of the one branch whose outcome moves (a union whose only `;`
lives in a comment is now `is unterminated`, loudly, rather than silently
truncated).

The published doc is untouched: the defect was in the reader, never in the
surface it reads.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
objectui#9073 items 2 and 3, both in the same file as item 1.

The floor test's `logic` control named a mode it cannot catch: "a reader that
stopped at the first line of a multi-line union". `logic` is itself a
single-line union, so a first-line-only reader reads it correctly and that leg
stays green. Measured by mutating the reader into a line-bounded one — six
tests redden, and every one of them through the zero-members throw raised out
of `documentedTypes()`, not through this control. The docblock now names that
mechanism and says plainly what the control does not cover.

The equality pin's message offered its exception as "a spelling a LATER ruling
RETIRED from this doc", citing objectui#4814. That retirement is 2026-08-16/17
and batch #88 is 2026-09-02, so it PREDATES the batch. The exception never
depended on the order: it now reads "ANY ruling".

⛔ No existing leg is weakened or removed, and no accept set moves.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 51 chunks) 3091.0 KB 3134.8 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-DArDpxOG.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 500.22KB 114.68KB
core (index.js) 8.28KB 3.31KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 209.38KB 57.92KB
fields (index.js) 247.14KB 62.34KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.21KB 2.26KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.95KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 131.18KB 34.62KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 252.19KB 65.42KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.94KB 41.04KB
plugin-grid (index.js) 211.56KB 57.50KB
plugin-kanban (index.js) 46.10KB 14.33KB
plugin-list (index.js) 112.58KB 27.65KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.54KB 3.31KB
plugin-view (index.js) 84.42KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

内容 PASS。 ⏸️ arm 等自身 test shard 转绿。两个 open question 我在下面裁了。

⚠️ 先更正我自己派单里的一处错

我在 A4 里写「:476 / :477 已经有测试钉住前两条的措辞」,并把 is unterminated 也算进了「既有行为」。那是错的,你测出来了:那两条 pin 覆盖的是 no longer declares 与 docInterfaceBlock 的 no interface IFACE block,⛔ 不包括 is unterminated —— 它本来就没被钉。
⇒ 我的派单把一条未钉的分支说成已钉。你没有继承它,而是去数了。记在这里,下一个读者不用重走。

⭐ A2 —— 那个坑你正面处理了,而且解法是「一个坐标系」而不是「补一次偏移」

const block = docInterfaceBlock(doc, iface).replace(/\/\/[^\n]*/g, '');
const at  = block.indexOf(`\n  ${key}:`);   // ← 在剥过的块上算
const end = block.indexOf(';', at);
const body = block.slice(at, end);          // ← 不再需要第二次剥

⇒ 剥注释提到最前,at / end / slice 全在同一个坐标系里,第二次 .replace 也一并消失了。⛔ 不是「先定位再把索引往回挪」那种能算对一次、下次再算错的形状。
你在注释里把两侧性写死了(「stripping SHORTENS the block, so an index taken before the strip addresses a different place after it」)—— 下一个人改这里时会先读到这句话。

⭐⭐ A3 —— 假诊断是逐字复现的,而且走的是同一条句子

AssertionError: this mirror accepts `type` members content/docs/.../filter-builder.mdx never published
              — the mirror widened past the authority.
  expected [ 'boolean', 'select', 'status', …(3) ] to deeply equal []
  member list: EIGHT (text number currency percent rating date datetime time) vs the doc's fourteen

⇒ 卡片说的 8 精确复现了,而且 ⭐ 你把 widenedPastTheDoc() / WIDENED_MESSAGE 提取出来让 pin 渲染同一条句子,⛔ 不是抄一份措辞 —— 所以「pin 断言的那句话」和「读者真会看到的那句话」不可能分叉。

⚠️ 而且你声明了这是受控输入:树里今天没有任何声明的注释里带 ;,所以 fixture 是「真实文档 + 改写一条注释」,并锚在一个文档一动就 throw 的字面行上。⛔ 没有把受控输入伪装成树里的真实读数。

⭐⭐ 三条消融腿各自证明了不同的东西 —— 这是本卡最值钱的结构

腿 变异 红 它证明了什么
A 还原成「先定位后剥」 恰好 2 本卡的修复是必要的
B at 在原块上取、用在剥过的块上 5(含「注释在 key 之前」那条) 错误的修复会被抓住
C union body 砍到第一个换行 6,全经 zero-members throw,而 logic 那条自己绿 ⭐ 这就是卡片 item 2 的测量

⇒ 腿 B 是我在派单里点名要的那个坑的可证伪形式;腿 C 顺手把 item 2(「logic 控制项命名了一种它抓不到的模式」)从断言变成了读数。

⭐ 你比派单多做了两件事,两件都对

① 卡片有三项,我的派单只摘了第一项。 你把 item 2 与 item 3a(「a LATER ruling」引 objectui#4814,而它早于 batch #88)在同一文件里一并改正。⛔ 同文件同主题不算扩面,正确。
② A5 你先按派单查了 222 个文件(控制项亮:residue-namespaces-3546.test.tsx:161 命中 KanbanImpl.tsx)得到零,然后又把范围扩到全仓 —— 扩出来 3 个命中,其中 scripts/markdown-test-inputs.mjs:321 是一个真实的注册表,声明该文件是某文档页的 reader。你跑了 --audit,改动前后都 exit 0。
⇒ ⭐ 派单只要求查「测试目录下的源文本 pin」;真正会咬人的那一条在 scripts/ 下。把查询范围本身当成可疑对象,这是对的。

两个 open question —— 我的裁决

OQ1(merged PR objectui#9069 的 body 里还有两句同样的假话):走 C,⛔ 不是 A 也不是 B。

  • ⛔ 不 PATCH(B):你给的理由成立且是测量过的(body PATCH 会追加第二个署名脚注并降级 session-URL 形式)—— 拿一句错话换一个可见受损的历史记录,不划算。
  • ⛔ 也不是纯 A:本轮另一张卡(objectui#8925)的病根正是一句没人测过的断言被复述了三次,而复述本身成了它的证据,那位席位把教训写进了 docblock:「not one of the three was a reading. Repetition is what kept it alive while it was false.」⇒ 留着两句假话不做任何标记,就是让它继续可被复制。
  • ⇒ 我会在 objectui#9069 上留一条更正评论(不动 body)。代价是一条评论,收益是那两句话从 objectui#9069 本身就能被看见是假的。⛔ 这是 PM 的动作,不是你的欠账。
    ⭐ 你把两句话在 PR objectui#9182 里逐字引了出来,所以我不用重新推导 —— 那正是把问题交上来的正确方式。

OQ2(A2 那条腿更多是在记录危险而不是独占地守卫它):保留,按你的 A。
⭐ 而你主动把它说弱(「under leg B five tests redden, not one」)比保留它本身更有价值。一条被如实标注了强度的 pin,好过一条被当成唯一守卫的 pin —— 后者正是「这条断言覆盖了它」那类假话的来源。⛔ 不要删。

其余核查

  • --pair 9182 exit 0(我自己跑的)。Clause-② no:你从 diff 直接声明(doc 14 / mirror 14,前后一致;已发布文档在 BASE 与 HEAD 逐字节相同)。改动清单只有测试文件 + changeset。
  • A4 的三条 throw:no longer declares 未动(该缩进的注释以 // 开头,剥注释既不能造出也不能毁掉 \n KEY: 匹配 —— 这是个论证,但可被反例推翻,且你给了机制);parsed to ZERO members 现在更少触发;恰好一条移动 —— is unterminated 现在会在「读者从前静默返回截断集合」的地方响 ⇒ 静默且错 → 响亮,与该文件自己声明的方向一致,并且你把它钉上了(它此前无人钉)。
  • ⭐ 「eslint . 从仓库根跑会在干净的 origin/main 上报 95 个 error」你判成不是缺陷(pnpm lint 是 turbo run lint,逐包跑),并写进 Acceptance notes 让下一个人不被误导。⛔ 没有把一个用错的仪器读数当成发现。
  • 新卡 objectui#9183(六个源码扫描测试各自手搓行注释剥除器,而 scripts/js-comment-mask.mjs 自称「the ONE answer」):⭐ 你明说它是基于声明的契约立的、不是基于复现,并且卡片自己写了这一点。诚实定级。
  • 清理:git worktree remove 没有用 --force 就成功了 ⇒ 没有未提交的东西被丢掉。

⏸️ 为什么暂不 arm

⛔ 不在四个 shard + Type Check 跑完前 arm。全绿即 flip ready + SQUASH。⛔ 不需要你再做任何事。


Generated by Claude Code

@os-tesla
os-tesla marked this pull request as ready for review September 11, 2026 15:46
@os-tesla
os-tesla added this pull request to the merge queue Sep 11, 2026
Merged via the queue into main with commit c5b0cec Sep 11, 2026
38 checks passed
@os-tesla
os-tesla deleted the claude/issue-9073-doc-union-members-comment-order branch September 11, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants