fix(types): dashboard dateRange takes the spec authoring member on both faces (objectui#10334) - #10338
Conversation
…th faces DashboardComponentSchema.dateRange was on DASHBOARD_SPEC_EXCLUDED and restated locally: the Zod mirror typed defaultRange as a bare string (and stripped unknown keys) while the TypeScript twin bound it to the spec's DateRangeDefaultRange, so the validator admitted preset names tsc refused. The spec declares the key, so per the group F rule 1 / F1 ruling both faces now take the spec's authoring member by reference: dateRange leaves the exclusion list, the local element and member are deleted, and the WiderThanDeclared / WIDER_ARMS rows are removed. A pin asserts type equality with the spec member and the spec's verdict and issue envelope. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01877XiBYSaRCk2CU7cMSg3S
…shboard-daterange
|
changeset-claim-re-read
|
✅ 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
|
Contract reviewServed-tier: ① Derived judgmentsSpec shape: confirmed on both sides. The installed 17.4.0 (dist
Only Rule 1 / F1: PASS.
Accept set narrowed: probed directly.
No default is injected: confirmed. The walker handles No stored document is refused. Every Published symbols. Forward compatibility. The shape is identical on Ledger. ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…shboard-daterange # Conflicts: # packages/types/src/__tests__/zod-mirror-parity.test.ts
✅ 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
|
…-unread-keys Resolves the WIDER ledger header: objectui#10334 (via #10338) retired the DashboardComponentSchema::dateRange row, and this branch retires the HeaderBarSchema::logo entry. Re-derived figures: 7 / 9 / 9, split 5 / 4 / 0 / 0, with both history sentences kept in order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01877XiBYSaRCk2CU7cMSg3S
… left, center, height) (objectui#10387) (objectstack-ai#10395) Fixes objectstack-ai#10387 Clause-②: yes Retires **all eight** `HeaderBarSchema` keys the `header-bar` renderer never reads (`title`, `logo`, `nav`, `left`, `center`, `right`, `sticky`, `height`) from both faces. It uses the ADR-0049 tombstone shape objectui#10286 used for `variant`: `?: never` on the TS face beside a `retirementTombstone(...)` refusal in the zod mirror. It also migrates the only in-tree authors (docs and a type-check example) to `crumbs` / `actions`. Authority: objectui#7759 ruling 5617465269 rule 2 (objectui-own key, so the read site is the truth), D1-(ii) (both faces dead and the spec silent, so retire), and ADR-0049 enforce-or-remove. The PM seat ruled **decision A** on the four keys that had authors (`title`, `logo`, `right`, `sticky`): retire them in this PR and migrate the authors. ## 1. The read set, measured at runtime (not by grep) A one-off probe (deleted afterwards, not committed) rendered `{ type: 'header-bar', crumbs: [{ label: 'Home' }] }` through the real `SchemaRenderer` and the real registry inside `SidebarProvider`. It varied one key at a time and compared `innerHTML` with the render that omitted the key: ``` title="PROBEX" identical=true logo="/probex.svg" | node | [node] identical=true nav=[{label,href}] identical=true left / center / right = node | [node] identical=true sticky=true | false identical=true height="PROBEX" | 99 identical=true className="probex-cls" identical=true rightContent=node (lit control) identical=false, probe text present ``` The renderer's one function takes only `{ schema }` and reads `crumbs`, `search`, `actions` and `rightContent`. No spread props reach it. Spec: `packages/spec/src` at objectstack `9d81af71` and the installed 17.4.0 `dist` have zero `header-bar` / `HeaderBar` hits. The control word `breadcrumb` hits in both. ## 2. Authors, and how they migrated | key | in-tree authors before | migration | |:--|:--|:--| | `title` | `packages/types/README.md` (Composable example), `content/docs/guide/objectos-integration.mdx` (page `header`), `packages/types/examples/dashboard.ts`; inert `title: 'H'` filler in `header-bar-right-content-numeric-falsy.test.tsx` (3 sites) and `node-slot-numeric-falsy.test.tsx` (1 site) | README and guide: `crumbs: [{ label: 'My App' }]`; dashboard: `crumbs: [{ label: 'Object UI Dashboard' }]`; tests: filler dropped (it never rendered, so each test's `CHROME` baseline is unchanged) | | `logo` | `packages/types/examples/dashboard.ts` (`'/logo.svg'`) | dropped | | `right` | `packages/types/examples/dashboard.ts` (Profile button) | the button moved to `actions` | | `sticky` | `packages/types/examples/dashboard.ts` (`true`) | dropped | | `nav`, `left`, `center`, `height` | none | none needed | The guide's header also carried an undeclared `navigation` bag that nothing reads (the page's `sidebar` already holds the same items). It was dropped together with `title`, because the node was rewritten anyway. The `header-bar` registration `inputs` offer only `crumbs`, `search`, `actions` and `rightContent`, so the designer never offered any of the eight. ## 3. Ledger (`zod-mirror-parity.test.ts`) - The `navigation.zod.ts#HeaderBarSchema` `WiderThanDeclared` entry (`logo`) and its `WIDER_ARMS` row (`['CONCRETE', 'CONCRETE']`) are removed. A note records where the entry stood. - The WIDER header now reads **7 entries / 9 keys / 9 arms, split 5 SCHEMA-NODE / 4 CONCRETE / 0 MIXED / 0 unions**. Its history now has this PR's sentence (it read 8 / 10 / 11, split 5 / 5 / 0 / 1) above objectui#10334's sentence (8 / 11 / 12, split 5 / 6 / 0 / 1). The derivation pin 'the WIDER ledger's header figures are derived from its ARMS' reads these figures and is green. - Instrument repair: the non-vacuity pin 'the enumeration can tell a union slot from a plain one' required the **ledger** to hold at least one union key. `logo` was the last one, so the pin went red on a shrinking ledger even though the unwrapper worked. Its union direction now also measures a fixed control slot outside the ledger, `SidebarSchema.content` (single-or-list, two arms). The single-arm direction still reads the ledger's own rows. After the merge, `WIDER_ARMS` holds zero multi-arm rows, so that control slot is now the only two-arm reading the pin gets. It passes. - origin/main is merged twice, never rebased: `00cdaff1f` (merge `2ea13bc2a`), then `ce6bd99a5`, which is PR objectstack-ai#10338 / objectui#10334 (merge `a74282b89`). The second merge conflicted only on the WIDER header paragraph. It was resolved by re-deriving the figures and keeping both history sentences in order. The merge changed nothing on this branch beyond the ledger figures: `navigation.ts`, `navigation.zod.ts` and the pin test are byte-identical to `968cd7698`. ## 4. Pins and reverse verification `packages/types/src/__tests__/header-bar-unread-keys-10387.test.ts` has 19 tests. They cover: - each of the eight keys refused BY NAME for a value from every arm either face admitted (including `logo` as a URL string, a node and a node list); - the refusal's `code` and message head, naming what the renderer reads instead; - a lit control: a document without the keys, and one with an undeclared key, both parse; - a row showing that the keys the renderer does read still parse; - a `@ts-expect-error` per key, checked by `tsc -p tsconfig.test.json`. Reverse verification ran from the committed state via objectstack `scripts/ablation-replace.mjs`: the anchor must hit, the blob must move, the file is restored to the HEAD blob, and `git diff HEAD` must be empty. | leg | mutation | red / total | |:--|:--|:--| | nav (on `095636b79`) | old `z.array(NavLinkSchema)` arm restored | 2 / 11 | | height (on `095636b79`) | old string-or-number arm restored | 2 / 11 | | logo (on `968cd7698`) | old node-or-node-list arm restored | 2 / 19 | | title (on `968cd7698`) | old `z.string()` arm restored | 2 / 19 | In every leg the lit control stayed green. The restore is proven by the blob matching HEAD (`385141979aac` on `968cd7698`) and an empty `git diff HEAD`. ## 5. Verification (head `a74282b89`; ablation and shape gate as noted) - `pnpm --filter '@object-ui/components^...' build`, then `pnpm --filter @object-ui/types type-check` (src, examples and test projects) and `pnpm --filter @object-ui/components type-check`: exit 0. - `vitest run packages/types/ examples/schema-catalog/` plus the three header-bar renderer tests: 265 files / 7437 tests passed on `a74282b89`. - **Spec Main Shape Gate** (run on `968cd7698`, NOT re-run on `a74282b89`: that merge brought in main's objectui#10334 type change, so the shape-gate reading below is for the pre-merge tree; CI runs it on the merge): - built `@objectstack/spec` from objectstack `origin/main` `9d81af714f49` in a throwaway worktree and packed it; - `node scripts/spec-main-shape-gate.mjs inject --tarball ... --sha 9d81af714f49... --upstream-checkout ...`: exit 0; - `TURBO_FORCE=true turbo run type-check --filter=@object-ui/types --filter=@object-ui/components`: 10/10 tasks; - `report --status 0`: "objectui type-checks against `@objectstack/spec` at that commit". - These gates exit 0: `check:component-surface-parity`, `check:designer-field-key-parity`, `check:prompt-keys`, `check:doc-types`, `check:doc-fences`, `check:new-line-citations`, `check:control-bytes`, `check:changeset-claims`, `check:pending-changeset-literals`, `check:spec-symbols` (on the normal install), `check:doc-example-readers`, `check:test-path-roots`, `check-changeset-presence`, `check-changeset-no-major`. - NOT MEASURED: `check:doc-snippets` and `check:doc-examples`. Reason: PRECONDITION NOT MET (exit 2); they need a 34-package build, so they are left to CI. - ESLint, narrowed to the 7 changed `.ts`/`.tsx` files (`--no-inline-config --format json`): 7 files, 0 errors, 6 warnings. All six are `no-explicit-any` on lines this PR did not edit. The population is `files: ['**/*.{ts,tsx}']` in `eslint.config.js`. Type-aware linting is off, so this diff cannot change the lint result of any file it does not touch. ## Acceptance notes (not filed) - `className` is also unread by `header-bar` (see the probe row above). The PM seat is filing it. - `NavLinkSchema` / `NavLink` lose their `header-bar` consumer. `SidebarSchema.nav` still uses them. - The Schema block in `header-bar.mdx` lists only `crumbs` and the base keys. Its usage note says the height is a fixed `h-16`, but the renderer uses `h-14 sm:h-16`. Neither is touched here. - `check:spec-symbols` exits 1 while spec **main** is injected into the store. Spec main now exports names (for example `ObjectTimelineProps` and `ObjectTreeProps`) that plugin packages already declare locally. This comes from the spec bump, not from this diff: it is green on the normal install. ## Changeset `.changeset/10387-header-bar-unread-keys-retired.md`: `minor` on `@object-ui/types`. The body has the breaking notes and the migration for all eight keys. Implemented by the os-dev agent dispatched from PM seat `session_01877XiBYSaRCk2CU7cMSg3S` (`domain:spec#1`). --- _Generated by [Claude Code](https://claude.ai/code/session_01877XiBYSaRCk2CU7cMSg3S)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Closes #10334
Part of #7759
Clause-②: yes
What
DashboardComponentSchema.dateRangenow takes@objectstack/spec'sDashboardSchema.dateRangeauthoring member by reference, on both faces. This settles the objectui#7759 group F residue rowcomplex.zod.ts#DashboardComponentSchema::dateRange.dateRangeleftDASHBOARD_SPEC_EXCLUDED. Both the Zod mirror (specFieldsExcept(stripImportedDefaults(SpecDashboardSchema).shape, …)) and the TypeScript twin (Omitover the specDashboardinput type) read that one list, so both now project the spec member.z.objectwithdefaultRange: z.string()) and the hand-written TS member were deleted.'dateRange'leftWiderThanDeclared['complex.zod.ts#DashboardComponentSchema'], and theWIDER_ARMSrow was removed. The WIDER header figures were re-derived to 8 / 10 / 11 and 5 / 5 / 0 / 1 (after merging main at2d76f4e67; main read 8 / 11 / 12 and 5 / 6 / 0 / 1), with a history sentence. The pin 'the WIDER ledger's header figures are derived from its ARMS' is green on them.page-app-dashboard-spec-parity.test.ts:dateRangewas removed from the Dashboardomittedlist.content/docs/guide/dashboard-filters.mdnow says the shape is the spec's and that unknown presets and keys are refused.Measurement (step 1): why the mirror was wider
I ran a tsc probe of the old mirror input against the old TS member, key by key. It gave
Type '"defaultRange"' is not assignable to type 'never', so onlydefaultRangewas wider: a barestringagainst the 14-memberDateRangeDefaultRange.fieldandallowCustomRangeagreed. The mirror also stripped unknown keys, which is not type-visible, whereas the spec object is strict.Rule applied: rule 1 (spec-declared) and F1
The spec declares the key in both places I checked:
dist/ui/index.d.ts):dateRange: ZodOptional of a strict ZodObject, withfield?string,defaultRangeaZodDefaultenum of the 14 names (DATE_RANGE_DEFAULT_RANGES, the presets pluscustom), andallowCustomRangeaZodDefaultboolean;mainb81da66d(packages/spec/src/ui/dashboard.zod.ts): the same key set,strictObjectwith named alias refusals, andz.enum(DATE_RANGE_DEFAULT_RANGES).default('this_month').The read site,
resolveDashboardFilterDefsin@object-ui/core, already implements every arm: each preset lifts to{ preset }throughPRESET_RANGES,customstarts empty,fieldfalls back tocreated_at, andallowCustomRangeis read with!== false. So no renderer change was needed, and I judge the protocol right. The mirror still authors no default, because every crossing goes throughstripImportedDefaults.Evidence (HEAD
2d76f4e67, after mergingorigin/mainpast objectui#10300a05c3506)pnpm --filter @object-ui/types type-check(tsc, examples, test config): exit 0, run under the verify lock.pnpm exec vitest run packages/types/:Test Files 228 passed (228),Tests 5099 passed (5099).coreandplugin-dashboardtype-check, and the threedateRangeconsumer suites pass (84 tests). Spec Main Shape Gate: a spec built from objectstack main9d81af71was injected, andtypestype-checks with 0 errors.packages/types/src/__tests__/dashboard-daterange-spec-10334.test.ts, 7 tests:Dashboard['dateRange'], plus a negative control;invalid_valueatdateRange.defaultRangeforlast_7_dayz;unrecognized_keysatdateRange;complex.zod.tsandcomplex.tsinto place. The on-disk markers confirmed the mutation (bare-stringdefaultRange1,'dateRange'in the exclusion list 1, old TS member 1).Tests 4 failed | 3 passed (7).tsc -p tsconfig.test.jsonexited 2 with 3 errors: the pin's mirror equality, plusWiderLedgerMismatchoncomplex.zod.ts#DashboardComponentSchemaand ondateRange.git checkout HEAD -- FILE. Afterwardsgit diff HEADwas 0 bytes, and the HEAD and disk blob hashes matched for both files.@objectstack/specfrom objectstackmainb81da66d, rannpm pack, thennode scripts/spec-main-shape-gate.mjs inject, which exited 0 and re-pointed zod 4.6.1. Against that spec:72 passed.pnpm install --force, verified by the spec 17.4.0 manifest declaring zod^4.4.3.@object-ui/plugin-dashboard^...closure (12 successful, 12 total), thenpnpm --filter @object-ui/core type-checkandpnpm --filter @object-ui/plugin-dashboard type-check, all exit 0. The three dateRange consumer suites (DashboardRenderer.filters, dashboardAuthoredInputs, core dashboard-filters) gave80 passed.--no-inline-config --format jsonover the 5 changed TS files: 5 files, 0 errors. There are 13no-explicit-anywarnings, all on untouched pre-existing lines. Type-aware linting is not configured (noparserOptions.projectineslint.config.js), so the diff cannot move any untouched file's verdict.check-changeset-presence,check:spec-symbols,check:new-line-citations(0 new citation(s)),check-changeset-no-major,check:control-bytes,check:changeset-claims,check:pending-changeset-literals,check:installed-pin-claims,check:doc-fences,check-doc-links.check:doc-snippets. Reason: it exited 2 with PREREQUISITE (it needs a scoped workspace build), so it is left to CI. The doc edit adds no code block.Changeset
.changeset/10334-dashboard-daterange-spec-authoring.mdisminoron@object-ui/types, and it states the breaking change to the validator only. An unknowndefaultRangeand unknown keys insidedateRangeare now refused where before they were accepted or stripped. The TS type is unchanged in effect.Acceptance notes
WIDER_ARMS, and the header pin is green. The next ledger PR in the chain, objectui#10387, re-derives after this one lands.defaultRangewith.default('this_month'), while an authoreddateRangethat omitsdefaultRangestarts with no window at the read site. This PR does not change that.Generated by Claude Code