Skip to content

feat(types)!: ObjectMapConfigSchema is strict, so objectui validate refuses an undeclared map key (objectui#5157) - #10997

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-5157-map-config-strict-a
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-5157-map-config-strict-a

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5157
Clause-②: yes — a published schema's accept set narrows: an undeclared key inside an object-map node's map block stops validating.
Ruling-ref: 5328158731 (B, the map block only) and 5871202958 (batch #238 item 2, letter A, maintainer 「同意」 2026-09-28T13:48Z)

Fix round 1 (bf4797f85f and d5c28d4da2, following contract review 5873208537):

  • the markdown-test-inputs ledger entry for the new pin (CI's Test (shard 1/8));
  • the runtime wording in the plugin-map README and docs page (a latitudeFieId typo draws the "Map configuration required" refusal, not a map);
  • the two docblocks that were stale since objectui#8169;
  • the changeset's "no diagnostic anywhere", corrected because a TypeScript author's compiler did name the key.

No code line moved.

Implemented by the domain:ui seat 2 dispatch, session https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN, claim comment 5872032029.

What changed

  • ObjectMapConfigSchema (packages/types, zod/objectql.zod.ts) closes with .strict(). ObjectMapSchema.map is this object, so the declared face, the runtime check ObjectMap runs and the validate face now share one accept set. .shape is unchanged: the same eight keys, so ObjectMap's FLAT_MAP_CONFIG_KEYS (derived from .shape minus style, seven keys) and the view flatten whitelists (hand-listed, pinned against .shape) see the same keys as before.
  • packages/plugin-map/src/ObjectMap.tsx is not changed. Its existing safeParse of the block now fails on an undeclared key, and the existing console.warn names the key.
  • Docs: the plugin-map README, the plugin-map docs page and the types README now say the map block 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 the map block as a closed sub-block on that face.
  • Changeset: '@object-ui/types': minor. It states the narrowing and the measured breakage, which is none.
  • ⛔ No other component sub-block is touched (the ruling's standing condition).

Pins: red on base, green on head

"Base" here means the head tree with .strict() ablated. The ablation used ablation-replace.mjs (objectstack origin/main copy): anchor }).strict(); went x1 to x0, and the blob went 7ef2545fa19c to 4d0b11cad207. The restore was proven by blob == HEAD (7ef2545fa19c) and an empty git diff HEAD. The pins resolve @object-ui/types/zod through the root vitest alias to packages/types/src/zod/index.zod.ts, so no dist/ is involved.

Pin File Base Head
(ii) safeValidateSchema refuses the typo block: exactly one issue, unrecognized_keys at map, keys latitudeFieId object-map-config-strict-5157.test.ts ❌ ✅
(ii) ObjectMapSchema refuses it at the same path same ❌ ✅
(ii) ObjectMapConfigSchema refuses it, naming the key at the block root same ❌ ✅
(ii) the same node one level down, inside a div's children, is refused same ❌ ✅
(ii) control: the clean block validates, alone and nested same ✅ ✅
.shape still declares the same eight keys; every declared key together parses same ✅ ✅
(iii) the sweep: 20 blocks, each parsed as a map block and validated inside an object-map node (40 rows) same ✅ ✅
(i) runtime: the typo block (which also binds locationField) renders the map and places the marker the declared keys bind ObjectMap.strictConfigWarn-5157.test.tsx ✅ ✅
(i) runtime: [ObjectMap] Invalid map configuration is warned, and its arguments name latitudeFieId same ❌ ✅
(i) control: a clean block warns nothing about its configuration same ✅ ✅

Run totals: base Tests 5 failed / 46 passed (51), head Tests 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 binds locationField. The card's own latitudeFieId typo 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-tree plus a git grep for a map key. It covers objectui examples / apps / content at 40c076fc2d and objectstack examples / apps at 3cf6449389. I reconciled every hit by hand, and I did not reuse the old report's list.

  • objectui: 18 blocks. content/docs/plugins/plugin-map.mdx has 13 brace-literal blocks plus the mapConfig variable in its TypeScript Support section. content/docs/fields/location.mdx has 1. The schema-catalog plugin-map JSON has 3 (event-venue-finder, real-time-delivery-tracking, store-locator-map). The one other grep hit is a catalog entry keyed map (id / meta / schema), which is not a config block.
  • objectstack: 2 blocks, both titleField + locationField: the app-showcase task.view.ts map list view, and task-map-marker-title.test.ts. The other hits are a translations label, the view keyed map, and a comment, none of them a config block.
  • Result: 20 of 20 use declared keys only, so no block is newly refused. The population is held as the SWEEP fixture, 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 by ObjectMap.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/types with .strict() ablated. A dist preflight read the ObjectMapConfigSchema closer in dist/zod/index.zod.js as });, and afterwards I restored and rebuilt, and the closer read }).strict(); again.

objectui validate, root typo node: head exits 1:

1. Unrecognized key: "latitudeFieId"
   Path: map
   Code: unrecognized_keys

base exits 0 with ✓ Schema is valid!.

objectui validate, the same node inside a div's children: head exits 1. The top-level issue is generic, but the arm detail printed under it names the key:

1. Invalid input
   Path: children
   Code: invalid_union
   ...
   1.6 [arm 1/6] Unrecognized key: "latitudeFieId"
       Path: children → 0 → map
       Code: unrecognized_keys

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! and Children: 1.

