Repository navigation
Commit e233db9
Fixes #17987
Clause-②: yes (widening) — one new optional key on two published element
faces plus one new `ComponentPropsMap` row. Nothing previously admitted
is refused, nothing is renamed or retired.
Executes the objectui#8652 maintainer ruling, reply verbatim 「B」 —
declare `navigation` on the platform element schemas. Options A / C / D
were not taken and are not reopened here.
## What landed
1. **`object-kanban` and `object-calendar` declare `navigation`**,
carrying `NavigationConfigSchema` — the def `ListViewSchema.navigation`
already declares, taken BY REFERENCE so the vocabulary cannot fork. The
retired `navigation.view` tombstone rides along onto both element faces.
2. **`object-timeline` gets its `ComponentPropsMap` row.** It was a
registered objectui renderer reachable only through the component type
union's open string arm with no row here, so the props gate skipped it:
unjudged in both directions. The row declares the key set measured from
the renderer's own read points at the `.objectui-sha` pin `53ded82b` —
`objectName`, `timeline`, `filter`, `sort`, `limit`, `data`, `items`,
`variant`, `dateFormat`, `rowLabel`, `minDate`, `maxDate`,
`descriptionField`, `mapping`, `navigation` — with the per-key
citations, and what it deliberately does NOT declare, in the schema's
own header.
3. **The view-level `navigation` on `ListViewSchema` is untouched.** The
element key is an ADDITIONAL carrier for the standalone placement, not a
replacement; a test pins that half so a later sweep cannot fold the two
into one question.
## Premise re-derived on today's `origin/main` before any edit
Base `24d622b94b8afe93d00deda39062bf292e0d5d2f`. The card's own numbers
had moved, so every leg was re-measured rather than inherited:
| leg | card said | measured at base |
| --- | --- | --- |
| `ComponentPropsMap` entries | 45 | **48** (49 after this PR) |
| `object-kanban` + `navigation` | refused by name | **refused**,
`unrecognized_keys: ['navigation']` |
| `object-calendar` + `navigation` | refused by name | **refused**,
identically |
| `object-timeline` row | absent | **absent** |
| view face | carries `navigation` | **carries it**, and still does |
Controls, in the same run, so the zeros are readings:
- **Positive control that fires** — `object-kanban` + `cardFields` and
`object-calendar` + `defaultView` both `success: true` at base, i.e.
these faces were not refusing everything.
- **Bogus-key control** — `zzzDefinitelyNotAKey` refused through the
**same** `unrecognized_keys` channel on both faces, before and after:
the refusal of `navigation` was the generic unknown-key rule, and it
still is for an undeclared key.
- **Firing control for the timeline zero** — `object-kanban` /
`object-calendar` / `object-grid` / `object-tree` all resolved in the
same call, so "no entry" was a reading rather than a dead probe.
## The card's acceptance criterion, run
`ComponentPropsMap['object-kanban'].safeParse({ objectName: 'task',
navigation: { mode: 'drawer' } })` returns `success: true`,
`ComponentPropsMap['object-timeline']` is defined, and the bogus-key
control still returns `success: false` through `unrecognized_keys`. All
three legs, plus the control, are pinned in
`packages/spec/src/ui/component-element-navigation-17987.test.ts` (12
tests at the current head; the sentence above is the card's criterion,
and the leg added in round 2 sits beside it). The parsed state is the
view face's: `{ mode: 'drawer', preventNavigation: false, openNewTab:
false, size: 'auto' }`.
The "cannot fork" clause has teeth rather than prose: the element door
is asserted to resolve to the **very object**
`ListViewSchema.navigation` carries, and the two faces' accept/refuse
verdicts are asserted EQUAL over a five-document battery that contains
both verdicts (so the equality cannot hold vacuously).
## Reverse verification — the fix committed first, then mutated on disk
⚠️ **The table below is ROUND 1, taken against head `02d81323c9`.** Its
numbers and blobs are that head's and are left exactly as measured — ⛔
not restated as if they were the current head's. The round-2 leg follows
it.
Both legs went through `scripts/ablation-replace.mjs`, so the write is
proven by anchor counts and blob hashes rather than by an exit code, and
each restore is proven by `blob == HEAD` with `git diff HEAD` empty.
| ablation | predicted | observed |
| --- | --- | --- |
| rename the `navigation` door on all three faces (anchor x3 → x0, blob
`9b38bfa56e57` → `4f12296e0afc`) | red on the acceptance legs, controls
unmoved | **5 failed / 6 passed** — the bogus-key control, the
no-default leg, the timeline-row-defined leg, the view-face leg and the
two timeline-key legs all stayed green |
| rename the `object-timeline` row key (anchor x1 → x0, blob
`9b38bfa56e57` → `e2b329749e22`) | red on every timeline leg | **9
failed / 2 passed** — the two survivors are the view-face leg and the
`TimelineConfigSchema` prescription leg, neither of which depends on the
row |
Restored to `9b38bfa56e57b72e630540950d55f8fd09b6bee2` both times; the
suite is green on the restored tree.
**Round 2, head `ee50b4cd13`** — the `data` door mutated through the
same tool (anchor x1 → x0, blob `d663b3c630d6`): predicted red on
exactly the two legs that author `data` and nothing else; observed **2
failed / 10 passed**, with the bogus-key control on that same face
staying green and its refusal naming the bogus key and not `data`.
Restore proven by `blob == HEAD` with `git diff HEAD` empty. ⭐ The
round-2 contract reviewer re-ran this leg independently with a different
replacement string and read the same 2 failed / 10 passed.
## Gates
`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`
derives **107** families for this diff. All 107 were run and reconciled
with `--ran`: **107 run, 0 NOT-MEASURED, 0 UNRUN**.
- `pnpm --filter @objectstack/spec build` then `check:generated` —
green; six artifacts were regenerated by `--fix` (api-surface,
api-surface-declarations, export-origins, declaration-map, the reference
pages, the strictness ledger) plus `gen:schema`'s authorable-surface and
json-schema manifest.
- `pnpm --filter @objectstack/spec test` — **500 files / 14611 tests
passed** at the ROUND-1 head `02d81323c9`; `typecheck` green.
- ⭐ **At the current head `ee50b4cd13`: 500 files / 14612 tests passed**
— read twice independently, by the implementer and by the round-2
contract reviewer, and `typecheck` green in both.
- `pnpm lint` — repo-wide, exit 0. Not a narrowed run: the whole `eslint
. --no-inline-config` universe.
- `check:react-declaration-parity` in **CI's own spelling**
(`MANIFEST="$PWD/sdui.manifest.json" … --baseline
react-declaration-parity.baseline.json --strict`) exits 0:
`object-timeline` reads `2 declared by both, 13 spec-only, 0
registry-only`, so the new row needs no baseline entry and the ratchet
reports no new divergence. ⚠️ The BARE form in the derived list exits 1
on purpose — with no `MANIFEST` the gate reports "did not run" rather
than skipping — and that non-zero in `ran.list` is that refusal, not a
divergence.
- The heavy runs went through `scripts/pm/os-verify-lock.sh`; the six
path-scheduled CI jobs, the artifact-roster families and the
wide-population families are CI's farm and are NOT measured here.
## Two files outside the card's declared surface, named rather than
widened silently
The card's surface is `packages/spec/src/ui/` plus the changeset and the
generated pages that follow. Two more tracked files had to move, both
mechanically and both printed by the gate that refused without them:
- `packages/spec/dropped-refinements.baseline.json` — the build REFUSES
until `ui/ObjectTimelineProps` declares the two sites its projection
cannot carry (`filter.element`, `timeline.groupByField`), and prints the
entry verbatim. It is the same row `ui/ObjectGanttProps` already
carries. Its header totals moved with it (204 schemas / 560 sites),
which `scripts/dropped-refinements.test.ts` asserts.
- `docs/audits/2026-07-unknown-key-strictness-ledger.counts.md` —
regenerated by `gen:strictness-ledger`: one more closed object site in
`ui/`.
## Acceptance notes — observed in passing, not filed, not fixed here
- **`object-grid`, `object-gantt` and `object-tree` declare `navigation`
as `z.unknown()`.** Three element faces therefore accept `navigation: {
mode: 'drawr' }` and `navigation: 'drawer'` while the three this card
touches now judge the value. Narrowing a published accept face is a
different act from widening one, it is outside this card's closed scope,
and it deserves its own ruling. Reported to the seat with dedupe words
rather than filed from here.
- **objectui's `ListViewTimelineConfig` declares a nested
`timeline.dateField` alias that `TimelineConfigSchema` refuses by
name**, and the renderer reads it. That is the same defect shape as this
card on a different key, on the VIEW face, so it is neither in scope nor
fixable as a rider. Reported likewise. This PR's guidance prescription
is careful about it: it names `timeline.startDateField`, the key the
alias resolves to and one `TimelineConfigSchema` accepts — the trap
#17054 already paid for once, pinned in the new test.
- `object-tree` reads "NO component in the manifest" in the parity gate.
Pre-existing, untouched, and outside the baseline's ratchet.
## Downstream
objectui#8652, objectui#8654 and objectui#8651's `CalendarSchema` arm
wait on this. ⚠️ Their unlock criterion is **consumer-installable** — a
released `@objectstack/spec` carrying the declaration, installable
there, with the pin moved — ⛔ not this merging.
<sub>Body edited by the dispatching seat `domain:spec#4`
(`session_01AmH9bKvGoLjiY86Q4Z3og2`) after the rework landed at
`ee50b4cd13e07a2d3327e78d5f98aefd30ed85ab`: the `object-timeline` key
list gained `data`, and the parity figure moved from 12 to 13 spec-only.
The implementer named both edits in its report and correctly declined to
patch this body itself — a dev writes a PR body once, at the POST that
opens it.</sub>
---
_Generated by [Claude Code](https://claude.ai/code)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 805811e commit e233db9
13 files changed
Lines changed: 1026 additions & 25 deletions
File tree
- .changeset
- content/docs/references
- ui
- docs/audits
- packages/spec
- api-surface-declarations
- api-surface
- authorable-surface
- declaration-map
- export-origins
- json-schema.manifest
- src/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 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
35 | | - | |
36 | | - | |
| 35 | + | |
| 36 | + | |
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| |||
361 | 361 | | |
362 | 362 | | |
363 | 363 | | |
364 | | - | |
| 364 | + | |
365 | 365 | | |
366 | 366 | | |
367 | 367 | | |
| |||
372 | 372 | | |
373 | 373 | | |
374 | 374 | | |
375 | | - | |
| 375 | + | |
376 | 376 | | |
377 | 377 | | |
378 | 378 | | |
| |||
0 commit comments