Repository navigation
Commit 119a02b
fix(spec): ViewMetadata names a view body (the union of its members’ input types), not unknown (#19919)
Fixes #19871
Clause-②: no (narrowing)
## What changed
- `packages/spec/src/ui/view.zod.ts`, the type export region only:
`ViewMetadata` was `z.input` of `typeof ViewMetadataSchema`, which is
exactly `unknown`. It is now `z.input` over `(typeof
VIEW_METADATA_MEMBERS)[ViewMetadataBranch]`: the union of the input
types of the four members the schema's union runs. The TSDoc says why
the type cannot be derived from the schema itself, and what it still
does not express.
- ⛔ No runtime change. `ViewMetadataSchema`, its members and its
`z.preprocess` are untouched, and a 44-body parse probe answers
byte-identically before and after (below).
- New pin: `packages/spec/src/ui/view-metadata-type.test.ts`.
- `packages/metadata/src/serializers/typescript-serializer.ts`: the
comment that documented the `unknown` now says why `view` stays
unannotated. The reason is the table's own rule (the spec type must
equal the bound schema's `z.input`), and it holds for a real reason: the
door accepts bodies `ViewMetadata` refuses. One word in the sibling
test's docblock ("is" to "once was") keeps it true.
- Changeset `.changeset/view-metadata-type-not-unknown.md`:
`@objectstack/spec` minor, BREAKING banner, `Clause-②: no (narrowing)`,
ADR-0087 disposition `not-required (no-migration-prescription)`.
## Why this type: the reading
- The TSDoc promises "any persisted view metadata body: container |
ViewItem record | flattened overlay". Under the ADR-0122 naming, the
bare name is the input shape and `ViewMetadataParsed` is the output
shape. So the fix is the members' INPUT union.
- It cannot be read off the schema. The `z.preprocess` input type is
`unknown`, and where the union is built its members are cast to
`z.ZodTypeAny`, so both `z.input` and `z.infer` of `ViewMetadataSchema`
are `unknown`. `VIEW_METADATA_MEMBERS` is the union's member list by
construction, so it is read there instead.
- Consumers: `git grep -w ViewMetadata` finds no annotation consumer in
this repo. objectui has zero hits at the pinned sha `62597c5880` and at
its `origin/main` `544ecba`. The control grep for `ViewMetadataSchema` /
`VIEW_METADATA_MEMBERS` at the pin answers exit 0 with 9 files, so the
negative reading is valid.
## Measurements: before `8490127962` (`origin/main`), after `2f378ec5cf`
Type probe, compiled against `src` AND against the BUILT
`dist/ui/index.d.mts`, with the same results on both:
| reading | before | after |
|---|---|---|
| `unknown extends ViewMetadata` | holds | does not hold (TS2322) |
| a value typed `unknown`, assigned to `ViewMetadata` | compiles |
TS2322 |
| list overlay plus undeclared key `notAViewKey` | compiles | TS2353 |
| `42` | compiles | TS2322 |
| the #19852 repro body (`name`, `type: 'grid'`, `object`, `columns`),
which the parse rejects | compiles | still compiles, see Residual |
| `ViewMetadataParsed` is `unknown` | yes | yes, not changed here |
Probe liveness control: at base, a `ListView` literal with an undeclared
key reports TS2353 in both programs. `--listFiles` shows 11 spec `dist`
files and 36 spec `src` files in the two programs.
Runtime acceptance is byte-identical. The probe runs 44 bodies through
`ViewMetadataSchema.safeParse`: the `view-union-diagnostics` battery
plus the undeclared-key body, the repro and a list overlay with console
row ids. It records the full result (verdict, parse output, issue
code/path/message). On `src` and on `dist`, before and after, it gives
20 accepted and 24 refused, report sha256
`f652fb343387bf490eda6fd7efdde6330d6103db811f246c8234ded9ee4993d7` all
four times.
Type against door over the same 44 bodies. Before, all 44 compile
(`unknown`). After:
| | door accepts | door refuses |
|---|---|---|
| type-checks | 17 | 8 |
| type error | 3 | 16 |
- The 3 in "door accepts, type error" are expected. Two carry the
console's row `id` on `sort`/`filter` rows, which the preprocess strips.
The third is the undeclared key, which a strip-mode member drops.
- The 8 in "door refuses, type-checks":
- the identity precondition refuses `{}`, name-only and
stamped-identity-only bodies;
- the container member's refinement refuses an empty `listViews`;
- the viewItem member types `config` as `unknown` (see Acceptance
notes), so a bad config or a decorated config type-checks;
- two bodies mix keys of different members: the #19852 repro and
`container.withAux`.
## Residual: the #19852 repro body still type-checks
TypeScript checks an object literal's keys against the union AS A WHOLE.
The container member (`ViewSchema`) has only optional keys. So once
`type` and `columns` are found on another member, `name` + `type` +
`object` + `columns` is assignable to the container member. Measured:
the same union without the container member refuses the body (TS2741,
`viewKind` missing). Each member alone refuses it too.
An exclusive union would add, to each member, every other member's keys
as optional-never. That refuses the repro and `container.withAux`. But
it also refuses four bodies the door accepts and the platform writes
itself: `put.isPinned`, `put.sortOrder`, `put.pinAndOrder` and
`overlay.badOperator`. Both forms are wrong somewhere, and the dispatch
named the plain members' input union. So this PR ships the plain union
and documents the gap in the TSDoc.
## Firing control for the pin
The fix was committed first. `scripts/ablation-replace.mjs` then put the
declaration back to today's spelling (anchor hit 1 to 0, blob
`bb6384c31aeb` to `a74e16786768`). `pnpm --filter @objectstack/spec
check:test-typecheck` then goes red with
"src/ui/view-metadata-type.test.ts: 3 type error(s) in a file the ledger
does not cover". tsc names them TS2578 `Unused '@ts-expect-error'
directive` at lines 61, 63 and 65. Restored: blob equals HEAD, and `git
diff HEAD` is empty. On the fixed tree the same gate is green: 53 files,
255 errors and 142 signatures, held exactly by the unchanged
`test-typecheck-debt.json`.
## Generated artifacts, and what `check:api-surface` sees
`gen:api-surface`, `gen:export-origins` and `gen:declaration-map` all
ran on the rebuilt dist and wrote zero diff ("0 shard(s) rewritten").
`check:api-surface` answers "public API surface + factory signatures
unchanged". The snapshot records that an export EXISTS and its kind, not
what a type alias resolves to (see the `build-api-surface.ts` header),
so this type change is invisible to it. The change is stated here
instead: **`ViewMetadata` narrows from `unknown` to the union of the
four members' input types.** The dispatch expected `check:api-surface`
to report it. Measured, it does not.
## Clause-② reading
- **Value `no`.** The runtime accept set does not move (the probe
above), and no export is added or removed.
- **Arm `(narrowing)`.** A published TYPE narrows from `unknown`: code
that assigns a non-view value to a `ViewMetadata` slot stops compiling.
ADR-0087's 2026-08-30 addendum reads a published type-surface narrowing
as BREAKING, truthfully.
- **Level `minor`.** This follows the launch-window convention in the
`check-changeset-no-major.mjs` header: breaking-ness is carried by the
banner and the disposition, not by the level.
- **Disposition `not-required (no-migration-prescription)`.**
`type-surface-only` is closed to this diff by its predicate 2 (it
touches `packages/spec/**`) and predicate 3 (a `*.zod.ts` moved).
Nothing authorable is removed or renamed, and no stored row moves.
`check-adr-0087-registration` answers "1 declared-breaking changeset(s),
each carrying an ADR-0087 disposition". `check-changeset-no-major`
answers "no `major` bump".
- The claim's provisional `Clause-②: no` is superseded by `no
(narrowing)`.
## Local verification at `2f378ec5cf`
- `pnpm --filter @objectstack/spec test`: 528 files, 15508 passed, 1
todo. The new file: 5 passed, one per member plus the coverage check.
- `pnpm --filter @objectstack/spec typecheck` (tsc, scripts program,
test layer): exit 0.
- `pnpm --filter @objectstack/metadata typecheck` and `test`: exit 0, 54
files, 821 passed.
- `pnpm --filter @objectstack/spec check:generated`: all 15 artifacts up
to date. It ran against a dist built from `aa5156d9de`; the only later
commit adds the changeset, so no spec source differs.
- `dispatch-gates --ran`: 83 families derived, 81 run with exit 0, 0
unrun, and 2 NOT MEASURED with exit 3, PREREQUISITE NOT MET:
`check:dual-build-cjs-loads` and `check:type-check-debt`. Both need the
whole workspace built. Neither reads the changed symbol: it has no
in-repo importer. The four type-check lanes the tool lists as CI-only
are CI's.
- `origin/main` moved 2 commits since the base (`beac798026`,
`a34c27cbe5`). Neither touches `packages/spec` or `packages/metadata`,
so they are not merged here.
## Acceptance notes
Sibling census: every exported type alias in `packages/spec/src` that
resolves to `unknown`, compiled with the package's own tsconfig at
`2f378ec5cf`. 2359 aliases were scanned. ⛔ None of these is changed
here; they are listed for the seat.
- `InlineAction` (`src/ui/action.zod.ts:2161`) is `z.input` of a
`lazySchema` wrapping a `z.preprocess`, so it is `unknown`. It has the
same shape as this card's type, in another file. (`InlineActionParsed`
resolves to a concrete type.)
- `ViewMetadataParsed` (`src/ui/view.zod.ts:6329`) is `unknown`: its
`z.infer` reads the union whose members are cast to `z.ZodTypeAny`.
Narrowing it the same way is not a one-line change.
`diagnoseViewMetadata` returns `data: parsed.data`, and `parsed.data` is
`unknown`, which is not assignable to the members' output union (TS2322,
measured). So the fix also needs an edit in that function, outside this
region.
- `AssembledViewArtifact` and `AssembledViewArtifactParsed`
(`src/ui/assembled-views.zod.ts:102/104`) are `unknown`. The cause is a
union of `z.ZodTypeAny`-cast members (the same `VIEW_METADATA_MEMBERS`
values), not a preprocess. The TSDoc says "One assembled `viewItems:`
entry (input shape)".
- `JoinedReportBlock` (`src/ui/report.zod.ts:407`) is `unknown` because
`JoinedReportBlockSchema` is annotated `z.ZodTypeAny`.
- `AssignmentValue(Parsed)` and `GetPublishedMetaItemResponse(Parsed)`
are `unknown` by design: their schemas are `z.unknown()`.
- Nested, not top-level: the viewItem member's `config` is typed
`unknown`, because `viewItemArmShape` takes `config: z.ZodTypeAny`. So
at the type level `ViewItem`, `ViewItemWire` and this union's viewItem
arm accept any `config`.
- `check:exported-any` catches a `ZodType` of `any`, not an exported
alias that resolves to `unknown`. No gate sees this class today. The
census is about 60 lines of TypeScript compiler API and could become
one. Not in scope here.
## Patch round (head `3cb0a8f6a`), after the at-tier FAIL `5808373817`
Written by the `domain:spec` seat 4
(`session_019c3Hi6ZMU1p6m6aA6Bz45d`), which took the card over on the
maintainer's 「你接手派补丁轮」. Two fast-forward commits; no code moved.
- `packages/metadata/README.md:77` (`7ca39fe366`): the published
parenthetical said `ViewMetadata` is `unknown`, which this PR makes
false. It now reads: `ViewMetadataSchema` is a `z.preprocess`, whose
`z.input` type is `unknown`; `ViewMetadata` is declared instead as the
union of the input types of the members that schema's union runs, so it
is not that schema's `z.input` type. That is the TSDoc's own wording,
and it is consistent with the serializer comment and the annotation-test
docblock.
- `.changeset/view-metadata-type-not-unknown.md` (`3cb0a8f6a`): one
paragraph naming the still-pending `.changeset/19852-*.md` sentence
(`ViewMetadata` is `unknown`) as superseded. That file is not edited,
because editing it is the deliberate-correction class.
- No second changeset for `@objectstack/metadata`. The README does
publish (`files[]`), but the two packages sit in one `fixed` group, so
the corrected README ships in the same release, and the false clause
never shipped. At-tier re-review `5815765984`: PASS.
---
_Generated by [Claude
Code](https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent cc6dfd9 commit 119a02b
6 files changed
Lines changed: 149 additions & 7 deletions
File tree
- .changeset
- packages
- metadata
- src/serializers
- spec/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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
77 | | - | |
| 77 | + | |
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
31 | | - | |
32 | | - | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
33 | 37 | | |
34 | 38 | | |
35 | 39 | | |
| |||
| 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 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6352 | 6352 | | |
6353 | 6353 | | |
6354 | 6354 | | |
6355 | | - | |
6356 | | - | |
| 6355 | + | |
| 6356 | + | |
| 6357 | + | |
| 6358 | + | |
| 6359 | + | |
| 6360 | + | |
| 6361 | + | |
| 6362 | + | |
| 6363 | + | |
| 6364 | + | |
| 6365 | + | |
| 6366 | + | |
| 6367 | + | |
| 6368 | + | |
| 6369 | + | |
| 6370 | + | |
| 6371 | + | |
| 6372 | + | |
| 6373 | + | |
6357 | 6374 | | |
6358 | 6375 | | |
6359 | 6376 | | |
| |||
0 commit comments