objectui check (advisory) over a directory holding the root typo, the nested typo, a root typo that also carries className, and the clean node exits 0 on both sides:

  • head lists only the root typo file, under ⚠️ 1 file carries a registered ObjectUI component type but did not validate as an ObjectUI schema, then prints ✓ All checks passed.
  • base lists nothing and prints ✓ All checks passed.
  • check validates only to recognise a file. A root that carries a structural key (children, className, …) is admitted without being validated, so the nested typo and the className typo stay invisible to check. The command's own description sends the verdict to objectui 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 through os-verify-lock.sh, and every verdict line is quoted from that run.

  • Tests: pnpm exec vitest run --maxWorkers=2 packages/types/ packages/cli/ plus the 31 test files outside those packages that read ObjectMapConfigSchema / ObjectMapSchema / FLAT_MAP_CONFIG_KEYS or author an object-map (found with git grep), plus examples/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-check and @object-ui/cli type-check: VERDICT command-exit 0. Both new test files are in their package's tsconfig.test.json (--listFilesOnly, 1 hit each). The plugin-map^... dependency closure was built first.
  • The strict-twin and mirror-parity pins (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 mention map, strip or tolerance, and none describes ObjectMapConfigSchema'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 ✅.
  • Governed-surface test on the changed paths: ✅ NOT GOVERNED.
  • ESLint on the three changed TS files with --no-inline-config --format json: 0 errors, 0 warnings on the two new pins. The one warning in objectql.zod.ts predates 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.
  • Eager closure: @object-ui/types/zod is 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, and pnpm check:eager-closure reads ✅ 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 HEAD against a fresh origin/main f667c1df97 exits 0 (clean). None of the four commits that landed since the base touch these files.
  • NOT MEASURED: 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

  • The dispatch expected the runtime pin (i) to be green on base. Measured, only its render half is. Its warn half is red on base, because the tolerant schema never fails the safeParse, so nothing is warned. The ablation above shows it.
  • The dispatch said FLAT_MAP_CONFIG_KEYS stays at 8 keys. .shape has 8 keys. FLAT_MAP_CONFIG_KEYS filters out style and has 7, before and after.
  • Nested diagnostics: the key is named, but as sub-line 1.6 of 10 under a generic 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 check admits a file whose root carries a structural key without validating it, so no depth of map typo in such a file reaches check. It is advisory by its own description. Recorded, not filed.
  • The docblock of warnOnTopLevelStyleUrl in ObjectMap.tsx still says @objectstack/spec list-view schemas "declare no map block at all". On spec 17.4.0, ListViewSchema declares map, and it refuses latitudeFieId with unrecognized_keys at map. That is a stale comment in a file this change does not touch. Noted, not fixed here, and no carrier.
  • Siblings with the same shape (a declared config sub-block that does not close) exist, for example the object-calendar node's calendar block (spec-derived, kept .passthrough()) and plain z.object config schemas such as SortConfigSchema, DrillDownConfigSchema and ReportExportConfigSchema. Not measured and not touched: the standing condition keeps generalisation out of this card.

Generated by Claude Code

…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
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: types plugin tests labels Sep 28, 2026
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 22 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/10393-retire-bare-map-key.md

  • names plugins/plugin-map.mdx → content/docs/plugins/plugin-map.mdx — edited by this change

    The @object-ui/plugin-map README now describes one registered type, and its sentence claiming a bare array under data reaches the in-memory adapter is corrected: a bare array under data is not a record source on the map (objectui#8348), so inline rows belong under staticData. The same false claim is corrected on the two docs pages that carried it, plugins/plugin-map.mdx and fields/location.mdx.

.changeset/5903-objectgantt-declared-keys.md

  • names src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Both halves move together. The TS declaration (packages/types/src/objectql.ts) and its zod mirror (src/zod/objectql.zod.ts) gain the same ten keys at the same requiredness — all optional — and no KnownDrift entry is added. navigation is taken from @objectstack/spec's NavigationConfigSchema by reference rather than restated, matching ObjectGridSchema.navigation.

.changeset/6940-rowactions-boolean-mirror.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The list view's same-named rowActions in zod/objectql.zod.ts — z.array(z.string()), the legacy bare-name action list on ObjectGridSchema — is a different key that is correct as it stands, is in parity with its own TS twin (rowActions?: string[]), and is not touched.

.changeset/7113-chart-data-model.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    .extend() with a NEW key still works and preserves the fold and the refinement; .optional(), z.discriminatedUnion, z.toJSONSchema and safeValidateSchema are all unaffected. Nothing in this repository calls the throwing combinators on either const, and the published surface already ships refined mirrors (objectql.zod.ts, complex.zod.ts, form.zod.ts, app.zod.ts), so the class is not new — but it is a real behaviour change on a published export and it belongs in the release note rather than in a reviewer's file.

.changeset/7200-object-form-section-style-keys-undeclared.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The authored-metadata type now agrees with @objectstack/spec, whose FormSectionSchema is a strict object declaring neither key, and with the ruling's rationale (maintainer 2026-09-01, verbatim): "retire the reads … Declaring the keys was weighed and not adopted: it would formally invite free Tailwind strings into authored metadata, the exact class the boundary exists to keep out." A ?: never tombstone was not used: ObjectFormSection has no zod mirror (ObjectFormSchema in zod/objectql.zod.ts does not declare sections), so there is no parse door to refuse at, and a tombstone is still a declaration in completion and in the published .d.ts.

.changeset/7265-types-user-filter-field-derives.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    zod/objectql.zod.ts declared two schemas under names @objectstack/spec/ui already exports. They were triaged separately, by reading their sites, and went different ways.

.changeset/7322-object-kanban-group-by-limit.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Breaking for authored metadata: ObjectKanbanSchema.groupField is RETIRED (objectui#7322, ADR-0049 enforce-or-remove), and the two keys the object-kanban renderer actually reads — groupBy and limit — are now DECLARED and validated on both published faces: the TypeScript interface in objectql.ts and the Zod mirror in zod/objectql.zod.ts.

.changeset/7352-drill-down-config-mirror.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    DrillDownConfigSchema is the zod mirror of DrillDownConfig, and both declarations that carry drillDown reference it — ChartSchema (zod/data-display.zod.ts) and ObjectDataTableSchema (zod/objectql.zod.ts) — so the published validator under @object-ui/types/zod reads the key for the first time (objectui#7352).

.changeset/7363-objectql-union-arms.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectGallerySchema and ObjectDataTableSchema are members of ObjectQLComponentSchema on both faces — the TS union in objectql.ts and the zod union in zod/objectql.zod.ts — so AnyComponentSchema, and with it validateSchema / safeValidateSchema / objectui validate, has an arm for object-gallery and object-data-table nodes (objectui#7363).

.changeset/7917-export-breadcrumb-object-tree-zod-schemas.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    AnyComponentSchema declares 107 node component types. 105 of them could be named on the ./zod barrel — ButtonSchema.safeParse(node), which is what a designer, a form builder or a targeted test needs. The arms declaring type: 'breadcrumb' (navigation.zod.ts) and type: 'object-tree' (objectql.zod.ts) could not: both were already export const in their own module, but index.zod.ts — the package's only zod entry point — did not re-export them, so the schemas existed, were maintained, and were applied by the union while no consumer could name them.

.changeset/8221-retire-legacy-string-sort.md

  • names content/docs/plugins/plugin-map.mdx → content/docs/plugins/plugin-map.mdx — edited by this change

    Director ruling, decision batch Release v0.2.1 with changeset automation #77 (2026-09-07), option B. Three faces disagreed about one key: @object-ui/core implemented the string clause on purpose (sort-query.ts, docblock and all), content/docs/plugins/plugin-map.mdx taught it as sort?: string | SortConfig[], and the html tier answered type-mismatch for it because all seven sort registrations publish type: 'array' alone — while @objectstack/spec refuses the string outright on element-record-picker. Option A (per-block string arms) was rejected by name: it would make one key mean different things on different blocks.

.changeset/8478-describe-line-addresses.md

.changeset/8735-objectql-mirror-docblocks-not-defaulted.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Correct four zod/objectql.zod.ts docblocks that described the behaviour objectui#8317 removed. Since that change the zod mirrors strip imported @objectstack/spec defaults at this package's import boundary, but the docblocks on HttpRequestSchema, ListColumnSchema, SelectionConfigSchema and PaginationConfigSchema still said, in the present tense, that method, prefix.type, type and pageSize are defaulted on parse — the opposite of what each export does. Each now says the key is declared and accepted but NOT defaulted on parse.

.changeset/8801-object-kanban-allow-collapse-retired.md

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.

.changeset/8885-object-chart-drilldown-title-compareto.md

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectChart.tsx reads all three off schema, and until now neither published copy declared any of them: not the TS interface (packages/types/src/objectql.ts) and not the zod mirror (packages/types/src/zod/objectql.zod.ts). They rode BaseSchema's index signature / .passthrough() and arrived unvalidated. drillDown was the sharpest case — this component's registry inputs advertise it to the designer palette, and @objectstack/spec publishes ChartDrillDownSchema for exactly this carrier, so an author was offered a key that neither published shape mentioned.

.changeset/8913-object-kanban-columns-declared.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    What moved. ObjectKanbanSchema gains columns on both halves that move together — the TypeScript interface (objectql.ts) and its Zod mirror (zod/objectql.zod.ts). Retiring the bare kanban node type key (objectui#8802) removed the only face that judged a lane, and object-kanban had never declared the key, so it rode BaseSchema's [key: string]: any / .passthrough(): read by the renderer at three sites, named by no published face.

.changeset/8990-object-kanban-groupby-optional.md

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    @objectstack/spec declares the key optional — groupBy: z.string().optional() on ObjectKanbanPropsSchema — while this package required it on the TypeScript declaration (packages/types/src/objectql.ts) and on the Zod mirror (packages/types/src/zod/objectql.zod.ts). objectui was therefore narrower than the protocol on a published key: ObjectKanbanSchema.safeParse and safeValidateSchema refused an object-kanban node the protocol accepts, and such a node could not be annotated with its own type.

.changeset/8992-user-actions-collapse-and-docblock.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    objectql.zod.ts's UserActionsSchema read stripImportedDefaults(Spec).extend({ group, hideFields, rowColor }), an extension that existed only because @objectstack/spec did not declare those three keys while normalizeListViewSchema folded objectui's legacy showGroup / showHideFields / showColor onto them. The protocol adopted all three in 17.3.0 (objectui#5435's ruling), so the extension is now a second local copy of a protocol declaration — the shape two faces start drifting from — and it collapses into the plain by-reference re-export its own note always said it would become.

.changeset/9309-object-gallery-filter-destination-typed.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectGallerySchema.filter is typed as the destination its own docblock names — QueryParams['$filter'] — on both faces, the TS interface in objectql.ts and the zod mirror in zod/objectql.zod.ts (objectui#9309).

.changeset/9511-record-id-is-a-string.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The three authorable keys, each on BOTH faces. ObjectFormSchema.recordId (objectql.ts + zod/objectql.zod.ts), DetailViewSchema.resourceId (views.ts + zod/views.zod.ts) and DetailSchema.resourceId (crud.ts + zod/crud.zod.ts). ⚠️ The crud pair is DetailSchema, not DetailViewSchema, and it reaches the same renderer — not by symbol but by data flow: plugin-detail registers the 'detail' node type onto DetailView. A read that follows TypeScript symbols alone finds two keys and is incomplete.

.changeset/9549-tree-filter-declared.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectTreeSchema.filter is declared on both faces, in the shape objectui#9309 settled for ObjectGallerySchema.filter: QueryParams['$filter'] by indexed access on the TS interface in objectql.ts, and the same two-arm union (array first) on the zod mirror in zod/objectql.zod.ts (objectui#9549).

.changeset/9606-object-kanban-card-title.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Both published faces of the object-kanban arm now name the key: the zod mirror ObjectKanbanSchema in zod/objectql.zod.ts and its TypeScript twin, the ObjectKanbanSchema interface in objectql.ts. Both declare it OPTIONAL, at the same requiredness the other face uses, so the two faces accept and refuse the same documents. (Located and cited by SYMBOL: line addresses in zod/objectql.zod.ts have drifted before, and this change is itself about a drifted mirror.)

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 4b742f41d (merge-base with origin/main): 7 file(s) changed outside .changeset/, read against 1701 pending declaration(s) that publish a body (2303 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3104.0 KB 3104.5 KB
Main entry chunk (gzip) 149.8 KB 350 KB
Entry file index-DYpVls0T.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 558.92KB 133.97KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.62KB 63.16KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.53KB 4.89KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.83KB 36.71KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.49KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.44KB 22.41KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…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
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d68b1086edb8429b9c5e999ed54d156aa2216db4

① Derived judgments

1. The change matches the ruling: PASS.

  • The diff adds exactly one .strict(), on the closer of ObjectMapConfigSchema. It changes no .passthrough() or .strip() and closes no other object.
  • .shape is unchanged at 8 keys. ObjectMap.tsx is not in the diff.
  • Parse sites I found at head with git grep (non-test code):
    • ObjectMap.tsx getMapConfig: ObjectMapConfigSchema.safeParse. On failure it calls console.warn and returns the raw {...config}. It warns and never throws.
    • safeValidateSchema (AnyComponentSchema.safeParse), in two places:
      • objectui validate prints the issues and exits 1.
      • objectui check uses it only to recognise a file. A root object-map with no structural key moves to the advisory "did not validate" list, and the command still exits 0.
      • Both are authoring-time refusals, which the ruling covers.
    • @object-ui/types/zod validateSchema (AnyComponentSchema.parse): this throws, but it has no caller in packages, apps or examples; it appears only in a README example. A third-party caller is on the validate face, which the ruling covers.
    • StrictAnyComponentSchema (the objectui#5250 twin): it already refused this typo on base (dev report H2) and has no consumer outside tests. Re-deriving an already-closed object changes nothing, and no twin test touches object-map.
    • @object-ui/core validateSchema (the dev-mode validation in SchemaRenderer) imports no zod face, so it is unaffected. vscode-extension does not import the types zod face either.
    • Stored and named views:
      • ListView (resolveListMapConfig) and ObjectView (pickFlatMapConfig) pass only flat keys through hand-listed whitelists and never emit a map key. A view's map block therefore never reaches the strict parse, so stored views are not refused at runtime.
      • No member of ListViewSchema or ObjectViewSchema references ObjectMapConfigSchema.
    • Parity pins:
      • zod-mirror-parity registers the pair by name only.
      • The two mapFlatten pins read .shape and parse a block of declared keys only.
      • None of them reads the unknown-key policy, so all are unaffected.

2. The sweep: PASS. I re-derived it with git grep.

  • objectui main 4b742f41d2 (examples, apps, content):
    • plugin-map.mdx has 13 brace blocks plus the mapConfig variable, so 14.
    • location.mdx has 1.
    • The schema-catalog has 3 JSON blocks.
    • That makes 18, and every fixture row matches its source block key for key.
  • objectstack: I re-fetched main at bea6d2ea3b, which is newer than the fixture's 3cf6449389. It still has the same 2 blocks. One is on the named view map in task.view.ts, so a named-view block is included. The objectui population has no view-type map config.
  • Outside the ruled population, packages/plugin-map/README.md carries 3 blocks. All use declared keys only, so nothing needs to change.
  • Result: 20 of 20 clean.

3. The pins, judged by reading: PASS.

  • (ii) The 4 refusal rows are red on base, because the tolerant parse succeeds, and green on head. They assert the exact issue: unrecognized_keys at [map], keys [latitudeFieId]. The clean control is lit on both sides.
  • (i) The warn row serialises the warn's arguments and asserts that latitudeFieId is among them, so the key is named. It is red on base because no warning fires. The dev's deviation from the order ("green on base") is correct. The render row and the no-warn control are lit.
  • (iii) The 40 rows are green on both sides by design: they hold a population, and an 18/2 count guards against a vacuous fixture. The dev's ablation, 5 of 51 rows red, matches this reading.
  • The ruling's "FLAT_MAP_CONFIG_KEYS … (8 keys)" mixes up .shape (8 keys) with FLAT_MAP_CONFIG_KEYS (7, because it drops style). Both are unchanged by construction, and the dev's pin on the 8 .shape keys is correct.

4. BLOCKER: CI is red. Test (shard 1/8) failed, and so did the aggregate Test check.

  • The failing test is scripts/__tests__/markdown-test-inputs.test.ts. Its audit reports unadjudicated-test for packages/types/src/__tests__/object-map-config-strict-5157.test.ts: it "reads markdown the ledger does not describe (content/docs/fields/location.mdx, content/docs/plugins/plugin-map.mdx)".
  • The cause is the SWEEP rows, which name those two documents as provenance strings. The dev's local gate list never ran scripts/__tests__.
  • Fix: add this entry to ADJUDICATED in scripts/markdown-test-inputs.mjs, in path order:
  // objectui#5157. The SWEEP fixture names these documents as the provenance of
  // hand-transcribed `map` blocks; the test opens neither.
  [
    'packages/types/src/__tests__/object-map-config-strict-5157.test.ts',
    { reads: [], notRead: ['content/docs/fields/location.mdx', 'content/docs/plugins/plugin-map.mdx'] },
  ],

5. Rendering of the nested refusal.

  • objectui validate names the key: arm sub-line 1.6 of 10, at children → 0 → map. That meets the ruling's bar, so no readability follow-up is owed for validate.
  • objectui check prints the file and type "object-map" but never the key, at any depth.
    • A nested typo, or any file whose root carries a structural key, never reaches its validation step. It exits 0 with "All checks passed".
    • By the ruling's letter ("if the rendered message does not name the key, that is a follow-up card"), check meets that condition. The dev recorded it as "not filed".
    • The seat should file one follow-up card for objectui check, not for validate's arm printer, after this lands and not in this PR.
    • The ruling's premise that check refuses holds only for a root object-map with no structural key.

6. Docs and changeset: wording defects (blocking; fix in the same commit as item 4).

  • a. packages/plugin-map/README.md and content/docs/plugins/plugin-map.mdx both say "At runtime the map still renders". That is false for the example the same sentence names.
    • latitudeFieId in place of latitudeField leaves the block with no coordinate binding. hasCoordinateBinding needs locationField or the lat/lng pair.
    • So ObjectMap renders the "Map configuration required" refusal (objectui#8169). Each file documents that refusal in its very next section.
    • The runtime pin only draws a map because it also sets locationField.
    • Replacement text:
      • README: "At runtime ObjectMap does not throw: it renders from the declared keys and warns [ObjectMap] Invalid map configuration in the console, naming the same key; the undeclared key is never read. A typo that leaves the block with no coordinate binding, as latitudeFieId does, therefore draws the "Map configuration required" refusal below rather than a map."
      • mdx: "At runtime ObjectMap does not throw: it renders from the declared keys, and the console warns [ObjectMap] Invalid map configuration, naming the same key. A typo that leaves the block with no coordinate binding, as latitudeFieId does, draws the refusal described under "An unconfigured map refuses" rather than a map."
  • b. The objectql.zod.ts docblock says "…this object stripped it, parsed clean, and the map rendered empty with no diagnostic." That has been stale since objectui#8169: on base the typo already draws the refusal.
    • Replacement: "…this object stripped it and parsed clean, and nothing named it: the card's typo drew the generic "Map configuration required" refusal (objectui#8169), which names the key the author meant, not the one they wrote."
    • The plugin-map pin's docblock has the same stale claim. Replace "the card's symptom was an empty map and a quiet console" with "the card's symptom was a quiet console: no warning named the key".
  • c. The changeset says "and no diagnostic anywhere named it". That is false for a TypeScript author: the closed ObjectMapConfig interface makes the typo a compile error that names the key, as this PR's own docblock says. It is also false for the published strict twin.
    • Replacement: "and neither objectui validate nor the runtime named it (only a TypeScript author's compiler did)."
    • Every other changeset sentence checks out:
      • an undeclared key is now refused instead of stripped;
      • at the root, unrecognized_keys at map and exit 1;
      • nested, invalid_union at children, with the arm detail naming the key at children → 0 → map;
      • the check bucket behaviour matches isObjectUiSchemaFile;
      • the runtime warns and does not throw;
      • .shape is unchanged;
      • measured breakage is 0, with the snapshot caveat stated.
  • d. The packages/types/README.md change is accurate, and the rest of the paragraph stays true (a node stays tolerant; the twin is derived). "Refused by both faces" names the two zod faces, not the renderer. Nothing in the diff claims the runtime refuses.
  • Optional, not blocking: in the README and mdx, "at map" holds for a root node. A nested node reports under invalid_union at children, with the key in the arm detail.

7. No generalisation: PASS. Only the one closer changed. The siblings (the calendar block, SortConfigSchema, DrillDownConfigSchema, ReportExportConfigSchema) appear in the Acceptance notes only.

② Semver level

minor, Clause-②: yes. This is correct.

③ Boundary flags

Implemented-by: claude/issue-5157-map-config-strict-a
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: FAIL

To land, the PR needs:

  • (1) the ADJUDICATED ledger entry from ①.4, so that Test (shard 1/8) goes green;
  • (2) the replacement wording in ①.6a to c, in the README, the mdx, the objectql.zod.ts docblock, the plugin-map pin's docblock and the changeset.

Separately, the seat should file the objectui check follow-up card from ①.5 after this lands.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3104.0 KB 3104.5 KB
Main entry chunk (gzip) 149.8 KB 350 KB
Entry file index-DYpVls0T.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 558.92KB 133.97KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.62KB 63.16KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.53KB 4.89KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.83KB 36.71KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.49KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.44KB 22.41KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3104.0 KB 3104.5 KB
Main entry chunk (gzip) 149.8 KB 350 KB
Entry file index-DYpVls0T.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 558.92KB 133.97KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.62KB 63.16KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.53KB 4.89KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.83KB 36.71KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.49KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.44KB 22.41KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d5c28d4da2f2560b5045ed1ec4a98e585321e8dc

This is an in-seat delta review of fix round 1: d68b1086ed to d5c28d4da2, two commits. I checked it against record 5873208537 (FAIL: the CI ledger entry and three wording defects; the code, pins, sweep and semver passed).

① Derived judgments

  • Scope: 6 files, +33 −11. The only line changes outside prose are one ADJUDICATED ledger entry and comment-only docblocks. The .strict() closer and every pin assertion are byte-identical to the reviewed head.
  • ①.4, the ledger: in place (bf4797f85f). object-map-config-strict-5157.test.ts is adjudicated with reads: [] and notRead for location.mdx and plugin-map.mdx, the reviewer's exact form, with a reason comment: the two paths are provenance strings, and the test opens neither page. The dev reports --audit: "106 candidate test files, all adjudicated". markdown-test-inputs.test.ts passes at head (3 files, 76 tests with the two pins).
  • ①.6a, the runtime wording: in place in the plugin-map README and the docs page. Both now say ObjectMap does not throw, renders from the declared keys and warns naming the key, and that a latitudeFieId typo, which leaves no coordinate binding, draws the "Map configuration required" refusal. The optional root-node qualifier for "at map" is also taken.
  • ①.6b, the docblocks: in place. The objectql.zod.ts docblock names the objectui#8169 refusal, and the plugin-map pin's docblock says "a quiet console: no warning named the key".
  • ①.6c, the changeset: in place. It now says "neither objectui validate nor the runtime named it (only a TypeScript author's compiler did)". The frontmatter is byte-identical.
  • The PR body: the seat rewrites the pin-(i) row and the render-half sentence at landing to say the pin's fixture also binds locationField, which is why it draws.
  • Commit trailers are the model-free Claude pair.

② Semver level

minor and Clause-②: yes hold, unchanged from 5873208537.

③ Boundary flags

  • CI: this record posts only after every check at d5c28d4da2 is final and green, Test (shard 1/8) included. The landing queue gates on it and re-runs git merge-tree against current main.
  • Follow-up owed (record 5873208537, ①.5): objectui check never prints the refused key, at any depth, and a nested typo or a root carrying a structural key never reaches its validation step. The seat files one card after this lands. It is not in this PR.
  • Landing: ready, auto-merge, and needs:contract-review removed.

Implemented-by: claude/issue-5157-map-config-strict-a
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 15:48
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit d6d8fb9 Sep 28, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-5157-map-config-strict-a branch September 28, 2026 16:04
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…re-qualify objectstack#3546 (objectui#10803, batch 8) (objectstack-ai#11012)

Fixes objectstack-ai#10803
Clause-②: no

Dispatched implementation of the `domain:ui` seat objectstack-ai#1 claim (comment
`5873413663`) on objectui#10803, batch 8, session
`https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk`. Citations
only: no sentence's claim moves, the one edited pending changeset keeps
a byte-identical frontmatter, and no runtime string moves, so the only
new changeset is an empty-frontmatter sweep. No test pins any changed
text (**The literal-anchor sweep**), so no test file is edited.

This batch carries release `5872268200`'s two lists (the seat's ruling A
in ACCEPT `5870953708`, which review `5870922323` read the same way):
- the **10 in-class lines that cite a dead objectstack number written
bare**: `objectstack-ai#9934` seven times, "PR `objectstack-ai#6281`" twice, "objectstack PR
`objectstack-ai#8452`" once;
- the **6 live-but-wrong `objectstack-ai#3546` lines** that mean objectstack#3546:
five written bare, one written with this repository's name.

Numbers are written in code spans in this body when they are the dead or
wrong spelling, so GitHub does not link them to the unrelated objectui
items they would resolve to.

## Why `Fixes`

Both lists read **0** after this batch (**Census**), and the closure
census over the card's two classes (pending `.changeset/*.md` prose and
non-test `packages/*/src`) finds nothing else dead:
- **Bare numbers**, batch 7's instrument (printed under **Census**): 965
distinct at the branch point, 961 at this head. The 916 between 100 and
25000 at the branch point were each read once as objectstack issues: 877
answer 200 and 39 answer 404. The 39 were read again as objectstack
issues and as pulls (404 each) and as objectui issues (200 each). Three
of them are this batch's (`objectstack-ai#9934`, `objectstack-ai#6281`, `objectstack-ai#8452`), gone at this head.
The other 36 remain, and each sentence was read: every one cites an
objectui card or pull request, and each answers 200 in objectui with the
subject its sentence names.
- **The instrument's blind spot** (a number written after `/`, `-` or
`.`, as in `objectui#9469/objectstack-ai#8649`): 46 numbers between 100 and 25000
appear only in that form at this head. Read as objectstack issues: 45
answer 200; one answers 404, `objectstack-ai#8649`, which sits in two lines as
`objectui#9469/objectstack-ai#8649` and means objectui#8649 (200).
- **Qualified citations** at this head: every distinct `objectui#N` and
objectui issue or pull URL (2723 numbers) answers 200; every distinct
`objectstack#N` and objectstack issue or pull URL (345 numbers) answers
200, except objectstack#14026, which answers 301 (transferred to
objectui#10102, re-qualified by batch 6).

So nothing dead is left in the card's classes, and nothing in them
resolves to the wrong repository as far as these instruments see.
**Acceptance notes** 1 and 2 record what is outside the classes, and
what could not be measured.

## Premise, re-measured on `origin/main` `665025908` (the branch point)

- **The three dead numbers.** objectstack `issues/N` and `pulls/N`
answer 404 for 9934, 6281 and 8452 (the in-range census read each as an
issue a second time). Lit controls in the same run: objectstack issues
3546, 3391, 5775 and 8326 answer 200; objectstack `pulls/13267` and
`pulls/16920` answer 200.
- **What they resolve to here.** objectui `objectstack-ai#9934` is a CI finding about
the `needs:contract-review` label; objectui PR `objectstack-ai#6281` is an app-shell
Studio grid change; objectui `objectstack-ai#8452` is a `not_contains` operator bug.
None is what its sentence meant.
- **`objectstack-ai#3546`, both repositories.** objectstack#3546 (200) is "detail/form
面的 edit/delete 按钮接入服务端 effective 操作集", the effective operation set on
detail and form surfaces, which each of the six sentences describes.
objectui `objectstack-ai#3546` (200) is the missing-i18n-keys card, unrelated. The 96
`objectui#3546` lines in the locale packs and the "slice seven" line in
`.changeset/3880-es-done-listo.md` are that i18n card, and are
untouched.
- **The landing commits**, each read over REST (`commits/SHA`) and
checked with `compare/SHA...main`, which answers `behind_by 0` for each,
i.e. each is an ancestor of objectstack `main`:
- `79c46da90`, "feat(contract): producer-side user-facing marking for
hook refusal messages — userMessage channel (objectstack-ai#9934) (objectstack-ai#9992)",
2026-08-19: `ApiErrorSchema.userMessage`, and the dispatcher door
(`packages/runtime/src/http-dispatcher.ts`) emits it as a declared
sibling of `code` and `message`;
- `85ec26d28`, "spec: SDUI props — enforce or remove (objectstack-ai#5775) (objectstack-ai#6281)",
2026-08-07: retires `page:card.body` in favour of `children`, with an
ADR-0087 D2 conversion;
- `27358d517`, "feat(spec,rest): add batch recordIds to security/explain
(objectstack-ai#8326) (objectstack-ai#8452)", 2026-08-13.
- These are the landings release `5872268200` names; review `5870922323`
read all three against their messages. Batch 7 already cites `79c46da90`
and `85ec26d28` at other sites in these classes.
- The one edited changeset,
`.changeset/7980-agent-key-envelope-read.md`, is pending: it is present
in `.changeset/` on `main`. This checkout is not shallow.

## Census (the enumeration pin for this batch)

The two lists (REF = a commit or tree):

```
git grep -nP '(?:^|(?<=[^\w#&/.\-]))#(9934|6281|8452|3546)(?![0-9A-Za-z_])' REF -- '.changeset/*.md' 'packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/test/**' ':!**/tests/**' | wc -l
git grep -n 'objectui#3546' REF -- '.changeset/*.md' 'packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/test/**' ':!**/tests/**' ':!packages/i18n/src/locales/**' | wc -l
```

| REF | bare four | `objectui#3546` outside the locale packs |
|:--|:--|:--|
| `665025908` (branch point) | **15**: `objectstack-ai#9934` 7, `objectstack-ai#6281` 2, `objectstack-ai#8452` 1,
`objectstack-ai#3546` 5 | **2**: the `plugin-detail` line and the i18n card's own line
in `3880-es-done-listo.md` |
| `8fe632d96` (this head) | **0** | **1**: the `3880-es-done-listo.md`
line, which is the i18n card and stays |

- Lit controls at this head: `objectstack#3546` reads 10 lines against 3
at the branch point (the 6 re-qualified here, and one in this PR's sweep
changeset); the 96 `objectui#3546` lines in the locale packs are
unchanged.
- The bare-number instrument, batch 7's (the census of **Why `Fixes`**):

```
git grep -hoP '(?:^|(?<=[^\w#&/.\-]))#\d+(?![0-9A-Za-z_])' REF -- '.changeset/*.md' 'packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/test/**' ':!**/tests/**' | sort -u | wc -l
```

965 at the branch point, 961 at this head: exactly `objectstack-ai#3546`, `objectstack-ai#6281`,
`objectstack-ai#8452` and `objectstack-ai#9934` are gone, and nothing is new. The blind-spot reading
replaces the lookbehind class with `[^\w#&]`.

## Citation form

- **An objectstack commit** is written as batches 6 and 7 wrote their
stand-ins: objectstack and the 9-character backticked sha of the commit
on objectstack `main` that landed the change the sentence rests on.
- **A bare `objectstack-ai#3546`** becomes `objectstack#3546`, including inside the
`[objectstack-ai#3546]` tags that open four comments; the one written with this
repository's name is re-qualified the same way.

## Mapping

| site | before | after |
|:--|:--|:--|
| `.changeset/7980-agent-key-envelope-read.md` | "(the `objectstack-ai#9934` channel,
whose presence *is* the marking)" | "(the objectstack `79c46da90`
channel, …)" |
| app-shell `index.ts`, the note above the `readEnvelopeFailureText`
export | "(the `objectstack-ai#9934` channel, which rides through the 5xx prose
withhold untouched)" | "(the objectstack `79c46da90` channel, …)" |
| app-shell `utils/apiErrorEnvelope.ts`, the `userMessage` bullet | "the
END USER (`objectstack-ai#9934`)" | "the END USER (objectstack `79c46da90`)" |
| app-shell `utils/apiErrorEnvelope.ts`, the `errorFromThrown` bullet |
"… has emitted the channel since `objectstack-ai#9934`" (a quotation) | "… has emitted
the channel since [objectstack `79c46da90`]" (**Special cases** 1) |
| app-shell `PackageFormDialog.tsx`, `readEnvelopeUserMessage`'s
docblock | "(`error.userMessage`, `objectstack-ai#9934`)" | "(`error.userMessage`,
objectstack `79c46da90`)" |
| app-shell `StudioDesignSurface.tsx`, the package-switcher failure read
| "the producer's `objectstack-ai#9934` channel" | "the producer's channel
(objectstack `79c46da90`)" |
| app-shell `packages-io.ts`, `duplicatePackage`'s failure arm | "has
emitted since `objectstack-ai#9934`" | "has emitted since objectstack `79c46da90`" |
| components `containers.tsx`, the `PageCardRenderer` `body` read |
"objectstack#5775 (PR `objectstack-ai#6281`, ADR-0087 D2)" | "objectstack#5775
(objectstack `85ec26d28`, ADR-0087 D2)" |
| components `containers.tsx`, the `card` registration's `children`
input | "objectstack#5775 (PR `objectstack-ai#6281`)" | "objectstack#5775 (objectstack
`85ec26d28`)" |
| plugin-grid `hooks/useRecordCrudVerdicts.ts` | "shipped in objectstack
PR `objectstack-ai#8452`" | "shipped in objectstack `27358d517`" |
| app-shell `RecordDetailView.tsx`, two comments | "[`objectstack-ai#3546`] …" |
"[objectstack#3546] …" |
| app-shell `RelatedRecordActionsBridge.tsx` | "[`objectstack-ai#3546`] Intersect the
child object's …" | "[objectstack#3546] …" |
| plugin-detail `renderers/record-details.tsx` | "[`objectstack-ai#3546`] Also AND
inline-editability …" | "[objectstack#3546] …" |
| plugin-form `fieldWriteGate.ts` | "`objectstack-ai#3546` intersects that with the
server's effective API operation set" | "objectstack#3546 intersects …"
|
| plugin-detail `index.tsx`, the `inlineEdit` input comment |
"(`objectui#3546`)" | "(objectstack#3546)" |

Each `objectstack-ai#9934` sentence is about the `userMessage` channel that
`79c46da90` added; each `objectstack-ai#6281` sentence is about the `page:card` `body`
retirement that `85ec26d28` carries; the `objectstack-ai#8452` sentence is about the
batch `recordIds` form that `27358d517` shipped.

## Special cases (the judgement calls)

1. **A quotation.** The `apiErrorEnvelope.ts` bullet quotes the note
beside `sendThrownError` in objectstack's
`packages/rest/src/package-routes.ts`, verbatim. That note still reads
"since objectstack-ai#9934" on objectstack `main` (`e956924` at the time of this PR);
there the number means objectstack's card, which answers 404, and here
it resolves to the unrelated objectui item. The quote keeps its words
and takes the commit in square brackets, the editorial mark for a
substituted word. The note's claim is the one `79c46da90` carries: its
`http-dispatcher.ts` hunk adds the `userMessage` spread to the
dispatcher door's `extra`.
2. **Line breaks moved** only where the stand-in made a line markedly
longer: app-shell `index.ts`, `StudioDesignSurface.tsx` and
`packages-io.ts`. Elsewhere the one line just grows, as batch 7's
`objectstack#3391/objectstack#3546` lines did.
3. **Wording kept.** "the `objectstack-ai#9934` channel" becomes "the objectstack
`79c46da90` channel" in two places, and "the producer's channel
(objectstack `79c46da90`)" in `StudioDesignSurface.tsx`, where "the
producer's objectstack … channel" would read as if the producer were
objectstack. No other word moves.

## The literal-anchor sweep (both test-pin classes, ruling `5861900779`)

- **Instrument.** Every string, template and regex literal in the 4108
tracked test and script files (104609 distinct strings, 2339 regexes),
read with the TypeScript scanner.
- **Test.** Does its occurrence count DROP between `665025908` and this
head in any of the 14 changed files, raw or comment-flattened?
- **Result.** 30 distinct literals drop, every one generic: digits,
punctuation, whitespace and character classes (`#\d+`, `\d{3}`, `[:#]`,
`\s+`), the bare words `objectui` and `objectui#`, `/objectui#\d+/`, and
comma-and-line-break fragments. None is a changed phrase, a dead number
or a `objectstack-ai#3546` tag. A search of tests for the changed spellings finds them
only in test doc comments (listed in **Acceptance notes** 1), which no
assertion reads.

## Held

**By the serial rule: nothing.** Open PRs were mapped at the branch
point before the first edit (7 open) and again before the push (7 open;
objectui#11003 and objectstack-ai#10997 have since merged, and objectui#11010 and
objectstack-ai#11006 have opened).

- **objectui#10990** and **objectui#11010** share
`packages/app-shell/src/index.ts`. objectui#10990's hunk is in the
side-effect import list, near the Cloud widgets; objectui#11010's is one
line in the `views` type export list. This PR's two changed lines are in
the note above the `readEnvelopeFailureText` export, more than ninety
lines above both hunks.
- **objectui#5400** (Version Packages) carries
`.changeset/7980-agent-key-envelope-read.md`, which it consumes at
release; it regenerates and is not a hold. If it lands first, that
file's one changed line has already published, and this PR's hunk there
conflicts with the deletion.
- Trial merges with this head (`git merge-tree --write-tree`): clean for
objectui#11010, objectstack-ai#11006, objectstack-ai#10990, objectstack-ai#10930 and objectstack-ai#10777. objectui#10278
conflicts in `ObjectGrid.tsx`, `plugin-grid/README.md` and
`content/docs/plugins/plugin-grid.mdx`, and conflicts in the same three
files against the branch point alone; none is in this diff.

## Changesets

- `.changeset/10803-dead-citation-sweep-eighth-batch.md`, EMPTY
frontmatter. It covers the comment-only edits in 5 released packages
(`app-shell`, `components`, `plugin-detail`, `plugin-form`,
`plugin-grid`) and the one edited pending changeset; no published
behaviour changes through them. It writes no bare number itself.
- No runtime string moves, so there is no `patch` changeset (amendment
`5860244997` Q1 = A applies to none of these lines: the `plugin-detail`
`index.tsx` line is a comment beside the `inlineEdit` input, not its
`description` string).

## Proof of prose-only (C4), against `665025908`

- **Source.** Each of the 12 touched `.ts` / `.tsx` files was parsed at
`665025908` and at this head with TypeScript 6.0.3's `createSourceFile`,
and re-printed by `createPrinter({ removeComments: true })`: 12 of 12
prints are identical, with 0 parse diagnostics. Lit controls on the same
instrument: editing a string literal moves the print; re-spacing a
comment does not.
- **Changesets.** The frontmatter block of
`.changeset/7980-agent-key-envelope-read.md` is byte-identical at
`665025908` and this head (md5); the overwrite gate below agrees.
- **Scope of the diff:** 14 files, +39 / −20: 1 edited and 1 new
changeset, and 12 non-test source files in 5 released packages. No test
file.

## Gates, on this head `8fe632d96`

Each line is the gate's own verdict and exit code, captured by
redirect-then-`$?`.

- `node scripts/check-changeset-presence.mjs`, exit 0: "12 source
file(s) of 5 released package(s) changed, and this change declares 1
changeset(s): .changeset/10803-dead-citation-sweep-eighth-batch.md."
- `pnpm changeset:check`, exit 0: "All workspace packages are in the
changeset fixed group." / "No changeset declares a `major` bump."
- `node scripts/check-changeset-overwrite.mjs` (report-only), exit 0: "1
changeset(s) added, 1 modified, 0 deleted"; for the modified one,
`declared at base` equals `declares now` ("@object-ui/app-shell: minor,
@object-ui/console: patch").
- `pnpm check:changeset-claims` (report-only), exit 0: "Every package
declared across those 1 body(ies) is either not negated …", and the
standing notice "15 pending changeset(s) describe a file this change
touches". Read against the diff: a pending changeset quoting a replaced
spelling would carry a bare number the census sees, and the census reads
0 in `.changeset/`.
- `pnpm check:control-bytes`, exit 0: "check-control-bytes: OK (scanned
9291 tracked text file(s); skipped 85 binary)." A `grep -P` control-byte
self-scan of the 14 files finds none.
- `pnpm check:new-line-citations`, exit 0: "VERDICT
new-cross-file-line-citations: 0 new citation(s), enforcement
report-only -> exit 0".
- `pnpm check:pending-changeset-literals`, exit 0: "No test source names
a pending changeset."
- Also run over the touched comments, on the tree this head carries:
`pnpm check:spec-symbols` exit 0 ("spec member citations: 1424 sources +
184 documentation pages; nothing cites a key its spec symbol does not
declare."); `pnpm check:installed-pin-claims` exit 0 ("OK"); `pnpm
check:comment-mask-corpus` exit 0 (1 disagreeing file, within the
ceiling objectui#7882 holds open); `node
scripts/check-hand-rolled-comment-mask.mjs` exit 0 ("OK every carrier is
a DEBT entry, and every DEBT entry still carries one."); `pnpm
check:handler-key-reads` exit 0 ("every judged read is a declared member
of its arm").
- The governed-surface predicate over the 14 paths, exit 0: "NOT
GOVERNED — 14 path(s) checked against 5 governed surface(s); none
matched." Lit control `AGENTS.md`: exit 3.

**Tests and type-check**, through the shared verify lock, on
`8fe632d96`.
- `scripts/__tests__/`, the whole directory, whose whole-tree scanners
read the touched files and changesets: `Test Files 177 passed | 2
skipped (179)`, `Tests 5350 passed | 2 skipped (5352)`, `VERDICT
command-exit 0`.
- `packages/components/`, `plugin-detail/`, `plugin-form/` and
`plugin-grid/`, whole packages, in one run: `Test Files 847 passed | 2
skipped (849)`, `Tests 8441 passed | 33 skipped (8474)`, `VERDICT
command-exit 0`; the source files were byte-identical to this head's.
- `packages/app-shell/`, whole package: `Test Files 1 failed | 863
passed | 1 skipped (865)`, `Tests 1 failed | 8835 passed | 9 skipped
(8845)`. The one failure is `AppContent.inaccessibleAppStrand.test.tsx`,
'bounces to the DECLARED landing where the deployment declares one', a
network-escape guard trip (a real socket at
`localhost:3000/api/v1/meta`) under the full suite on a shared box. That
file is not in this diff, and C4 shows no code token moves; re-run alone
three times on this head it reads `Tests 6 passed (6)` each, and the CI
test shards are green on this head.
- Type-check: `turbo run build` of the 28-package dependency closure
(`Tasks: 28 successful, 28 total`), then `pnpm
--workspace-concurrency=2` with the 5 package filters `run type-check`:
5 script echoes, 5 `Done`, `VERDICT command-exit 0`.
- No red leg: the sweep found no anchor to move, so there is no pin
whose old copy should fail.

CI on `8fe632d96`: 43 check-runs, 39 success, 3 skipped, 1 failure;
`Spec Main Shape Gate` success. The failure is `Bundle Analysis`, step
"Check console performance budget", which fails the same way on `main`
at this PR's branch point `665025908` and at `d6d8fb9d7` and `2049b03df`
after it; this diff moves no code token.

## Acceptance notes

1. **Outside the card's two classes, as it stands** (whole tree at this
head, bare spelling): `objectstack-ai#9934` in 3 test doc-comment lines in 2 files and
in 1 `apps/console` source line (`AgentConnectSection.tsx`, "the objectstack-ai#9934
channel"); `objectstack-ai#6281` in 1 test doc-comment line; `objectstack-ai#8452` in 1 test
doc-comment line; bare `objectstack-ai#3546` in 23 test lines in 14 files and 19
published `CHANGELOG.md` lines in 8 files. `apps/console`, tests and
`CHANGELOG` history are outside the classes this card carries (triage
`5858031244`; release `5861553562`: recorded, not carried).
2. **Not measured: `framework#N` and `cloud#N`.** The two classes also
cite 82 distinct `framework#N` and 43 distinct `cloud#N` numbers.
Neither repository is on this card's lists, and both answer 403 through
this container's proxy, so no reading of them is offered.
3. **Left as the brief allows.** The two test comments that call
objectui#8652 `pm:blocked` (it reads `pm:on-hold`) and batch 7's
sweep-changeset phrase "declared separately below" are in files this PR
does not touch, and stay.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk)_

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…e parser tier answers (objectui#10981) (objectstack-ai#11006)

Fixes objectstack-ai#10981
Clause-②: no — message, `.describe()` and docblock text only; every
accept or refuse verdict stays byte-identical.

Implemented by the `os-dev` agent dispatched from the `domain:spec @
objectui` seat, session
`https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm`
(`mode:subagent`). Base `4b742f41d`. PR objectstack-ai#10986 landed on
`zod/objectql.zod.ts` meanwhile, so `main` `665025908` was merged in
before the final push. Head `ebd156ef5`.

## What changed

Only the false clause changes, on four sites. Each clause names the code
the parser tier answers for that key and value shape, as measured on the
live registry (next section).

| site | face | before | after |
|---|---|---|---|
| `zod/form.zod.ts`, the `FormSchema.mode` refusal (objectui#10286) |
zod message and `.describe()`, one string through `retirementTombstone`
| "rendered the same form — no error, no warning" | "rendered the same
form — no render-time error or warning; only the parser tier's
`unknown-prop` warning noticed it" |
| `data-display.ts`, the `TimelineSchema.events` docblock
(objectui#6170) | TS face, emitted `.d.ts` | "drew an EMPTY rail, with
no error and no warning" | "drew an EMPTY rail, with no render-time
error or warning; only the parser tier's `unknown-prop` warning noticed
it" |
| `base.ts`, the `data-table` sentence in the `BaseSchema.bind` docblock
| TS face, emitted `.d.ts` | "renders its header over an empty body,
with no error and no warning" | "renders its header over an empty body,
with no render-time error; nothing on the page says why, but a
render-time console warning (`[ObjectUI] DataTable bind:`,
objectui#6575) and the parser tier's `unknown-prop` warning both name
it" |
| `zod/objectql.zod.ts`, the note on
`OBJECT_GRID_EXPORT_OPTIONS_GUIDANCE` (objectui#7762) | source note on a
non-exported constant; it ships in the emitted JS, in no `.d.ts` | "no
error, no warning, no console line" | "no render-time error, warning or
console line (only the parser tier's `type-mismatch` warning noticed
it)" |

- **Why `base.ts` has its own wording.** That site was not silent at
render either. `data-table` has logged `[ObjectUI] DataTable bind:` for
an authored `bind` since objectui#6575 (`aff3d7aef`, 2026-08-28). The
docblock was written a day later (`99a3c2db6`, 2026-08-29). `merge-base
--is-ancestor aff3d7a 99a3c2d` answers 0, with the control leg
`94d4876df` also answering 0. So "only the parser tier noticed it" would
be false there too.
- **Why `exportOptions` names `type-mismatch`.** The `object-grid`
registration declares `exportOptions` as `type: 'object'`. So
`checkType` answers an array, not the undeclared-key branch.
- **New pin:**
`packages/types/src/__tests__/silence-clause-parser-tier-closure-10981.test.ts`.
- **Optional item, taken:**
`packages/components/src/renderers/__tests__/header-bar-unknown-prop-parser-tier-10981.test.tsx`,
a new file beside `container-declaration-ratchet`.
- **Changeset:**
`.changeset/10981-silence-clause-parser-tier-closure.md`,
`@object-ui/types: patch`.

## The parser tier, measured before each clause was written

**Instrument.** The same as PR objectstack-ai#10980: a one-shot probe, deleted and
never committed, under `packages/components/src/renderers/__tests__/`.
- It imported the components renderers, `fields` and 18 of the 19
`plugin-*` packages (all but `plugin-designer`). `plugin-tree` has no
vitest alias, so it came in by relative source path.
- The manifest was built from every KNOWN registry key through
`manifestFromConfigs`, the way `container-declaration-ratchet.test.tsx`
builds it.
- Each leg ran `validateTree` on a control and on the control plus the
one key, and reports only the diagnostics that key added.
- Each leg also rendered through the real `SchemaRenderer`, inside
`PredicateScopeProvider` and `SchemaRendererProvider` with a stub data
source, all inside `act`. `console.warn` and `console.error` were spied,
and the HTML was compared with the control's.

| site | keys | value shapes | the parser tier adds | render leg |
|---|---|---|---|---|
| `FormSchema.mode` | `form`, `ui:form`. Bare `form` resolves to
`ui:form`; `view:form` is `skipFallback` | `edit`, `read`, `disabled`,
`create`, `view` (the objectui#10286 pin's set) | exactly one
`unknown-prop` on `mode`, 10 of 10 legs | 0 warnings, 0 errors on every
variant. HTML equals the control's except for one `mode` attribute on
the form element |
| `TimelineSchema.events` | `timeline` (resolves to `view:timeline`),
`view:timeline`, `plugin-timeline:timeline` (the `TimelineRenderer`
registration `TimelineSchema` describes) | 2 events; `[]`; `items` plus
`events` | exactly one `unknown-prop` on `events`, 7 of 7 |
byte-identical HTML, 0 warnings, 0 errors |
| `BaseSchema.bind` on `data-table` | `data-table`, `ui:data-table` |
`bind: 'users'` and `bind: 'app.settings.users'`, each with `data`
absent and with `data: []` | exactly one `unknown-prop` on `bind`, 8 of
8 | byte-identical HTML (the "No results" body in both), 0 errors, and
exactly 1 console warning on every variant: `[ObjectUI] DataTable bind:
… is ignored` |
| `exportOptions` | `object-grid`, `plugin-grid:object-grid` | `['csv',
'xlsx']`, `['csv']`, `[]` | exactly one `type-mismatch` ("expected an
object"), 6 of 6 | the control is `{ formats: ['csv', 'json'] }`, the
default an array loses to. Byte-identical HTML, the export control and
the rows present in both, 0 warnings, 0 errors |

**Controls.**
- `form` with an invented key draws `unknown-prop`. `view:form` with
`mode: 'edit'` draws nothing, because that registration declares `mode`.
- On `object-grid`: an `exportOptions` object draws nothing, a string
draws `type-mismatch`, and an invented key draws `unknown-prop`. So the
probe tells the two codes apart on one node.
- `accordion` with a `body` list draws `not-a-container`, the third
code.
- With `data` absent, the `data-table` control already carries
`missing-required-prop` on `data`; the table above counts only what
`bind` adds.
- The first control render in the file logged one `react-i18next`
initialisation warning. No variant did.

**The same answer before each retirement (source reading, not
executed).** The `mode`, `events` and `exportOptions` sentences are past
tense.
- `FormSchema.mode`, at `a05c35063^`, the parent of the objectui#10286
commit: the `ui:form` registration declared the same 13 inputs as today,
none named `mode`. `plugin-form`'s `form` was `namespace: 'view',
skipFallback: true`. `validate.ts` had the same base-prop set, without
`mode`, and one `unknown-prop` branch.
- `TimelineSchema.events`, at `195052fff^`, the parent of the
objectui#6170 tombstone commit: neither `plugin-timeline:timeline` nor
`view:timeline` declared `events`. A `git log -G` for an `events` input
name over `packages` finds no registration that ever declared one, back
to the shallow boundary (2026-07-20).
- `exportOptions`, at `f08d1a86a^`, the parent of the objectui#7762
refusal: `GRID_QUERY_INPUTS` declared `exportOptions` as `type:
'object'`. It had done so since `375efb402` (2026-08-17), and
objectui#7762 was filed on 2026-09-05. `checkType` gave a single
non-enum arm `type-mismatch`, as today.
- `BaseSchema.bind` is present tense, measured today.
- The history reads ran on a deepened checkout (`git fetch
--deepen=4000`, shallow boundary 2026-07-20).

## Census over joined text (the closure nail)

**Instrument.** A one-shot script. It enumerates the non-test `.ts`
files of `packages/types/src` from the ref with `git ls-tree` and reads
each one from the ref. It joins JSDoc and line-comment continuations and
`'…' + '…'` concatenations, and unescapes quotes. Then it counts:
- A: "no (adjective) error" and "no (adjective) warning" within 60
characters, with no sentence break;
- B: the same, in reverse order;
- C: "no (adjective) error or / nor / and (adjective) warning", the
family's corrected shape;
- D: "without (an) error or / and warning";
- E: "neither error nor warning";
- F: "silent…" within 80 characters of a negated warning, in either
order;
- G, the wide net: any negated warning ("no warning", "without a
warning", "never warns", …);
- H, the wider net over other nouns: "no diagnostic", "no console line",
"without complaint", "not a warning".

| reading | base `4b742f41d` | main `665025908` | head `ebd156ef5` |
|---|---|---|---|
| files enumerated | 67 | 67 | 67 |
| A | 4 | 4 | 0 |
| B, D, E | 0 | 0 | 0 |
| C, and how many are followed by "the parser tier's" | 302, 300 | 302,
300 | 302, 302 |
| F | 1 | 1 | 0 |
| G | 6 | 6 | 2 |
| H | 25 | 25 | 24 |
| control: the objectui#10928 clause | 282 | 282 | 282 |
| control: the objectui#10959 tail | 18 | 18 | 20 |

- **Positive control.** C fires on every one of the 300 known
objectui#10928 / objectui#10959 control strings. A fires on exactly the
four sites the card names, which were found independently.
- **Every non-control hit at base, read.**
- A is the four sites and nothing else. The two C hits without the
parser tier's clause are two of the four (`base.ts`, `data-display.ts`).
F is the fourth site again.
- G adds two hits that say "no warning window" (`objectql.ts`,
`zod/objectql.zod.ts`, objectui#6475). That is a deprecation window, not
a silence claim, so they stay.
- H is outside the card's phrasing family. It is listed under Acceptance
notes and not changed.

## Every other byte, and only comment and string lines

- Substitute the old clause back for the new one on joined text: each
head file equals its base file, 4 of 4, against `4b742f41d` and again
against `665025908`.
- The first four ablation legs put the old clause back with the anchor
tool. Each mutated blob equals the base blob byte for byte: `eba1c495f`,
`4dfce4ac6`, `b1abfe909` and `e2c561734` are the base blobs of the four
files.
- `git diff -U0` over the four files: every changed line is a ` * `
JSDoc line or a `+ '…'` string-literal line.
- The objectui#10286, objectui#6170, objectui#7762 and `bind` pins pass
unchanged; they are in the full `types` run.
- **Emitted `dist`**, after `pnpm --filter @object-ui/types build` at
head:
  - the old clauses, both spellings: 0 files;
  - the `mode` clause: `zod/form.zod.js`, `zod/index.zod.js`;
- the `events` clause: `data-display.d.ts`; the `bind` clause:
`base.d.ts`;
- the `exportOptions` note: `zod/objectql.zod.js`, `zod/index.zod.js`
(the emitted JS keeps comments).

## Reverse verification (ablation), after the fix was committed

**Instrument.** objectstack's `scripts/ablation-replace.mjs` in WRAP
mode, run from this worktree.
- The anchor must hit exactly once and the on-disk blob must move. The
tool proves each restore: blob equals HEAD and `git diff HEAD` is empty.
- The driver re-proved each restore independently, and ran inside an
EXIT / INT / TERM trap.
- The types pin imports `../zod/*` source and reads the files from disk.
The components pin resolves the registry from source through the vitest
aliases. So no build sits between a mutation and a run.

| leg | red | red tests |
|---|---|---|
| the old clause back into `FormSchema.mode` | 3 / 9 | the `mode` arm,
the spelling walk, the pairs walk |
| the old clause back into `TimelineSchema.events` | 3 / 9 | the
`events` docblock, the pairs walk, the parser-tier walk |
| the old clause back into `BaseSchema.bind` | 3 / 9 | the `bind`
docblock, both walks |
| the old clause back into the `exportOptions` note | 2 / 9 | the note,
the pairs walk |
| the note names `unknown-prop` in place of `type-mismatch` | 1 / 9 |
the note |
| `zod/ai.zod.ts`, which the pin does not name: an objectui#10928 clause
loses its parser-tier half | 1 / 9 | the parser-tier walk |
| `header-bar.tsx` starts declaring `title` (components pin) | 4 / 6 |
both population legs, both lit controls |

- Every leg restored to its HEAD blob (`3a595a747`, `33e3a4770`,
`a12a28cf1`, `4e75a80cf` twice, `a40e67ff6`, `ce3850854`) with an empty
`git diff HEAD`. After the run, `git status --porcelain` was empty.
- **The first `header-bar` attempt was a no-op.** Its replacement
contained the anchor, so the tool refused (anchor count 1 to 1) and ran
no test. It was re-run with a replacement that does not contain the
anchor. The table shows the second run.

## The pins

**`silence-clause-parser-tier-closure-10981.test.ts`.** It follows the
house style of PR objectstack-ai#10956 and PR objectstack-ai#10980. `@object-ui/types` imports no
workspace package, so it holds the prose.
- **Zod face.** `FormSchema.mode` is read through its refusal: code
`invalid_type` and path `['mode']` first, then the clause. The walk
covers every spelling of the objectui#10286 pin, and the `.describe()`
text.
- **`exportOptions`.** The bare array is refused with `invalid_type` at
`['exportOptions']`, with exactly the `.describe()` string. Then the
note above `OBJECT_GRID_EXPORT_OPTIONS_GUIDANCE` must carry the clause
and name no `unknown-prop`.
- **TS face.** The `TimelineSchema.events` and `BaseSchema.bind`
docblocks.
- **The closure walk.** It reads every non-test `.ts` file of
`packages/types/src` from disk, with the root taken from the pin's own
`import.meta.url`.
- No joined text may pair "no error" with "no warning" (patterns A, B,
D, E).
- Every C phrasing must have "the parser tier's" within 80 characters
after it.
- Non-vacuity, by name: the four site files are reached, and both
earlier waves' clauses are found.

**`header-bar-unknown-prop-parser-tier-10981.test.tsx`.** It answers the
open question from PR objectstack-ai#10956 and PR objectstack-ai#10980, on the live components
registry.
- On `header-bar` and `ui:header-bar`, each of the 9 refused keys draws
exactly one `unknown-prop` naming the key. This holds in every value
shape the objectui#10387 and objectui#10286 pins refuse.
- The control draws nothing.
- The lit control shows `crumbs` declared, and no refused key declared.

## Dispatch assumptions, checked

1. **Holds.** There are exactly four sites, one per file, on `4b742f41d`
and on `665025908`. The census was widened to patterns A–H above, with
controls.
2. **Holds, and made more exact.**
- `form` with `mode` draws `unknown-prop`: for all five spellings, on
`form` and `ui:form`.
- A bare `timeline` with `events` draws `unknown-prop`. The bare key
resolves to `view:timeline`. The docblock describes `TimelineRenderer`,
which is registered as `plugin-timeline:timeline` (`skipFallback`). That
registration also answers `unknown-prop`, so the clause is true for the
node the docblock names and for the bare key.
- `data-table` with `bind` draws `unknown-prop`. But "only the parser
tier noticed it" would be false there, because a render-time console
warning fires too (objectui#6575). The site names both.
- `object-grid` with an `exportOptions` array draws `type-mismatch`: for
three array shapes, on both keys.
3. **Holds**, by source reading at the parent of each retirement. See
above.
4. **Changed during the run.** PR objectstack-ai#10986 landed (`8aa68b159`) on
`zod/objectql.zod.ts`. `main` `665025908` was merged in before the final
push, with no conflict. The note edited here is away from that hunk. PR
objectstack-ai#10997 had not landed at `665025908`.

## Gates, head `ebd156ef5`

All ran after the merge commit. Exit codes were captured before any
pipe.
- `pnpm --filter @object-ui/types build` → 0. Its
`check-dist-completeness` verified 134 emitted files.
- `pnpm --filter @object-ui/types type-check` → 0. `tsc -p
tsconfig.test.json --listFiles` includes the new pin.
- `pnpm --filter @object-ui/types lint` → 0, with 0 errors. The 295
warnings are existing `no-explicit-any` ones; none is in the new file.
- `pnpm exec vitest run packages/types/` → 0: 272 files and 5993 tests
passed.
- Outside `types` → 0: 27 files and 931 tests passed.
- The script tests that read `packages/types/src`:
`body-dialect-producer-scan`, `changeset-polarity-census`,
`check-action-forward-parity`, `check-changeset-claims`,
`check-component-surface-parity`, `check-doc-component-types`,
`check-doc-example-types`, `check-handler-key-read-sites`,
`check-readme-exports`, `check-spec-symbol-derivation`,
`component-node-vocabulary-7434`,
`one-authority-per-exported-name-6273`,
`check-pending-changeset-literals`, `spec-symbol-ledger-types-7265`,
`unconsumed-widget-option-claim-6186`, `vite-types-zod-lazy`;
  - `component-fixture-declared-keys` in `examples/schema-catalog`;
- the new components pin, `container-declaration-ratchet`, the four
`header-bar` component tests, `skill-guide-data-table-binding` and
`data-table-bind-diagnostic`;
- the parser pins `body-dialect-6771` and
`containment-declared-slot-9910`.
- `pnpm --filter '@object-ui/components^...' build`, then `pnpm --filter
@object-ui/components type-check` → 0. `tsconfig.test.json --listFiles`
includes the new components pin. `eslint` over the two new test files →
0, with no findings.
- `check-changeset-presence`, `check-changeset-no-major`,
`check-changeset-fixed`, `check-changeset-overwrite` → 0.
`check-changeset-claims` → 0; it is report-only.
- `pnpm check:control-bytes`, `check:new-line-citations` (0 new),
`check:pending-changeset-literals`, `check:test-path-roots`,
`check:phantom-deps` and `check:self-import` → 0.
- `check-governed-queue-guard --test` over the 7 paths → NOT GOVERNED.
- Declared to CI: repo-wide `pnpm lint`, full `pnpm test` and the other
workflow gates.

## Acceptance notes

Not changed here, and left for the seat to route.
- **Same false claim, outside this package.**
`skills/objectui/rules/protocol.md`, the "Readers only." paragraph, says
a `bind` on `data-table` renders its header over an empty body "no
error, no warning". The render leg and the parser leg above both
contradict it. It is on the governed `skills/**` surface, outside this
card.
- **Same claim in unpublished test prose.** The headers of
`base-bind-declared.test.ts` and
`object-grid-export-options-refusal-7762.test.ts` repeat it.
`mirror-partial-record-narrowing-8516.test.ts` has a different "no error
and no warning" claim, about grid columns. All three are test files,
outside the census.
- **Pending changesets.**
`.changeset/7762-object-grid-export-options-bare-array-refusal.md` and
`.changeset/6575-data-table-bind-diagnostic.md` describe the pre-fix
behaviour in the old words. Pending changesets are out of scope here.
- **Adjacent wording, left in place.**
- The `base.ts` heading "the one documented silent failure" stays. The
corrected sentence now says in which sense it is silent: nothing on the
page.
- The `exportOptions` guidance string says an array "is silently dropped
for the csv/json default". That is present tense about the renderer, and
"silently" alone is outside the phrasing family.
- **H, the "no diagnostic" nouns (24 at head).** Most are scoped to a
named tier or instrument: "on either face" (`objectql.ts`), "zero
unresolved-module diagnostics" from `tsc`, `checkType` by name, the TS
index signature. A few are unscoped claims about an authored value:
`ai.ts` (`maxResults`), `crud.ts`, `data-display.ts` (three) and
`zod/data-display.zod.ts`. They were not measured here.
- **Observation.** A `mode` on a `form` reaches the DOM as an inert
`mode` attribute on the form element. The visible form is identical.
- **Boundary.** `view:form` declares `mode` (enum `create`, `edit`,
`view`). It is a different registration and node type, which the
`FormSchema` docblock does not describe.
- **A parser-tier finding, reported to the seat.** `validateTree`
answers `bind` with `unknown-prop` on `list` and `tree-view` too,
although both read `bind` through `useDataScope`. No registration
declares a `bind` input, and the parser's base-prop set has no `bind`.

---
_Generated by [Claude
Code](https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: types plugin tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Decision] ObjectMapConfigSchema 要不要 .strict() —— 类型面的保证只保护 TS 作者,而 AI 产出的 metadata 恰恰不带类型

2 participants