Uh oh!
There was an error while loading. Please reload this page.
Add the ObjectViewSchema drift guard, and fold the four per-view-type config aliases - #5760
Merged
Merged
Conversation
…istViewSchema Phase-3 carry-over from #2890: `kanban.groupField`, `kanban.cardFields`, `gallery.imageField` and `timeline.dateField` are the pre-#2231 objectui spellings kept declared alongside the spec keys so stored view metadata keeps validating. They now fold at the same boundary as the A1-A5 folds, in the same one-directional shape: the canonical key wins when both are present, and the legacy key is removed so a missed read-site fails loudly. One read-site changes behaviour, and it is a correction of the same inverted precedence A2 fixed for `densityMode`: ListView's kanban adapter resolves `cardFields || columns`, i.e. legacy over canonical, so a config carrying both rendered the legacy value. Every other reader of these four was already canonical-first. `calendar.defaultView` is deliberately not folded — it aliases nothing and has no spec counterpart, so it wants promotion upstream rather than a rename. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E7snar5mwF7qoXJazqKhys
…sked for `ObjectViewSchema` is the node #2231 never touched and the only view schema with no parity guard: `object-view-spec-parity` returned zero hits repo-wide, against three files for its `list-view` sibling. The 2026-07 audit (docs/audits/2026-07-objectview-detailview-schema.md) opened with exactly this gap, and asked for the guard to land BEFORE any restructuring so the restructure has a baseline. Scope note: the audit's step 1 -- "make the declaration match the reads (the 28 undeclared keys)" -- has since been ruled the other way (maintainer, 2026-08-18 on objectui#5097). Those keys are host-composition surface, deliberately undeclared, already pinned by name in objectViewHostSurface.test.tsx. This guard therefore covers the DECLARED surface, which had no guard at all: - zod shape vs TS interface, ratcheted so the gap can shrink but never grow; - every declared key that has a spec counterpart still has a LIVE one; - every declared key is triaged: mapped upstream, sanctioned local, or a recorded TS-only gap. Two mechanisms are load-bearing and both were verified by ablation rather than assumed: - ADR-0087 tombstone filtering. A D2 retirement replaces a member with `z.never()` instead of deleting the key, so `Object.keys(shape)` still reports it and a naive "is this key in the shape" check passes on a retired key. The spec pin objectui resolves carries five such tombstones on ListViewSchema (striped, bordered, virtualScroll, responsive, performance), so the filter is exercised by real data. - The compiler-checked interface key list. The plain `keyof` spelling does not work here: BaseSchema ends with `[key: string]: any`, so `Exclude<keyof ObjectViewInterface, keyof BaseInterface>` collapses to `never` and the exhaustiveness check accepts any object. Measured -- with that spelling, deleting an entry left type-check green. A key-remapping filter that drops index members restores the error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E7snar5mwF7qoXJazqKhys
Contributor
✅ Console Performance Budget
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
Size Limits
|
os-sam
marked this pull request as ready for review
August 23, 2026 05:34
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #2890 — the two items the 2026-08-06 triage put in
pm:queue, and only those. The card body describes a much larger project; the triage narrowed it, and that narrowing is what this PR implements. All verification below ran atf108410.1. The
ObjectViewSchemadrift guardpackages/types/src/__tests__/object-view-spec-parity.test.ts— the sibling oflist-view-spec-parity.test.tsfor the node that never had one. Premise re-verified onorigin/main3ddc8c2:object-view-spec-parity/objectViewSpecParityreturn zero hits acrosspackages, controlled againstlist-view-spec-parity, which hits three files under the same scan.The audit's step 1 has been ruled the other way since July, and the guard is shaped around that. The 2026-07 audit asked first for "make the declaration match the reads (the 28 undeclared keys)". Maintainer ruling of 2026-08-18 on objectui#5097 (verbatim 「同意」) settled it the opposite way: those keys are host-composition surface, deliberately not declared on
ObjectViewSchema, withOBJECT_VIEW_HOST_COMPOSITION_KEYSas their single home andplugin-view/src/__tests__/objectViewHostSurface.test.tsxpinning them by name from the side that can read the source. So the reads half is already guarded, and guarded where it belongs.What had no guard at all is the declared surface. That is what this file covers:
Re-measured, since the audit is from July
3ddc8c2)type/description, which the node narrows)type/description/className, which the envelope owns)The 11 are pinned in
TS_ONLY_BACKLOG. Each is a key an author can write in TypeScript that the CLI validator and the VS Code extension silently ignore, because those parse through zod.Two mechanisms are load-bearing, and both were verified by ablation
ADR-0087 tombstone filtering. A D2 retirement does not delete the key — it replaces the member with
z.never(), soObject.keys(shape)still reports it and any check spelled "is this key in the spec shape" passes on a retired key. This is not hypothetical here: the spec pin objectui resolves (@objectstack/spec@17.1.0) carries five live tombstones onListViewSchema—striped,bordered,virtualScroll,responsive,performance— so the filter is exercised by real data rather than by a synthetic fixture. Ablated by replacing the predicate with the naive spelling: 2 tests redden.The compiler-checked interface key list. The obvious
keyofspelling is a guard-shaped no-op here, and this was caught by ablation rather than reasoning.BaseSchemaends with[key: string]: any, sokeyofcollapses tostring | number,Exclude<keyof ObjectViewInterface, keyof BaseInterface>becomesnever,Record<never, true>is{}— and the exhaustiveness check accepts any object. Measured: with that spelling, deleting an entry lefttype-checkgreen. A key-remapping filter that drops index members restores it — deleting an entry now givesTS2741, adding an undeclared one givesTS2353. That same index signature is the deeper reason this node's TS half could drift unnoticed for so long.2. The four phase-3 per-view aliases folded into
normalizeListViewSchemakanban.groupField→groupByField,kanban.cardFields→columns,gallery.imageField→coverField,timeline.dateField→startDateField. Same one-directional shape as the A1–A5 folds: the canonical key wins when both are present, and the legacy key is removed so a missed read-site fails loudly instead of quietly taking the legacy path.One rendering behaviour changes, and it is a correction.
ListView's kanban adapter resolves the card field list ascardFields || columns— legacy over canonical, the same inverted precedence A2 fixed fordensityMode. A kanban config carrying both therefore rendered the legacy value and silently ignored the spec-canonicalcolumns. After the fold the authoredcolumnsreaches it. Every other reader of these four was already canonical-first, so nothing else moves; each is pinned in the new tests.calendar.defaultViewis deliberately not folded — it aliases nothing and has no spec counterpart, so it wants promotion upstream rather than a rename.The fold is scoped to the declared per-view path. It does not reach into the legacy
options.*twin, which is a passthrough bag ListView merges underschema.kanban; that boundary is stated as a test rather than left implicit.Verification
All at
f108410, each read from the gate's own verdict line (never a bare$?behind a pipe):pnpm exec vitest runovercore,types,plugin-list,plugin-view,plugin-kanbanTest Files 221 passed (221)·Tests 3446 passed (3446)pnpm lint(full repo)Tasks: 47 successful, 47 total— 0 errors (warnings pre-existing; the workflow sets no--max-warnings)pnpm type-check(full repo)Tasks: 81 successful, 81 totalpnpm check:spec-symbols✅ spec symbol derivation: 1297 files scanned against 4966 spec export namespnpm check:control-bytes✅ check-control-bytes: OK (scanned 4801 tracked text file(s))Both lint and type-check were run at full repo scope, so no narrowing argument is claimed.
Ablations. Every leg confirmed the mutation on disk with an anchored
grepcount before reading any result, and every script carried atrap … EXIT INT TERMrestore; each restore was verified byte-exact withdiff. These suites resolve the code under test through relative source imports (not a packageexportshop intodist/), so no rebuild sits between the mutation and the reading — stated because adist-resolved ablation that skips the rebuild stays green and is invisible to CI.delete nextCfg[legacy];1 → 0, marker injected 1viewActions: true,1 → 0type-checkexit 2,TS2741type-checkexit 2,TS2353Out of scope, deliberately untouched
Both were named by the triage as rider risks, and neither is an objectui decision:
objectName→data.provider:'object', andviewType. Blocked upstream —@objectstack/spec'sreact-blocks.tsdeclaresobjectNamea sanctioned React-tier prop andpackages/lint/validate-react-page-props.tsenforces it. Apackages/specface, owned by the objectstackdomain:specseat via cross-seat transfer.objectNameis recorded in the guard'sSANCTIONED_LOCALwith that reason rather than quietly triaged as local-forever.conditionalFormatting/exportOptions. objectui's shapes are supersets carrying capability the spec cannot express; they want upstream promotion, not a rename.No
@objectstack/specdeclaration was changed, so clause ② is not tripped: no contract accept/reject behaviour moves and no public surface widens.Build Docsis inherited-red onmain(#5668,fsreaching the browser bundle viapg-connection-string); unrelated to this diff.Generated by Claude Code