Repository navigation
Commit 315888d
Closes #20940
Clause-②: yes (widening)
> The declaration above is the seat's: ruling `5921583377` took the
dev's recommendation, and the claim line was corrected in place to match
(`scripts/pm/clause2-line.mjs:90`: two new exports expand the public
surface). The reasoning is in "Clause ② re-read" below.
## What this does
Three page walks descend into a component's `properties` bag, and each
kept its own list of positions:
- the ADR-0087 conversion walker: a module-private
`COMPONENT_CHILD_KEYS` plus `items[].children`;
- `@objectstack/lint`'s `walkPageComponents`: `items[].children`,
`children`, `body`, `footer`;
- the exported `walkAddressedPageComponents`: `children`,
`items[].children`.
So a node in a `page:card` footer was judged by `os lint` and skipped by
every consumer of the exported walk: `translatePage`, the CLI extractor
and objectui's validator.
Now `packages/spec` owns **one** list, derived from the component rows.
There is no fourth list.
- **The rows declare the fact.** `componentSlot()` /
`retiredComponentSlot()` are module-private markers in
`ui/component.zod.ts`. Each registers the exact schema instance a row's
shape holds, and returns it unchanged, so the parse and the JSON Schema
are the unmarked schema's. Five keys are marked `componentSlot`:
`PageContainerProps.children`, `PageCardProps.children`,
`PageCardProps.footer`, and `children` on the `page:tabs` and
`page:accordion` panel items. `PageCardProps.body`, a `retiredKey`
tombstone, is marked `retiredComponentSlot`.
- **`pageComponentSlotPositions()` derives the list** from
`ComponentPropsMap`. It is exported with its type
`PageComponentSlotPosition` from `@objectstack/spec/ui`. It returns
`children`, `body` (retired), `footer`, and the panel position
`items[].children`. Order is the visit order: direct slots in row order,
then panels, so `children` is still walked before `items[].children`,
the order the exported walk's nested-id arbitration has used since
#16772. The list is derived on first call and memoized, never at import.
The first call reads every row's shape, which constructs the
`lazySchema` rows: measured 17.6 ms, once per process. Two rows that
contradict each other on one position throw.
- **All three walks read it.** The conversion walker reads every entry.
`walkAddressedPageComponents` and lint's `walkPageComponents` read the
authorable entries, so `page:card.footer` is now descended by all three.
- **`body` is handled per the standing ruling.** #5775 (maintainer
ruling 2026-08-06, direction A, recorded in
`docs/protocol-upgrade-guide.md` and in the `PageCardProps.body`
tombstone and the `PageContainerProps` guidance) made `children` the one
composition key. It kept the renderers' `body` read as a back-compat
fallback for stored documents, not as an authorable spelling. So:
- The exported walk still does not descend `body`.
- **Lint's descent of `body` was re-read against that ruling, as the
claim asked, and removed.** An author who writes `body` is refused by
the tombstone's own rename prescription. Judging the sub-tree under a
refused key as if it were authored is what the ruling rules out. After
the rename the sub-tree is judged under `children`.
- The conversion walker keeps descending `body`. Stored documents still
carry it, and a conversion ordered before `page-card-body-to-children`
(for example `page-header-subtitle-alias`) meets the sub-tree there.
`page-component-walk.test.ts` pins that reach, and removing it would be
a reach regression the claim did not order.
- **Recursive typing of `children` is declined.** The component rows are
not reached by any load-path parse: `PageComponentSchema.properties` is
an open bag. So a recursive `children` type would take effect only in
lint's props gate, which already judges every nested node through the
walk. It would also narrow a published accept set (`children` legally
holds bare id strings and `null`). And it would not replace any
consumer's walk, as the card itself notes.
## Clause ② re-read — why the declaration is `yes (widening)`
- The claim's original `no` covered the walk: the exported walk descends
a slot the rows already declared. But the design the claim orders
("`packages/spec` owns one list … lint's `walkPageComponents` reads it")
needs `@objectstack/lint`, a separate package, to import the list. So
`@objectstack/spec` grows two exports, and `api-surface/ui.json` gains
two rows in this diff.
- `scripts/pm/clause2-line.mjs` states the question the declaration
answers: 「本卡放宽接受集或扩大公开面吗」. The public surface grows, so the answer is
yes.
- The level rule the gate mechanizes (`pr-automation.yml`, "WHICH
LEVEL", ruling 2026-09-04) says: "a new exported symbol on an `index`"
is a widening that takes at least `minor`.
- `scripts/pm/check-widening-tells.mjs` names "T3: a new row in a
published entry point's export listing
(`packages/spec/api-surface/*.json`)" as a widening tell. Under a `no`
it refuses the enqueue.
- The changeset grades `@objectstack/spec` `minor`, so
`check-changeset-no-major`'s level axis passes under either value. The
seat took this in ruling `5921583377`.
## Changeset levels
- **`@objectstack/spec`: `minor`.** It adds two exports:
`pageComponentSlotPositions`, `PageComponentSlotPosition`. The exported
walk's signature and return shape are unchanged. The one earlier
widening of this walk (#16772) was banner-BREAKING only for its
return-shape change, so none is declared here.
- **`@objectstack/lint`: `patch`.** No export is added, and
`walkPageComponents`'s signature is unchanged. Its reach drops the
retired `body` per the ruling, and the tombstone still refuses the key
itself.
- **`@objectstack/cli`: `patch`.** There is no code change, only
corrected comments. The changeset tells an upgrader that `os i18n
extract` / `os i18n check` now offer footer component keys.
- No ADR-0087 marker: nothing an author writes moves, no stored shape
changes and no conversion id is touched. `check-adr-0087-registration`
reads the changeset as non-breaking (exit 0).
## Verification — at `d9d0d38cfc` (this PR's head when opened)
- The cut round at `5dc435cb07` re-ran the spec and lint suites and
typechecks, `check:generated`, and the derived gates (report
`5922116038` on #20940): all green, 91 of 91 derived gates exit 0.
- `pnpm --filter @objectstack/spec exec vitest run --project local
--maxWorkers=2`: **584 files, 17196 passed, 1 todo**, exit 0.
- `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2`:
**117 files, 5440 passed**, exit 0.
- `pnpm --filter @objectstack/spec typecheck && pnpm --filter
@objectstack/lint typecheck`: exit 0. `check:test-typecheck` held for
both, with the debt ledgers unchanged.
- `pnpm --filter @objectstack/spec check:generated`: exit 1 at first, on
`api-surface/` and `export-origins/` (the two new exports). `--fix`
regenerated exactly those two, adding +2 rows each (commit
`d9d0d38cfc`). Then "All 15 generated artifacts are up to date", exit 0.
`check:authorable-surface` and `check:docs` were green throughout, which
is the marker's no-schema-change claim, measured.
- These gates exited 0: `check-adr-0087-registration --base
origin/main`, `check-changeset-no-major --base origin/main`,
`check-empty-changeset --base origin/main`,
`check-closing-keyword-parity`, `check-issue-citations`,
`check-spec-docblock-symbol-anchors`, `check:nul-bytes`,
`check:doc-authoring`, `check:cross-package-test-inputs`,
`check:test-source-alias`, `check-undeclared-dep-imports`,
`check:issue-citations`. They ran at `3dee2204c9`; the commit since only
adds four generated JSON lines.
- The changed-file suites were run at each commit: `component.test.ts`,
`i18n-resolver.test.ts`, `page-component-walk.test.ts` (671 passed), and
lint `page-walk.test.ts` plus `page-walk-conversion-parity.test.ts` (21
passed).
## Ablations — each run from the committed head, restored and re-greened
All three used `scripts/ablation-replace.mjs`, whose anchor hit 1 → 0
with the blob changed. Each was restored with `git checkout HEAD`, and
the blob equals HEAD with `git diff HEAD` empty.
1. **The exported walk stops descending `footer`**, by filtering
`footer` out of its positions.
- The spec source suite went red, 3 of 300: the footer translate case,
the walk trace, and "hands a malformed node in a `page:card` footer to
the visitor".
- Spec was rebuilt, and `ablation-dist-preflight` found the marker in 4
built files.
- Lint's three-walk parity case, which reads spec's `dist/`, went red, 1
of 5.
- Restore leg: spec rebuilt, the preflight `--absent` was clean with a
clean tree, and both suites were green again (300/300, 5/5).
2. **Lint's walk takes every list entry**, the retired `body` included,
which is its pre-change reach. Lint went red, 4 of 21: the walk case,
both #6775 parity cases, and the three-walk parity case. Restored:
21/21.
3. **The row stops marking `footer`**: `componentSlot(` was removed from
`PageCardProps.footer`. Spec went red, 6 of 671: the derived-list pin,
the three exported-walk footer cases, the conversion walker's footer
case, and its list-completeness case. This shows the list is derived
from the row marker, not written down anywhere else. Restored: 671/671.
## Not measured when this PR was opened
- `packages/cli` unit tier, `test/platform-page-i18n-parity.test.ts`. It
imports built `@objectstack/cloud-connection` / `mcp` /
`platform-objects`, whose dependency closure this fresh worktree had not
built (a first attempt died with the container). The expectation is
edited: `card_footer_child` is offered and applied on both sides. The
run and its reading go in the os-dev-report on #20940.
- The remaining families from `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` on this diff. It derives
89 commands: 12 were run directly (listed above), and 5 more are covered
inside `check:generated` (`check:api-surface`,
`check:authorable-surface`, `check:docs`, `check:liveness`,
`check:strictness-ledger`). The rest go in the os-dev-report; CI runs
the full farm.
## Acceptance notes
- **Consumers.** objectui needs no change here. Its pin (e) goes red at
the spec bump, and objectui deletes that row, as the card and triage
say.
- **Visit order.** Within one component, lint and the conversion walker
now visit direct slots before panels. Previously they visited
`items[].children` first. This is observable only as finding or notice
order, for a component carrying both a direct slot and panels.
- **New import in `conversions/walk.ts`.** It now imports
`ui/component.zod.ts`. `component.zod.ts`'s import closure (49 files)
was measured and reaches no `conversions/` module, so there is no cycle.
The derived list is read only inside functions.
- **Prose this change made stale is corrected in this PR**
(`5dc435cb07`; the review adoption `5921754490` named this PR the
carrier): `packages/spec/src/automation/region-slots.ts:35-37`,
`packages/lint/src/page-envelope-audit.ts:59`, and
`packages/lint/src/validate-visibility-predicates.ts:1236` with its test
comment at `:497`.
- **`packages/cli/src/utils/i18n-extract.ts` comments.** Only comments
changed there, where they stated the old positions (`body`/`footer`
undescended; lint's walk "wider"). The claim scopes that file to "where
the widened walk moves an assertion". These are prose assertions the
widening made false.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8d329f0 commit 315888d
18 files changed
Lines changed: 716 additions & 209 deletions
File tree
- .changeset
- packages
- cli
- src/utils
- test
- lint/src
- spec
- api-surface
- export-origins
- src
- automation
- conversions
- system
- ui
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
932 | 932 | | |
933 | 933 | | |
934 | 934 | | |
935 | | - | |
936 | | - | |
937 | | - | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
938 | 939 | | |
939 | 940 | | |
940 | 941 | | |
| |||
1099 | 1100 | | |
1100 | 1101 | | |
1101 | 1102 | | |
1102 | | - | |
1103 | | - | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
1104 | 1107 | | |
1105 | 1108 | | |
1106 | 1109 | | |
| |||
1113 | 1116 | | |
1114 | 1117 | | |
1115 | 1118 | | |
1116 | | - | |
1117 | | - | |
1118 | | - | |
1119 | | - | |
1120 | | - | |
1121 | | - | |
1122 | | - | |
1123 | | - | |
1124 | | - | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
| 1124 | + | |
| 1125 | + | |
| 1126 | + | |
1125 | 1127 | | |
1126 | 1128 | | |
1127 | 1129 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
288 | 288 | | |
289 | 289 | | |
290 | 290 | | |
291 | | - | |
292 | | - | |
293 | | - | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
294 | 294 | | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
295 | 298 | | |
296 | 299 | | |
297 | 300 | | |
| |||
375 | 378 | | |
376 | 379 | | |
377 | 380 | | |
378 | | - | |
379 | | - | |
| 381 | + | |
| 382 | + | |
380 | 383 | | |
381 | | - | |
382 | | - | |
383 | | - | |
384 | | - | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
385 | 390 | | |
386 | 391 | | |
387 | 392 | | |
388 | 393 | | |
389 | | - | |
390 | | - | |
| 394 | + | |
| 395 | + | |
391 | 396 | | |
392 | 397 | | |
393 | 398 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
| |||
0 commit comments