feat(types)!: ObjectMapConfigSchema is strict, so objectui validate refuses an undeclared map key (objectui#5157) - #10997
Conversation
…red map key is refused `ObjectMapSchema.map` is this object, so `safeValidateSchema` (and `objectui validate` with it) now refuses a `map` block carrying an undeclared key with an `unrecognized_keys` issue at `map` that names the key, while `ObjectMap` keeps rendering and warns through its existing `safeParse` channel. `.shape` is unchanged. Pins: the validate face (red without `.strict()`), the runtime face (renders and the warning names the key), and the pre-landing sweep of every `map` block found in authored metadata, held as a fixture. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
A `'@object-ui/types': minor` changeset that states the narrowing: `objectui validate` refuses an undeclared `map` key by name, `objectui check` stays advisory, and the runtime keeps rendering and warning. The plugin-map README, the plugin-map docs page and the types README say the block is closed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…g warn pin Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
|
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
|
…kdown-input ledger `object-map-config-strict-5157.test.ts` names `content/docs/fields/location.mdx` and `content/docs/plugins/plugin-map.mdx` as provenance on its sweep fixture rows and opens neither, so it is recorded with an empty `reads` and both pages under `notRead`. The declared entries, and so what `--list` / `--changed` answer for any path, are unchanged. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Contract reviewServed-tier: ① Derived judgments1. The change matches the ruling: PASS.
2. The sweep: PASS. I re-derived it with
3. The pins, judged by reading: PASS.
4. BLOCKER: CI is red.
5. Rendering of the nested refusal.
6. Docs and changeset: wording defects (blocking; fix in the same commit as item 4).
7. No generalisation: PASS. Only the one closer changed. The siblings (the calendar block, ② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL To land, the PR needs:
Separately, the seat should file the |
✅ 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
|
…at named it before A `latitudeFieId` typo leaves the block with no coordinate binding, so `ObjectMap` draws the "Map configuration required" refusal (objectui#8169), not a map. The plugin-map README and docs page now say the component does not throw, renders from the declared keys and warns naming the key, and that a binding-less typo draws that refusal; both qualify "at `map`" as the root-node answer. The schema docblock, the runtime pin's docblock and the changeset no longer describe the pre-#8169 empty map, and the changeset says what did name the key before (only a TypeScript author's compiler). Frontmatter unchanged. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ 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: This is an in-seat delta review of fix round 1: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #5157
Clause-②: yes — a published schema's accept set narrows: an undeclared key inside an
object-mapnode'smapblock stops validating.Ruling-ref: 5328158731 (B, the
mapblock only) and 5871202958 (batch #238 item 2, letter A, maintainer 「同意」 2026-09-28T13:48Z)Fix round 1 (
bf4797f85fandd5c28d4da2, following contract review5873208537):markdown-test-inputsledger entry for the new pin (CI'sTest (shard 1/8));latitudeFieIdtypo draws the "Map configuration required" refusal, not a map);No code line moved.
Implemented by the
domain:uiseat 2 dispatch, sessionhttps://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN, claim comment 5872032029.What changed
ObjectMapConfigSchema(packages/types,zod/objectql.zod.ts) closes with.strict().ObjectMapSchema.mapis this object, so the declared face, the runtime checkObjectMapruns and the validate face now share one accept set..shapeis unchanged: the same eight keys, soObjectMap'sFLAT_MAP_CONFIG_KEYS(derived from.shapeminusstyle, seven keys) and the view flatten whitelists (hand-listed, pinned against.shape) see the same keys as before.packages/plugin-map/src/ObjectMap.tsxis not changed. Its existingsafeParseof the block now fails on an undeclared key, and the existingconsole.warnnames the key.mapblock is closed. No sentence anywhere said the block tolerated unknown keys, or that validate accepted them. The types README said the whole rendering face, every named mirror included, is tolerant, so it now names themapblock as a closed sub-block on that face.'@object-ui/types': minor. It states the narrowing and the measured breakage, which is none.Pins: red on base, green on head
"Base" here means the head tree with
.strict()ablated. The ablation usedablation-replace.mjs(objectstackorigin/maincopy): anchor}).strict();went x1 to x0, and the blob went7ef2545fa19cto4d0b11cad207. The restore was proven by blob == HEAD (7ef2545fa19c) and an emptygit diff HEAD. The pins resolve@object-ui/types/zodthrough the root vitest alias topackages/types/src/zod/index.zod.ts, so nodist/is involved.safeValidateSchemarefuses the typo block: exactly one issue,unrecognized_keysatmap, keyslatitudeFieIdobject-map-config-strict-5157.test.tsObjectMapSchemarefuses it at the same pathObjectMapConfigSchemarefuses it, naming the key at the block rootdiv'schildren, is refused.shapestill declares the same eight keys; every declared key together parsesmapblock and validated inside anobject-mapnode (40 rows)locationField) renders the map and places the marker the declared keys bindObjectMap.strictConfigWarn-5157.test.tsx[ObjectMap] Invalid map configurationis warned, and its arguments namelatitudeFieIdRun totals: base
Tests 5 failed / 46 passed (51), headTests 51 passed (51). Exactly the four (ii) refusal rows went red, plus the runtime warn row. That fifth red is expected. At base the typo parses clean, so the warning never fires, and that silence is the card's own symptom. The runtime row that is green on both sides is the render half. The pin's map still draws, with no throw, because its fixture also bindslocationField. The card's ownlatitudeFieIdtypo alone leaves no coordinate binding, so it draws the "Map configuration required" refusal (objectui#8169) rather than a map.The sweep (iii)
I re-derived the population with
git ls-treeplus agit grepfor amapkey. It covers objectuiexamples/apps/contentat40c076fc2dand objectstackexamples/appsat3cf6449389. I reconciled every hit by hand, and I did not reuse the old report's list.content/docs/plugins/plugin-map.mdxhas 13 brace-literal blocks plus themapConfigvariable in its TypeScript Support section.content/docs/fields/location.mdxhas 1. The schema-catalogplugin-mapJSON has 3 (event-venue-finder,real-time-delivery-tracking,store-locator-map). The one other grep hit is a catalog entry keyedmap(id/meta/schema), which is not a config block.titleField+locationField: the app-showcasetask.view.tsmap list view, andtask-map-marker-title.test.ts. The other hits are a translations label, the view keyedmap, and a comment, none of them a config block.SWEEPfixture, with each row naming its source by path and a quoted anchor, never a line address. The fixture header says the population is a snapshot. The three catalog entries are also re-read live and validated whole byObjectMap.catalogRecordSource-6939.test.tsx, which is green on head.The nested-document measurement
Measured with the built CLI (
packages/cli/dist/cli.js) against built@object-ui/types, on fixtures from the seat's scratchpad. For base, I rebuilt@object-ui/typeswith.strict()ablated. A dist preflight read theObjectMapConfigSchemacloser indist/zod/index.zod.jsas});, and afterwards I restored and rebuilt, and the closer read}).strict();again.objectui validate, root typo node: head exits 1:base exits 0 with
✓ Schema is valid!.objectui validate, the same node inside adiv'schildren: head exits 1. The top-level issue is generic, but the arm detail printed under it names the key:That is 1 of 10 arm sub-lines. The other nine are the non-matching arms (
expected object / string / number / boolean / null). base exits 0 with✓ Schema is valid!andChildren: 1.objectui check(advisory) over a directory holding the root typo, the nested typo, a root typo that also carriesclassName, and the clean node exits 0 on both sides:⚠️ 1 file carries a registered ObjectUI component type but did not validate as an ObjectUI schema, then prints✓ All checks passed.✓ All checks passed.checkvalidates only to recognise a file. A root that carries a structural key (children,className, …) is admitted without being validated, so the nested typo and theclassNametypo stay invisible tocheck. The command's own description sends the verdict toobjectui validate.⇒ The rendered refusal names the key at both depths, so under the ruling's condition this needs no follow-up card. The noise around the nested line is recorded in the Acceptance notes.
Gates
Everything below is at head
d68b1086ed. Test runs went throughos-verify-lock.sh, and every verdict line is quoted from that run.pnpm exec vitest run --maxWorkers=2 packages/types/ packages/cli/plus the 31 test files outside those packages that readObjectMapConfigSchema/ObjectMapSchema/FLAT_MAP_CONFIG_KEYSor author anobject-map(found withgit grep), plusexamples/schema-catalog/test/. Result:Test Files 358 passed (358),Tests 9311 passed (9311).pnpm exec vitest run --maxWorkers=2 packages/plugin-map/plus the new types pin:Test Files 34 passed (34),Tests 243 passed (243).pnpm --filter @object-ui/types type-check,@object-ui/plugin-map type-checkand@object-ui/cli type-check:VERDICT command-exit 0. Both new test files are in their package'stsconfig.test.json(--listFilesOnly, 1 hit each). Theplugin-map^...dependency closure was built first.strict-authoring-face-8345.test.ts,zod-mirror-parity.test.ts) are inside the types run above, and green.check-changeset-presence:✅ 3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s).check-changeset-no-major:✅ No changeset declares a major bump.check-changeset-overwrite:✅ No pre-existing changeset was modified or deleted.check:changeset-claims(report-only) exits 0. It lists 22 pending changesets that name a file this change touches. I re-read the seven that mentionmap, strip or tolerance, and none describesObjectMapConfigSchema's accept set.check:pending-changeset-literals:✅.check:new-line-citations:VERDICT new-cross-file-line-citations: 0 new citation(s).check:control-bytes:✅ OK. A self-scan of the changed files for control bytes found none.check:spec-symbols:✅.check-doc-links:Links are valid across 17 scan roots.check:doc-fences,check:doc-types,check-prompt-component-keys,check:test-path-roots,check:vi-mock-specifiers: all✅.✅ NOT GOVERNED.--no-inline-config --format json: 0 errors, 0 warnings on the two new pins. The one warning inobjectql.zod.tspredates this change. This is a narrowed run: the config enables no type-aware linting, so this diff cannot move another file's verdict. The repo-wide lint is CI's.@object-ui/types/zodis not in the console's eager closure. The console build's own guard printed[plugin assert-types-zod-stays-lazy] ... 1 chunk(s) holding the validators, none in the eager closure, andpnpm check:eager-closurereads✅ Console eager closure is 3103.9 KB gzipped ... (budget: 3104.5 KB, headroom: 0.6 KB). That is the same reading the dispatch quoted, so the delta is 0.0 KB at the gate's precision.git merge-tree --write-tree origin/main HEADagainst a freshorigin/mainf667c1df97exits 0 (clean). None of the four commits that landed since the base touch these files.check:doc-snippets/check:doc-examples. Reason: they compile against every built package. This diff adds no code fence and changes no TypeScript declaration, so CI's run is the reading.Acceptance notes
safeParse, so nothing is warned. The ablation above shows it.FLAT_MAP_CONFIG_KEYSstays at 8 keys..shapehas 8 keys.FLAT_MAP_CONFIG_KEYSfilters outstyleand has 7, before and after.invalid_union, and nine of those lines describe arms that were never candidates. That is a readability note on the CLI's arm printer, not a missing diagnostic. I filed no card.objectui checkadmits a file whose root carries a structural key without validating it, so no depth ofmaptypo in such a file reachescheck. It is advisory by its own description. Recorded, not filed.warnOnTopLevelStyleUrlinObjectMap.tsxstill says@objectstack/speclist-view schemas "declare nomapblock at all". On spec 17.4.0,ListViewSchemadeclaresmap, and it refuseslatitudeFieIdwithunrecognized_keysatmap. That is a stale comment in a file this change does not touch. Noted, not fixed here, and no carrier.object-calendarnode'scalendarblock (spec-derived, kept.passthrough()) and plainz.objectconfig schemas such asSortConfigSchema,DrillDownConfigSchemaandReportExportConfigSchema. Not measured and not touched: the standing condition keeps generalisation out of this card.Generated by Claude Code