Skip to content

feat(spec): object-map, object-gantt and object-tree get ComponentPropsMap rows, derived from the renderers' read points (objectui#8348 Q1-C) - #18403

Merged
os-warren merged 7 commits into
mainfrom
claude/issue-18305-component-props-map-map-gantt-tree
Sep 16, 2026
Merged

os-warren merged 7 commits into
mainfrom
claude/issue-18305-component-props-map-map-gantt-tree

Conversation

@os-warren

@os-warren os-warren commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #18305

Executes the objectui#8348 ruling 「8348 以协议为准」 (decision batch #83, 2026-09-08) and batch #136 item 3 (Q1-C, maintainer 「同意」): ComponentPropsMap gains object-map, object-gantt and object-tree, each row's key set derived the #7751 way — from the objectui renderer's own read points, at the .objectui-sha pin 53ded82b.

Not derived from @object-ui/types' mirror. That mirror standing in as the authority for map and gantt is the defect this card closes, and for object-tree the mirror is measurably wrong about four keys (below).

The #7751 derivation table, extended by three rows

Every citation is a line in the objectui checkout at 53ded82b. The shared record-source ladder is resolveRecordSourceConfig in core/src/utils/record-source.ts — rung 1 data (:151, returned VERBATIM as a ViewData), rung 2 staticData (:155, wrapped into { provider: 'value' }), rung 3 objectName (:162).

object-map — plugin-map/src/ObjectMap.tsx, registry shell plugin-map/src/index.tsx

key read point declared as
objectName ladder rung 3; :793 (fetch dep), :890 (navigation) z.string().optional()
data :169 array-shorthand head, then resolveRecordSourceConfig at :174 ViewDataSchema
staticData ladder rung 2 z.array(z.unknown())
filter :742 — $filter: schema.filter z.array(ViewFilterRuleSchema)
sort :743 — $orderby: convertSortToQueryParams(schema.sort) z.array(SortItemSchema)
map :370 — getMapConfig branch 1, the author face; the registration's declared { name: 'map', type: 'object' } input z.unknown() (see note)
mapStyle :365 — schema.mapStyle || schema.map?.style z.string()
navigation :889 — useNavigationOverlay z.unknown()
enableClustering :905 — enableClustering ?? (schema.enableClustering || …) z.boolean()

Measured and deliberately NOT declared: the flat map-config spellings getMapConfig branch 2 reads at :382-396 (locationField, latitudeField, longitudeField, titleField, descriptionField, zoom, center) — the ObjectView/ListView flatten product, ruled an internal transport form, not a second authoring surface (maintainer ruling objectui#5018, 2026-08-17); style, which :283 reads ONLY to say it is not consumed as a map style (objectui#5017) and which is the node's own inline CSS record; and the React props of ObjectMapProps (clusterRadius, the data ARRAY prop, the four callbacks, className, dataSource).

object-gantt — plugin-gantt/src/ObjectGantt.tsx, registry shell plugin-gantt/src/index.tsx

key read point declared as
objectName ladder rung 3; :1359 (layout key), :1491 (navigation), :1873 (export name) z.string().optional()
data resolveRecordSourceConfig at :593 ViewDataSchema
staticData ladder rung 2 z.array(z.unknown())
filter :738 — $filter: schema.filter z.array(ViewFilterRuleSchema)
sort :739 — $orderby: convertSortToQueryParams(schema.sort) z.array(SortItemSchema)
gantt :499-501 — getGanttConfig branch 1; the registration's { name: 'gantt', type: 'object' } input, and :501 validates it against this repo's OWN GanttConfigSchema GanttConfigSchema
navigation :1487 — schema.navigation ?? { mode: 'drawer' } z.unknown()
label :1871 — resolveInlineI18nLabel(schema.label, displayLocale) in the export-file-name chain (:1849 is the comment ABOVE that chain, not a read) I18nLabelSchema
skipWeekends :1205 — !!sw into the working calendar z.boolean()
holidays :1206 — new Set(hol) for the duration math z.array(z.string())
persistLayout :1357 — schema.persistLayout === false z.boolean()
viewName :1359 — the objectName:viewName storage key z.string()
markers :1826 — markers={schema.markers} z.array(z.unknown())
criticalPath :1829 — criticalPathDefault={!!schema.criticalPath} z.boolean()
showBaselines :1832 — schema.showBaselines !== false z.boolean()
readOnly :1833, :1916 — !!schema.readOnly z.boolean()
mobileReadOnly :1834 — schema.mobileReadOnly !== false z.boolean()

Measured and deliberately NOT declared: the 30 flat GanttConfig spellings branch 2 reads at :513-543 (objectui#6469 inherited the objectui#5018 ruling for this block); title, which this renderer never reads; and a row cap — the reload's $top is the platform ceiling NON_GRID_ROW_CEILING_TOP, which the renderer's own comment marks 「⛔ Not authorable」.

object-tree — plugin-tree/src/ObjectTree.tsx, registry shell plugin-tree/src/index.tsx

key read point declared as
objectName ladder rung 3; :534, :570, :605 z.string().optional()
data resolveRecordSourceConfig at :359 — rung 1, returned VERBATIM as a ViewData. That ONE site is the whole support for this arm ViewDataSchema
staticData ladder rung 2 z.array(z.unknown())
filter :474 — $filter: schema.filter z.array(ViewFilterRuleSchema)
tree :108 — the nested block getTreeConfig reads; the registration's { name: 'tree', type: 'object' } input TreeConfigSchema
navigation :591 — useNavigationOverlay({ navigation: (schema as any).navigation }) z.unknown()

data IS on the row, and that is the measurement, not family symmetry. The card left this conditional — 「if the renderer's authored surface has no data, the row declares none」 — and the condition is FALSE here: the renderer reaches schema.data through the shared ladder at :359, which returns it verbatim as a ViewData. ⛔ :496 is not a second site for that arm, and an earlier revision of this body over-claimed it as one: (rest as any).data ?? (schema as any).data is gated by Array.isArray(passed) on the very next line, so it honours only the bare-ARRAY shorthand this row refuses — the same shorthand object-map measures and declines above. One ladder site is sufficient and :359 is it, so the row stands on a corrected citation. What is absent is the DECLARATION, on both published faces: ObjectTreeSchema on @object-ui/types at this pin declares no data, no staticData, no filter and no navigation at all, and makes objectName required — four keys the renderer reads. That is precisely the mirror-is-wrong shape the ruling puts the protocol in front of, so the row follows the read points and the mirror is the face that has to follow.

No sort. ObjectTree.tsx:473-484 issues $filter, $top and $expand and no $orderby, and nothing else in the file reads an order. A sort door here would publish a key with no read site — the exact defect #7751 exists to remove, in the other direction.

Three derivation decisions, each pinned rather than left to review

  1. The flat config spellings stay unauthorable on all three. ObjectView / ListView build these nodes by spreading options.map / options.gantt / options.tree's CONTENTS at the top level, carrying no block key at all; that is an internal transport form (objectui#5018, inherited by objectui#6469), and the map and gantt renderers name every flat key beside a present block as IGNORED in a dev warning. Writing one now gets a wrong-layer prescription naming the config block — the channel object-calendar already uses. Each set is held equal to the config block it points at (ListMapConfigSchema.shape; GanttConfigSchema.shape plus the legacy singular dependencyField; TreeConfigSchema.shape plus titleField, which getTreeConfig:117 reads only as labelField's last fallback), so it cannot drift silently.
  2. filter and sort are the family's one orthography from birth — ViewFilterRule[] (ui#6206-B reaching the family: finding(spec): four object-* filter doors in ComponentPropsMap declare z.unknown() — no orthography at all — so they still accept the MongoDB-style record ui#6206-B retired, silently; the card that closed the last FilterConditionSchema door assumed they declared the array #15449, batch Refactor: Separate authentication configuration from identity data models #55 option A) and SortItem[] (objectui#8221, batch 🔗 Broken links detected in documentation #77 option B). A new door on a family-wide ruling has no "measured before the ruling" arm. The whole-map census pins in component.test.ts already demanded it: every filter door must refuse the record form, and only record:related_list may take a string sort.
  3. Config-block VALUES follow the read point. gantt takes GanttConfigSchema because ObjectGantt.tsx:501 literally validates the authored block against that schema, imported from @objectstack/spec/ui. tree takes TreeConfigSchema, which spec: GanttConfigSchema is strictObject(...).passthrough(), so a mistyped gantt key is silently accepted — the only one of the three view config schemas that does not refuse it #15469 shut against unknown keys on this very measurement, re-measured unchanged here. map stays z.unknown() — and that is measured, not conservative-by-default: the spec's own ListMapConfigSchema is strict and declares no style, the key getMapConfig:365 reads at schema.map?.style, so pointing this door at it would refuse a value the renderer honours today. The pin records the reason so the later value ratchet has one.

Measured file face

13 files, all additive to a published surface; nothing retired, nothing narrowed.

.changeset/18305-component-props-map-map-gantt-tree.md      new
packages/spec/src/ui/component.zod.ts                       +3 schemas, +3 rows, +3 guidance sets
packages/spec/src/ui/component.test.ts                      +1 describe, 4 door lists extended
packages/spec/src/ui/filter-rule-array-guidance.test.ts     +3 doors (seven -> ten)
packages/spec/src/ui/component-type-vocabulary.test.ts      +1 pin
packages/spec/authorable-surface/ui.json                    +32 rows   (generated)
packages/spec/json-schema.manifest/ui.json                  +3 entries (generated)
packages/spec/api-surface/ui.json                           +9 exports (generated)
packages/spec/export-origins/ui.json                        generated
packages/spec/declaration-map/ui.json                       generated
content/docs/references/ui/component.mdx                    generated
content/docs/references/index.mdx                           generated
docs/audits/2026-07-unknown-key-strictness-ledger.counts.md generated (444 -> 447 sites)

No corpus moves: a whole-tree grep for an authored object-map / object-gantt / object-tree page node finds zero outside sdui.manifest.json (a registry dump, not authored metadata), against a positive control that finds object-grid authored twice in examples/app-showcase. PageComponentSchema.type already accepted all three through its open string arm and still does; what changes is that an authored props bag on one of them is judged instead of skipped.

Verification

All readings below are at e7967ce543 (the branch tip) unless a row says otherwise, exit codes captured by redirect-then-$?, never through a pipe. The spec suite, typecheck and the generated-artifact checks were re-run after the patch round below.

run verdict
pnpm --filter @objectstack/spec build exit 0
pnpm --filter @objectstack/spec test exit 0 — 482 files, 13727 tests passed
pnpm --filter @objectstack/spec typecheck exit 0 (tsc --noEmit + check:scripts-typecheck + check:test-typecheck)
pnpm --filter @objectstack/lint test exit 0 — 103 files, 3821 passed, 5 skipped (the ComponentPropsMap consumer; its closure built first)
pnpm --filter @objectstack/lint typecheck exit 0
pnpm --filter @objectstack/spec check:generated exit 0 after --fix regenerated the 5 it proved stale
MANIFEST=… check:react-declaration-parity --baseline … --strict exit 0 — object-map 2 both / 7 spec-only / 0 registry-only, object-gantt 2 both / 14 spec-only / 0 registry-only; baseline ratchet: no new divergence
pnpm exec eslint . --no-inline-config --format json exit 0 — 6786 files, 0 errors, 0 warnings, repo-wide, no narrowing
47 further derived gates (scripts/pm/dispatch-gates.mjs --commands) all exit 0 — see the acceptance notes for the one that did not

packages/spec has no workspace dependencies, so the dependency-closure build is an empty run by construction.

CI on this head is not enumerated here and is not claimed green — the verdicts above are local runs only.

Patch round — the at-tier review's two text findings, and the citations

The contract review returned PASS; these are the three things its scope did not settle, all of them text or citation, with no key, no value shape and no row membership moved. Verified after the round rather than asserted: the three key sets read back identical, ComponentPropsMap still carries 48 rows, object-tree still accepts the ViewData object arm and still refuses the bare array, the gantt block is still strict, object-chart is still absent.

  1. object-gantt's describes said "chart". Every sibling names its own noun, and object-chart is the row this section deliberately leaves absent, so "chart" pointed an author at the one block with no row. Three describes now name gantt (objectName, label, readOnly). Proof on the shipping artifact — content/docs/references/ui/component.mdx, regenerated with the repo's own gen:docs, never by hand: Object this chart binds to → 0; Object this gantt binds to → 1, at :398; control Object this NOUN binds to → 6, unchanged, so all six blocks name their own noun exactly once.
  2. object-tree's objectName gave a reason that is false for tree. It said the component-level dataSource binding can supply the object. Measured at the pin 53ded82b: plugin-tree/src/index.tsx has 0 hits for the ElementDataSource* wiring, against 7 each in plugin-map, plugin-gantt, plugin-grid and plugin-calendar — four controls, so the zero discriminates. The key stays optional; the reason is now the true one, the ladder's first two rungs (data can name the object itself, staticData needs none), plus the explicit negative. In the generated doc the false reason reads 0 hits and the new line 1, at :768.
  3. Two citations corrected, in the tables above: gantt label at :1871 (:1849 is the comment), and tree data on :359 alone.

check:docs was red on e3a256daa3 — source fixed, shipping artifact not yet regenerated — and is green on e7967ce543: check:docs exit 0, "223 generated files in sync with packages/spec"; check:generated exit 0, "All 15 generated artifacts are up to date"; check:authorable-surface exit 0, which is why gen:schema had nothing to write and gen:docs was the only stale artifact. pnpm --filter @objectstack/spec test exit 0, 482 files / 13727 tests, and typecheck exit 0, both re-run after the round. git diff --name-only origin/main...HEAD -- content/docs/releases/ → 0 paths, against a control of 2 under content/docs/references/.

On this head Type Check · source gates is completed / success — the job the red had truncated now reaches its end. The rest of CI is still running and is not claimed green.

Patch round 2 — the at-tier review's FAIL: F1 blocking, F2 and F3 in passing

Review of record: comment 5696414075 on this PR, VERDICT: FAIL at head e7967ce543. That review re-derived the accept set, the public surface, all three key sets, every value posture and the minor level and judged them RIGHT — nothing in this round moves a key, a value shape, a row membership or the level. Text, plus the one regeneration it forces.

F1 (blocking) — six describes named a function that does not exist in the code they describe. The objectName / data / staticData describes on object-gantt and object-tree said the record source is resolved by, or "read FIRST/SECOND by", getDataConfig. Re-measured at the .objectui-sha pin 53ded82b, each file fetched with git show in the same command block as its count:

file at 53ded82b getDataConfig resolveRecordSourceConfig
plugin-gantt/src/ObjectGantt.tsx 0 2 — import :64, call :593
plugin-tree/src/ObjectTree.tsx 0 3 — import :41, call :359, comment :422
plugin-map/src/ObjectMap.tsx (control) 8 3

The control discriminates, and it also settles what the map keeps: ObjectMap.tsx really does hold a local getDataConfig at :135 — the array-shorthand head, delegating to the shared ladder at :174 — so the three map describes are TRUE and keep the name. Gantt and tree reach the shared three-rung ladder directly: resolveRecordSourceConfig in @object-ui/core utils/record-source.ts:146, rungs at :151 (data, returned verbatim), :155 (staticData, wrapped as { provider: 'value', items }) and :162 (objectName, folded to { provider: 'object', object }). So the ladder ORDER those six sentences describe was already true and only the name was wrong; the six now name resolveRecordSourceConfig. Neither renderer has any reader above its ladder call, so "read FIRST" is literal for both — unlike the map, whose array head runs first, which is the second reason its describes keep the wrapper's name.

Both sides counted, because this trap already bit this PR once. An earlier round fixed a false describe in the source alone and check:docs caught the generated doc still carrying the old text. After gen:docs, at c431c22e05:

getDataConfig resolveRecordSourceConfig
packages/spec/src/ui/component.zod.ts 3 — map only, :3226 :3242 :3244 11
content/docs/references/ui/component.mdx 3 — map rows only, :638-640 6 — gantt :398-400, tree :768-770

Both sides read 9 and 0 before the round. The generated diff is exactly six table rows; no map row moved.

F2 — component.zod.ts:3545, the tree data field JSDoc, cited two read points where the schema header at :3487 already rules that :496 is not a second site for the object arm. It cites :359 alone now. Source comment; no artifact follows it.

F3 — two citations off by one line, both re-read at the pin: getGanttConfig's safeParse is ObjectGantt.tsx:500 (:501 is the if (!result.success) beneath it), and the tree's "Not authorable" marker is ObjectTree.tsx:482, not :477.

Verification for this round

  • pnpm --filter @objectstack/spec build, then check:generated — exit 0, all 15 artifacts up to date including check:docs, with content/docs/references/ui/component.mdx actually regenerated by gen:docs rather than hand-edited. Before the regeneration the same gate named that one artifact, and only it, stale.
  • pnpm --filter @objectstack/spec test — exit 0, 482 files / 13727 tests. pnpm --filter @objectstack/spec typecheck — exit 0.
  • check:objectui-pin-citations — exit 0, 26 asserting pin citations match 53ded82bf. It verifies the sha, not the line numbers, and reports 0 anchor content assertions here: F3 was measured by hand at the pin, not caught by this gate.
  • 34 of the 109 gate families dispatch-gates derives for this change set were run locally, every one exit 0; the remaining 75 are the repo-wide farm CI owns. A declared narrowing, not a silent skip.
  • pnpm lint narrowed to the diff, with the three readings that make the narrowing a measurement: the population is eslint's own **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} and eslint itself answers the regenerated .mdx with "File ignored because no matching configuration was supplied", so the linted population of this diff is one file; --format json reports 1 file, 0 errors, 0 warnings; and no type-aware linting is configured anywhere in eslint.config.mjs (no projectService, no parserOptions.project), so a comment-and-describe edit in one file cannot move any untouched file's verdict.

Acceptance notes

Docs Drift Check, re-verified rather than waved through

The drift check flagged 6 hand-written pages. Each was re-read; none needs an edit, and the readings are here rather than implied by their absence from the diff.

  • content/docs/protocol/objectui/layout-dsl.mdx does not ride this PR — measured, not assumed. It is flagged via the symbol ComponentPropsMap, which it names exactly once, at :721, and as a MECHANISM: "For the platform's own types the authoring rules dispatch ComponentPropsMap and reject a misspelled prop; a custom.* type has no entry there." That sentence is still exactly true after this change — three more types now have entries, and it enumerates none. The page carries no register of the object-block family at all: no table, no list, and its only numeric completeness claim is about breakpoints (:579). The discriminating control: object-calendar and object-form each get 0 hits on that page, and both have carried ComponentPropsMap rows since A typo'd key inside a page component's properties is accepted in silence — the props bag is a passthrough record and the SDUI props gate has no schema to dispatch for object-* blocks #7751 landed on 2026-08-12 — so the page already omits 2 of the 6 pre-existing rows, against a positive control of object-metric (3), object-master-detail-form (3), object-kanban (2), object-grid (1). A page that has never tracked the row set is not a list this change makes incomplete; its ### Metric Widget / ### Master-Detail Forms / ### Kanban Board sections are chosen worked examples. Writing three new hand-written renderer sections would be new documentation of three renderers, which is a card, not a rider on "add three rows".
  • The other five pages are literal-name collisions, not semantic links. content/docs/ui/views.mdx, concepts/architecture.mdx, getting-started/common-patterns.mdx, protocol/kernel/i18n-standard.mdx and ui/field-grouping-and-order.mdx each get 0 hits for ComponentPropsMap and 0 for object-map / object-gantt / object-tree. They are reached only through string literals that this PR's new OBJECT_GANTT_FLAT_CONFIG_GUIDANCE key array happens to contain — groupByField, timeZone, colorField, startDateField and friends. views.mdx documents the LIST-VIEW gantt block (type: 'gantt' with gantt: { startDateField, … }, :340-352), whose schema is ListViewSchema.gantt / GanttConfigSchema in view.zod.ts — a file this diff does not touch at all, so that page's vocabulary is unmoved.
  • Release-owned pages: untouched, with a control. The drift check names 7 pages under content/docs/releases/; they are read-only. git diff --name-only origin/main...HEAD -- content/docs/releases/ returns 0 paths, against a control of 2 paths under content/docs/references/ in the same diff, so the zero is a reading and not an empty filter.

component-reference-rail.test.ts needs no change — the reading, not the omission

The card names it among "the vocabulary tests that pin the row set". Measured on this branch: the file is 164 lines, mentions ComponentPropsMap 4 times, and every one of them is the single subscript ['record:reference_rail'] or prose about that one row (:3, :21, :33, :35). Object.keys(ComponentPropsMap) → 0 hits; any object- type literal → 0 hits. Control, the same two probes on component-type-vocabulary.test.ts, which genuinely does pin the row set: 6 and 8 ⇒ the zeros discriminate. All twelve of its describe/it subjects are record:reference_rail behaviour. It reads no property of the row SET, so nothing in it can move when three object-* rows land — and it passed unchanged inside the 482 green spec files above.

The row-set pins that DO move with the rows are component.test.ts (six ruled blocks to nine, plus the filter / sort / plural-filters / optional-objectName door lists, plus a new describe) and component-type-vocabulary.test.ts. The reference rail that regenerates is the docs one, content/docs/references/ui/component.mdx.

Out of scope for this PR, noted rather than fixed

Each with the measurement that found it:

  • ListMapConfigSchema declares no map style at all. The spec's list-view map block is strict and has neither style nor mapStyle, while ObjectMap.tsx:365 reads schema.map?.style and objectui's ObjectMapConfigSchema declares style. ListMapConfigSchema.safeParse({ style: '…' }) is refused — pinned as a negative in this PR's own tests, because it is the reason object-map.map stays z.unknown(). An author cannot declare a map style through the spec's list-view face today. Carrier: this PR's map value ratchet, whenever it is taken.
  • object-tree is absent from the tracked sdui.manifest.json. check:react-declaration-parity prints object-tree: NO component in the manifest — not registered or not public, although plugin-tree/src/index.tsx registers it under both object-tree and tree. The baseline ratchet is unaffected (a block with no baseline row cannot regress), so this PR is green either way, but the new row gets no parity comparison. Carrier: the next sdui:manifest regeneration.
  • pnpm check:cross-package-test-inputs is sensitive to local build state, not to this diff. It exits 1 in a worktree where packages/spec/dist/ exists and 0 where it does not — measured here as a one-variable control, with the diff held constant and the directory restored byte-identically (216 files, dist/ui/index.mjs hash unchanged). It names packages/cli/test/init-created-files-summary.e2e.test.ts, which symlinks packages/spec/dist at :115; this PR touches neither packages/cli nor turbo.json. The Lint & Repo Gates job runs it on an unbuilt tree, which is why CI is green on it.
  • objectui#9239 has moved since the card was written. The card describes it as an open, separate calendar-mirror contradiction; it is closed completed. Nothing in this PR depends on it, and object-calendar is untouched.
  • objectui's own gantt face carries the same false attribution F1 removed from ours, and it is pre-existing. At the same pin 53ded82b, packages/types/src/zod/objectql.zod.ts docblock :726 states getDataConfig is in plugin-gantt/src/ObjectGantt.tsx, and three ObjectGanttSchema describes repeat the name (:738, :739, :838) — while that file has 0 occurrences of it (control: ObjectMap.tsx 8, and the map's own mirror describes at :641-643 are correspondingly TRUE). Separately, core/src/utils/record-source.ts quotes our describes as the ruled contract at :28, :92, :95 and :97 and attributes the text to the map and gantt zod twins, so after this PR that quotation matches the map twin and no longer matches the gantt one. Both sites are objectui's, in objectui's repo; nothing here can fix them and the pin does not move. Handed to the seat to file against objectui rather than fixed in passing.

Not touched, deliberately: the object-chart deliberate-absence note in component.zod.ts, pinned as still absent in the new tests.

objectui#8348 carries pm:blocked on this card; its remaining slice judges map / gantt / tree against these rows once this lands. Nothing here changes any objectui file.

Authored by the domain:spec execution seat, session session_01KB5PFtxuy1x3dcR5gxudx6.


Generated by Claude Code

…psMap rows, derived from the renderers' read points

WIP: the three rows plus their flat-config guidance sets. Key sets measured
from objectui's plugin-map / plugin-gantt / plugin-tree at the .objectui-sha
pin 53ded82b, per the #7751 derivation method.

Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6
Co-authored-by: Claude <noreply@anthropic.com>
…rescriptions and door lists

Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6
Co-authored-by: Claude <noreply@anthropic.com>
… docs and strictness ledger; add the changeset

Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 16, 2026
@github-actions

github-actions Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 50 documentable anchor(s). ⚠️ 5 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/authorable-surface/ui.json, packages/spec/declaration-map/ui.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/architecture.mdx (via groupByField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE))
  • content/docs/getting-started/common-patterns.mdx (via groupByField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE))
  • content/docs/protocol/kernel/i18n-standard.mdx (via timeZone (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE))
  • content/docs/protocol/objectui/layout-dsl.mdx (via ComponentPropsMap (symbol, a top-level const object))
  • content/docs/ui/field-grouping-and-order.mdx (via groupByField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE))
  • content/docs/ui/views.mdx (via colorField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE), dependenciesField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE), endDateField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE; a string literal in examples), groupByField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE), progressField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE), startDateField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE; a string literal in examples), titleField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE; a string literal in OBJECT_MAP_FLAT_CONFIG_GUIDANCE; a string literal in OBJECT_TREE_FLAT_CONFIG_GUIDANCE; a string literal in examples))

⛔ 7 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via borderColorField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE), summaryExtent (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE))
  • content/docs/releases/v16.mdx (via dependencyTypes (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE), groupByField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE))
  • content/docs/releases/v17/17-0.mdx (via descriptionField (literal, a string literal in OBJECT_MAP_FLAT_CONFIG_GUIDANCE))
  • content/docs/releases/v17/17-1.mdx (via ComponentPropsMap (symbol, a top-level const object), descriptionField (literal, a string literal in OBJECT_MAP_FLAT_CONFIG_GUIDANCE), latitudeField (literal, a string literal in OBJECT_MAP_FLAT_CONFIG_GUIDANCE; a string literal in examples), locationField (literal, a string literal in OBJECT_MAP_FLAT_CONFIG_GUIDANCE), longitudeField (literal, a string literal in OBJECT_MAP_FLAT_CONFIG_GUIDANCE), titleField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE; a string literal in OBJECT_MAP_FLAT_CONFIG_GUIDANCE; a string literal in OBJECT_TREE_FLAT_CONFIG_GUIDANCE; a string literal in examples), viewMode (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE; a string literal in examples))
  • content/docs/releases/v17/17-3.mdx (via ComponentPropsMap (symbol, a top-level const object))
  • content/docs/releases/v17/17-4.mdx (via ComponentPropsMap (symbol, a top-level const object))
  • content/docs/releases/v9.mdx (via groupByField (literal, a string literal in OBJECT_GANTT_FLAT_CONFIG_GUIDANCE))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 5 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/authorable-surface/ui.json, packages/spec/declaration-map/ui.json, …) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b6fe311a9ab7bebdfb22631030ac19ebeb87c6a5 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from d20154227410f6a7b1d37f07ae61e0cce799fa02 — the merge of head c431c22e058f0f74be179cd2388911634d64c67f into base b6fe311a9ab7bebdfb22631030ac19ebeb87c6a5, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d20154227410f6a7b1d37f07ae61e0cce799fa02 && git checkout d20154227410f6a7b1d37f07ae61e0cce799fa02
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b6fe311a9ab7bebdfb22631030ac19ebeb87c6a5 c431c22e058f0f74be179cd2388911634d64c67f && git checkout -B drift-repro b6fe311a9ab7bebdfb22631030ac19ebeb87c6a5 && git merge --no-ff c431c22e058f0f74be179cd2388911634d64c67f

node scripts/docs-audit/affected-docs.mjs --json b6fe311a9ab7bebdfb22631030ac19ebeb87c6a5

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs b6fe311a9ab7bebdfb22631030ac19ebeb87c6a5 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e235bf4b70cb1e91c53721b9951baf13a071a2e2

① Derived judgments

The method was tested against the renderers themselves at the .objectui-sha pin 53ded82b (read with git show 53ded82b:… from the objectui checkout), not against the PR's own table. Every schema.*, (schema as any).* and rest.* read in plugin-map/src/ObjectMap.tsx (1207 lines), plugin-gantt/src/ObjectGantt.tsx (2011), plugin-tree/src/ObjectTree.tsx (744), the three registry index.tsx shells and core/src/utils/record-source.ts was enumerated and reconciled with the declared key sets.

  • Declared keys with no read point: none. object-map 9 of 9 (data :169 then resolveRecordSourceConfig :174, filter :742, sort :743, map :370, mapStyle :365, navigation :889, enableClustering :905, objectName :793 and :890 plus rung 3, staticData rung 2 at record-source.ts:155). object-gantt 17 of 17 (gantt :498-505, the ladder at :593, filter :738, sort :739, skipWeekends :1205, holidays :1206, persistLayout :1357, viewName :1359, navigation :1487, objectName :1359/:1491/:1873, markers :1826, criticalPath :1829, showBaselines :1832, readOnly :1833 and :1916, mobileReadOnly :1834, label :1871). object-tree 6 of 6 (the ladder at :359, filter :474, tree :108, navigation :591, objectName :534/:570/:605, staticData rung 2).
  • Read points with no declared key, each accounted for. The flat config spellings (map 7 at :382-396, gantt 30 at :513-543, tree 5 at :109-119) are read but carried as wrong-layer guidance sets under the objectui#5018 ruling (2026-08-17, 「同意」: the map block is the author face and the flat spelling is an internal form that stays out of the declared schema), inherited by objectui#6469 for gantt. I re-derived the three guidance key lists from the renderer reads: 7 of 7, 30 of 30 including the legacy dependencyField, 5 of 5 including titleField (read only as labelField's last fallback at :117). style at ObjectMap :283 is read only to warn (objectui#5017); filter.map at :216-221 and filter.tree at ObjectTree :108 are legacy stashes read to warn or as a fallback; rest.data (gantt :686 and :794, tree :496) is the React host prop, and the gantt shell forwards no host props at all.
  • Mirror and calendar traces: none. The rows differ from @object-ui/types in key set (ObjectMapSchema also declares the flat locationField and titleField; ObjectTreeSchema at this pin declares only a REQUIRED objectName plus parentField, labelField, fields, defaultExpandedDepth, so the PR's claim that the tree mirror lacks data, staticData, filter and navigation is verified) and in value shapes (filter/sort are the ruled array orthographies; map, navigation and markers are z.unknown() where the mirror binds objectui schemas). Shared describe boilerplate with object-grid and object-calendar is family idiom; no key set matches an existing row, and object-calendar's array-arm data is not reproduced.
  • Claim 1, tree data: verified, the card's conditional is FALSE. ObjectTree.tsx:359 calls resolveRecordSourceConfig(schema), whose rung 1 (record-source.ts:151-153) returns schema.data verbatim, and the tree consumes that config: dataProvider, dataObjectName and dataItems at :438-445, the object fetch at :461 and the value branch at :506. The ViewData row is measured, not invented for symmetry. One correction to how the table cites it is in ③.
  • Claim 2, map: z.unknown(): honest, not a dodge. ListMapConfigSchema (view.zod.ts:1631) is strict with exactly seven field keys and no style; the renderer reads schema.map?.style at :365 and config.style at :378, so binding the door to it would refuse an honoured value. The A typo'd key inside a page component's properties is accepted in silence — the props bag is a passthrough record and the SDUI props gate has no schema to dispatch for object-* blocks #7751 section header already sets this value posture (z.unknown() where the value contract still lives in objectui; object-calendar.calendar is the precedent), the key is what the card buys, and the reason is pinned in the new tests with ListMapConfigSchema.safeParse({ style }) as the negative. The gap it exposes belongs to the list-view face and is correctly filed as out of scope.
  • Generated artifacts match the source. authorable-surface +32 = 9 + 17 + 6 rows exactly; api-surface and export-origins +9 = three symbols per row; declaration-map +6 follows the existing two-entries-per-row pattern (calendar and grid carry the same pair); json-schema.manifest +3; strictness ledger 444 to 447 = the three new strictObject sites; component.mdx gains three sections with nested shapes only where a spec schema is bound (.gantt, .tree, none for map); index.mdx 153 to 156 and 1522 to 1525. packages/spec/json-schema/ is gitignored, so no per-schema JSON is owed to the diff, and the liveness ledger does not key on ComponentPropsMap rows (0 hits for ObjectCalendarProps in liveness/ui.json), so nothing there is stale. The parity gate's "14 spec-only" for gantt reconciles with 17 keys because label is a node-level key the gate reports separately.
  • object-chart note untouched. The diff carries no object-chart line; the note sits at head :2416 and :3689 (base :2416 and :3295, a pure line shift), and the new tests pin the absence.
  • layout-dsl.mdx does not ride, agreed. Re-counted at head: object-calendar 0 and object-form 0 (both rows since A typo'd key inside a page component's properties is accepted in silence — the props bag is a passthrough record and the SDUI props gate has no schema to dispatch for object-* blocks #7751), against object-metric 3, object-master-detail-form 3, object-kanban 2, object-grid 1; ComponentPropsMap 1, the :719-723 callout, a mechanism sentence that stays true. A page that never enumerated the row set is not made incomplete by three more rows.
  • Scope. The merge-base is 8b81ab60d7 and the three-dot diff is exactly the 13 files the PR lists; .objectui-sha is 53ded82b at both base and head; all 46 check runs on this head are success or skipped, zero failures.

② Semver level

minor on @objectstack/spec is the right level. Three new rows and nine new exports (ObjectMapPropsSchema, ObjectMapProps, ObjectMapPropsParsed and the gantt and tree twins) widen a published surface; the PageComponentSchema parse is unchanged (all three types already passed the open string arm), the props gate that dispatches these rows is @objectstack/lint's warning layer (#5068, untouched), and nothing is retired or narrowed. Precedent agrees: the #7751 six-row entry (8f20201) shipped as a Minor Change. Nothing else in the diff widens or narrows beyond the three rows: the only source change outside the three schemas, three guidance sets and three map rows is importing GanttConfigSchema and TreeConfigSchema, both already exported from view.zod. Clause-②: yes is correct and the changeset carries it.

③ Boundary flags

  1. Tree data at :496 is an ARRAY read, not a ViewData read. const passed = (rest as any).data ?? (schema as any).data; if (Array.isArray(passed)) … honours the bare-array shorthand the row refuses, the same shorthand the PR classifies for map as measured and deliberately NOT declared. The ViewData arm rests on :359 plus the consumption at :438-445, :461 and :506, which is sufficient on its own. The table, the schema header and the test comment should say so rather than cite :496 as support for the object arm, and objectui#8348's remaining slice inherits a concrete read to retire under 「以协议为准」: (schema as any).data at :496.
  2. Tree objectName describe gives a reason that is false for this block. "Optional because the component-level dataSource binding can supply the object instead" holds for map and gantt, whose shells wrap ElementDataSourceGate; plugin-tree/src/index.tsx at the pin is a plain wrapper with no gate, so the spec's PageComponentSchema.dataSource binding does not reach the tree. Optionality itself is right (rungs 1 and 2 supply records without an object name), and the published description should give that reason. One-line describe fix; it does not change the accept set.
  3. Citation slip. gantt label is cited at :1849 in the PR table, the schema header and the test comment; :1849 is a comment line, and the read is resolveInlineI18nLabel(schema.label, displayLocale) at :1871. Key and shape are right.

Implemented-by: claude/issue-18305-component-props-map-map-gantt-tree
Reviewed-by: session_01KB5PFtxuy1x3dcR5gxudx6

VERDICT: PASS


Generated by Claude Code

…tree states the real reason its objectName is optional; two citations corrected

Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Contract review

126/126 at-tier stamps (control: an os-dev round in this same session reads 209/209 claude-opus-5)
Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e7967ce543b038804ae6eff70a309f962ebb80b1

① Derived judgments

Method: every read point re-read from objectui at the pin 53ded82b (the .objectui-sha at both head and origin/main) with git show 53ded82b:PATH; every spec reading from git show e7967ce…:PATH or a detached worktree at that sha; the dev's report and the earlier review comment on the PR were treated as claims and re-derived, not adopted.

Accept-set and public-surface changes, one by one:

  1. Three new ComponentPropsMap rows, 48 rows total, Object.keys lists them, object-chart still undefined (probe; note intact at :2416/:3719). RIGHT.
  2. object-map key set of 9 — each key has a read point (data :169/:174, staticData and objectName via the ladder :155/:162 plus :793/:890, filter :742, sort :743, map :370, mapStyle :365, navigation :889, enableClustering :905). Full enumeration of schema.* reads (17 distinct) = 9 declared + 7 flat + node keys type/style; nothing undeclared. RIGHT.
  3. object-gantt key set of 17 — every key cited lands (gantt :498-505, ladder :593, filter :738, sort :739, skipWeekends :1205, holidays :1206, persistLayout :1357, viewName :1359, navigation :1487, objectName :1359/:1491/:1873, markers :1826, criticalPath :1829, showBaselines :1832, readOnly :1833/:1916, mobileReadOnly :1834, label :1871). Enumeration 47 distinct = 17 declared + 30 flat + type; title 0 reads. gantt: GanttConfigSchema is right — the renderer validates the block against that very schema (:500) and all 19 ganttConfig.* reads sit inside its 29 keys. RIGHT.
  4. object-tree key set of 6 with data IN and sort OUT — :359 is rung 1 returning schema.data verbatim, consumed at :438-445/:461/:506; the fetch :473-485 issues $filter $top $expand, $orderby 0 hits (control $filter 1); :496 is the bare-array read gated by Array.isArray at :497, so the PR's corrected reading is TRUE; the mirror ObjectTreeSchema declares only a required objectName plus four tree keys, so the mirror-is-wrong claim is TRUE. The card's conditional ("if the renderer's authored surface has no data, declare none") is falsified by measurement. RIGHT.
  5. Value postures: data = ViewDataSchema (object arm accepted, bare array refused) RIGHT; filter = ViewFilterRule[] and sort = SortItem[] RIGHT (map :742-743 and gantt :738-739 hand them to the same sinks the grid uses; string sort refused); map = z.unknown() RIGHT and measured, not lazy (ListMapConfigSchema 7 keys, no style; ObjectMapConfigSchema 8 keys incl. style; renderer reads map?.style at :365); tree = TreeConfigSchema RIGHT (4 keys = the 4 nested reads in getTreeConfig); navigation = z.unknown() follows the object-grid precedent at :2601; label = I18nLabelSchema (:1871 resolves an inline locale map); markers = z.array(z.unknown()) (GanttMarker { date, label?, color? }). RIGHT.
  6. Flat-config guidance sets: map 7 = ObjectMap :382-396 = ListMapConfigSchema.shape; gantt 30 = :513-543 = GanttConfigSchema 29 + dependencyField; tree 5 = :109-119 = TreeConfigSchema 4 + titleField (:117). Probe shows the block-naming prescription in each refusal. RIGHT.
  7. Tree objectName describe (no dataSource gate): plugin-tree shell 0 ElementDataSource lines vs 5 each in the map, gantt, grid and calendar shells. TRUE.
  8. Generated artifacts: check:generated exit 0 in my worktree at head — all 15 up to date; authorable-surface +32 = 9+17+6, api-surface and export-origins +9, declaration-map +6, json-schema manifest +3, ledger 444 to 447, component.mdx and index.mdx regenerate identically. RIGHT.
  9. Lint type gate is untouched for these types (validate-component-types.ts :96 skips any type without a reserved colon namespace); the only behavioural change is the props gate now judging an authored props bag on these nodes, which is what the PR states. Corpus: 0 authored nodes of the three types outside spec/manifest/docs vs 7 for object-grid. RIGHT.
  10. Patch-round assertions on the shipped doc: Object this chart binds to 0, Object this gantt binds to 1, all-noun control 6, tree registers no such gate 1, the old tree reason 0. TRUE.

FALSE claims — text only; no key, value shape, row membership or level moves:

  • F1. The objectName, data and staticData describes on object-gantt (component.zod.ts :3379, :3387, :3389) and object-tree (:3542, :3549, :3551) say the record source is resolved by, or "read FIRST/SECOND by", getDataConfig. At the pin ObjectGantt.tsx and ObjectTree.tsx contain 0 occurrences of getDataConfig (control: ObjectMap.tsx 8, which keeps a local wrapper at :135; resolveRecordSourceConfig 2 and 3 in gantt and tree). The name was removed from both files by objectui commit ce2aaefe1 (objectui#7632, one shared ladder). The ladder ORDER is true; the function named does not exist in the code these describes are about, and the six sentences ship into the public reference at component.mdx :398-400 and :768-770 (9 getDataConfig rows at head vs 0 at base; the three map rows are true). MUST CHANGE: in those six describes name the actual read point — the shared record-source ladder resolveRecordSourceConfig (@object-ui/core record-source.ts :151/:155/:162), reached at ObjectGantt.tsx :593 and ObjectTree.tsx :359 — or use a function-agnostic spelling; then gen:docs so component.mdx follows. The map describes may keep getDataConfig.
  • F2. The tree data field JSDoc at component.zod.ts :3545 still reads "Declared from the READ POINTS (:359 and :496)", contradicting the schema header at :3487 (":496 is NOT a second site for the object arm") and the PR's own corrected position. MUST CHANGE: cite :359 alone (source comment only; no regeneration owed).
  • F3. Two off-by-N citations in source JSDoc: gantt ":501 validates" — the safeParse is at :500 (:501 is the if (!result.success)); tree "Not authorable at :477" — the marker is at :482. Fix in passing.

② Semver level

Changeset .changeset/18305-component-props-map-map-gantt-tree.md, level quoted: "@objectstack/spec": minor. Correct. Three new rows and nine new exports (ObjectMapPropsSchema/ObjectMapProps/ObjectMapPropsParsed and the gantt and tree twins) widen a published surface — @objectstack/spec is not private, sits in the fixed group, and its files[] ships dist, json-schema, api-surface and src/**/*.zod.ts. Nothing is retired or narrowed: PageComponentSchema still parses all three types through the open string arm, the lint type gate never refused them, and only the props gate's coverage grows. Precedent 8f20201f1c (#7751, six rows) shipped as minor. The F1–F3 fixes are text and leave the level unchanged.

③ Boundary flags

open_questions is empty. Every flag the dev raised, answered:

  1. "Tree data conditional is FALSE" — confirmed at the pin (:359 rung 1, consumption :438-445/:461/:506); the row correctly declares data. Answered.
  2. "component-reference-rail.test.ts pins no row set" — confirmed: 164 lines, 4 ComponentPropsMap mentions all ['record:reference_rail'], Object.keys(ComponentPropsMap) 0, 'object- literals 0 vs 6 and 4 on the vocabulary test. No edit owed. Answered.
  3. "ListMapConfigSchema declares no style" — confirmed (7 keys; safeParse({ style }) false); out of scope for this card; the z.unknown() posture for map is the right one here. File it against the list-view face. Answered.
  4. "object-tree absent from sdui.manifest.json" — confirmed (57 components, none object-tree; parity prints NO component). The row is derived from sources, so unaffected; carrier is the manifest regeneration. Answered.
  5. "check:cross-package-test-inputs is build-state sensitive" — not re-measured here; not contract-bearing for this diff (no cli or turbo.json touch); Lint & Repo Gates is green on the head. Escalate as a filed, non-blocking defect. Answered by escalation.
  6. "layout-dsl.mdx does not ride" — confirmed: object-calendar 0, object-form 0 vs object-metric 3, master-detail-form 3, kanban 2, grid 1; ComponentPropsMap once at :721 as a mechanism sentence. Answered.
  7. "GanttConfigSchema has no dependencyField alias inside the block" — confirmed by probe (refused inside the block); view.zod.ts is untouched by this diff; out of scope. Answered.
  8. "objectui#9239 closed; object-calendar data is the array arm" — confirmed (calendar data: z.array(z.unknown()) at :2983); not this card. Answered.
  9. "Declared narrowing: 60 gate families unrun" and "CI not green" — answered by measurement: 42 check runs on the head, 38 success, 4 skipped, 0 failure; plus my own worktree runs at head — build 0, check:generated 0, parity 0, test 0 (482 files, 13727 tests).
  10. Clause-②: check-clause2-carriers --pair 18403 exit 0; needs:contract-review read on both the card and the PR from the API. Answered.
  11. "Changeset publishes" — package flags and files[] read above; agree. Answered.
  12. os_dev_md_divergences — process notes, not contract; no action in this review. Noted.
  13. The earlier review's three ③ items (:496 over-claim, tree objectName reason, :1849 citation) — verified fixed at head in the header, the describe and the PR table; the one leftover is the field-level JSDoc in F2.

Implemented-by: claude/issue-18305-component-props-map-map-gantt-tree
Reviewed-by: session_01KB5PFtxuy1x3dcR5gxudx6

VERDICT: FAIL


Generated by Claude Code

…ree describes

The `objectName` / `data` / `staticData` describes on `object-gantt` and
`object-tree` named `getDataConfig` as the reader that resolves the record
source. Measured at the `.objectui-sha` pin `53ded82b`: `ObjectGantt.tsx` and
`ObjectTree.tsx` contain zero occurrences of that name (control: `ObjectMap.tsx`
has 8 — it keeps a local wrapper at :135, so the map describes stay as they are).
Both renderers reach the shared three-rung ladder `resolveRecordSourceConfig`
(`@object-ui/core` `record-source.ts` :151 / :155 / :162) — gantt at :593, tree
at :359. The ladder ORDER those sentences describe was already true; only the
function they named was wrong, and all six ship into the public reference.

Also: the tree `data` JSDoc cited two read points where the schema header
already rules that `:496` is not a second site for the object arm, and two
source citations were off by a line (`safeParse` is at :500, not :501; the
"Not authorable" marker is at :482, not :477).

Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6
Co-authored-by: Claude <noreply@anthropic.com>
…rce describes

Six rows follow the corrected describes: `object-gantt` and `object-tree` now
name `resolveRecordSourceConfig`. The three `object-map` rows keep
`getDataConfig` because that renderer really does keep a local wrapper of that
name above the shared ladder.

Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Contract review

119/119 at-tier stamps (control: an os-dev round in this same session reads 156/156 claude-opus-5)
Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c431c22e058f0f74be179cd2388911634d64c67f

① Derived judgments

Method: every read point re-read from objectui at the pin 53ded82b (the .objectui-sha at both head and origin/main) with git show; every spec reading from git show c431c22e:PATH; the generated reference re-produced by running build and check:generated in a detached worktree at origin/main-merged-with-head (exit 0, 15 of 15 artifacts current, clean status). The prior FAIL record, the dev reports and the PR body were treated as claims and re-derived.

Accept-set and public-surface changes, one by one:

  1. ComponentPropsMap gains object-map, object-gantt, object-tree (48 rows); object-chart remains undefined and its note is untouched (test pins both). RIGHT.
  2. Nine new exports (ObjectMapPropsSchema, ObjectMapProps, ObjectMapPropsParsed and the gantt and tree twins); authorable-surface +32 = 9 + 17 + 6; declaration-map, export-origins, json-schema manifest, strictness ledger 444 to 447 and both reference pages regenerate identically — measured by running the generator, not by reading the diff. RIGHT.
  3. object-map key set of 9 — every key has a read point at the pin (data :169 then ladder :174, staticData and objectName via rungs :155 and :162 plus :793 and :890, filter :742, sort :743, map :370, mapStyle :365, navigation :889, enableClustering :905); the 17 distinct schema.* reads reconcile as 9 declared + 7 flat + the node keys type and style (:283 reads style only to warn). RIGHT.
  4. object-gantt key set of 17 — every citation lands (ladder :593; filter :738; sort :739; gantt :498-505 with the safeParse at :500 against GanttConfigSchema imported from @objectstack/spec/ui at :28; navigation :1487; label :1871; skipWeekends :1205; holidays :1206; persistLayout :1357; viewName :1359; markers :1826; criticalPath :1829; showBaselines :1832; readOnly :1833 and :1916; mobileReadOnly :1834; objectName :1359, :1491, :1873). title reads 0; the row cap is the platform ceiling marked not authorable at :747. RIGHT.
  5. object-tree key set of 6 with data IN and sort OUT — :359 is the ladder call and no reader of schema.data sits above it; the fetch :473-485 issues $filter, $top, $expand and $orderby is 0 (control $filter 1); :496 is the bare-array read gated by Array.isArray at :497, correctly cited only as the negative; the mirror ObjectTreeSchema at the pin declares a REQUIRED objectName plus four tree keys and (via BaseSchemaCore) none of data, staticData, filter, navigation, so the mirror-is-wrong claim is TRUE and the card's conditional is falsified by measurement. RIGHT.
  6. Value postures: data = ViewDataSchema on all three (rung 1 returns the authored value verbatim); filter = ViewFilterRule[], sort = SortItem[] (the ruled orthographies; string sort refused); gantt = GanttConfigSchema (the renderer validates against that very schema, ListViewSchema.gantt binds the same block at view.zod.ts:2116); tree = TreeConfigSchema (view.zod.ts:2121; four keys = the four nested reads); map = z.unknown(), measured not lazy (ListMapConfigSchema 7 keys and no style; objectui ObjectMapConfigSchema 8 keys including style; renderer reads map?.style at :365); navigation = z.unknown() on the object-grid precedent; label = I18nLabelSchema (:1871 resolves an inline locale map); markers = z.array(z.unknown()) (GanttMarker { date, label?, color? }). RIGHT.
  7. Flat-config guidance sets held equal to the blocks they point at: map 7 = ListMapConfigSchema.shape; gantt 30 = GanttConfigSchema 29 + dependencyField (:513-543); tree 5 = TreeConfigSchema 4 + titleField (:117). RIGHT.
  8. Consequence for consumers: PageComponentSchema does not dispatch ComponentPropsMap at parse time (6 mentions, all prose), so the spec's parse accept set is unchanged; @objectstack/lint's validate-component-props gains three rows and emits severity: 'warning' only; validate-component-types never refused these types. Only the warning layer's coverage grows, exactly as the PR states. RIGHT.

Every sentence asserting runtime behaviour, re-read at the pin — all 32 describes on the three new schemas, on BOTH sides (32 of 32 byte-exact in content/docs/references/ui/component.mdx; counts in the evidence above):

  • The six F1 sentences now name resolveRecordSourceConfig; ObjectGantt.tsx and ObjectTree.tsx contain 0 getDataConfig and reach the ladder at :593 and :359 with no earlier reader, so "read FIRST", "read SECOND" and "THIRD" are literal. TRUE, both sides (mdx :398-400, :768-770). The three map sentences keep getDataConfig, which exists at ObjectMap.tsx:135 and delegates to the ladder at :174. TRUE (mdx :638-640).
  • map objectName and gantt objectName "the component-level dataSource binding can supply the object": both shells wrap ElementDataSourceGate (5 lines each). TRUE. tree objectName "registers no such gate": tree shell 0 against those two controls. TRUE.
  • map mapStyle (read before map.style, not the node style, overriding the public demo tiles): :365, :283, DEFAULT_MAP_STYLE = demotiles. TRUE. map enableClustering "clusters only above 100 markers": :905. TRUE. map map "taken WHOLE when present, flat spelling ignored": :370-378 plus warnOnShadowedFlatMapKeys. TRUE.
  • gantt gantt, navigation (default drawer), label (second link after exportFileName, before the object's label), skipWeekends (Saturday/Sunday, scheduling.ts:34), holidays (ISO strings folded into a Set), persistLayout (only explicit false disables; key objectName:viewName), viewName (default 'default'), markers (drawn like the Today marker), criticalPath (toggle stays in the toolbar, GanttView.tsx:535), showBaselines (on unless false), readOnly (every write path plus the drawer lock at :1916), mobileReadOnly (narrow viewports, GanttView.tsx:894). All TRUE. One simplification, not a falsehood: the persistence key's first half falls back to data.object then 'gantt' when objectName is absent (:1359).
  • tree tree ("same block ListViewSchema.tree declares", parentField auto-detects, :533-535) and navigation (row click, :606). TRUE.
  • filter "lowered to the wire $filter" and sort "lowered to $orderby" on every door that carries them. TRUE.

Citations: every :NNN in the schema headers and describes of the three new blocks was opened at the pin and lands on what it claims, including the three corrected in this round (:500 safeParse, :482 marker, :1849 named only as the comment above the chain, :496 cited only as the negative). The residual :499-501 range is a true range (cast, safeParse, failure branch).

Non-blocking imprecisions, source or docs text, none a false attribution and none moving a key, value shape, row or level — the seat may fold them into the next round that touches these lines, with gen:docs for the two that ship:

  • N1 (ships in the mdx, map row :643). map map describe writes { latitudeField, longitudeField, locationField?, … }, marking the pair required. At the pin ObjectMapConfigSchema declares all eight keys optional and the flat branch (:382) accepts locationField alone; the spec's own ListMapConfigSchema describes locationField as the alternative to the pair. Suggested spelling: latitudeField?, longitudeField? or "either locationField or the latitudeField/longitudeField pair". The door is z.unknown(), so nothing is refused by it.
  • N2 (ships in the mdx, three navigation rows). The mode list page | drawer | modal | split | popover | none omits new_window, which the spec's NavigationModeSchema declares and useNavigationOverlay.ts switches on at the pin (:298). Inherited idiom: the object-grid row lists five of the seven (no popover, no new_window), so this PR's list is closer to complete than the precedent, not worse. Suggested fix: add new_window or point at NavigationModeSchema.
  • N3 (source only, component.test.ts, never generated). Two comment citations were not carried when the zod headers were corrected: :3425 still reads label (:1849) — the read is :1871; :3521 still reads "ObjectGantt.tsx:501 validates" — the safeParse is :500. Same class as the prior review's F3.
  • N4 (gate coverage, not text truth). The three new schema headers cite the pin in the HISTORICAL spelling ("measured at the .objectui-sha pin 53ded82b", head :3187, :3339, :3468), while the sibling A typo'd key inside a page component's properties is accepted in silence — the props bag is a passthrough record and the SDUI props gate has no schema to dispatch for object-* blocks #7751 rows use the ASSERTING spelling (.objectui-sha = 53ded82bf, 7 sites). check:objectui-pin-citations therefore counts these roughly 45 line anchors among the "36 historical not checked" and will not flag them on the next pin bump. The gate permits the historical form for "where the measurement was taken", so this is within its vocabulary; recommend the asserting form (or anchor quotes) so the records stay re-checkable, since re-checkability is what they are for.

② Semver level

Changeset .changeset/18305-component-props-map-map-gantt-tree.md, level quoted: "@objectstack/spec": minor. Correct. Three new declared rows and nine new exports widen a published surface; nothing is retired or narrowed: PageComponentSchema still parses all three types through the open string arm and does not dispatch the rows at parse time, the lint type gate never refused them, and the props gate that now judges them emits warnings only. The changeset body carries the Clause-② widening declaration, which by AGENTS.md makes minor the floor; no author-writable key is removed or renamed, so nothing reaches major. Precedent (#7751, six rows) shipped as minor.

③ Boundary flags

open_questions is empty in all three dev reports. Each flag and each out_of_scope_findings entry, re-derived:

  1. Tree data conditional is FALSE — confirmed at the pin (:359 rung 1, consumption :438-445, :461, :506; no earlier reader). The row correctly declares data. Answered.
  2. component-reference-rail.test.ts pins no row set — confirmed: 164 lines, Object.keys(ComponentPropsMap) 0, 'object- literals 0, against 6 and 4 on the vocabulary test. No edit owed. Answered.
  3. ListMapConfigSchema declares neither style nor mapStyle — confirmed (7 keys). Out of scope for this card; the z.unknown() posture for map is the right one here. Escalate to the seat as a filing against the list-view face.
  4. object-tree absent from sdui.manifest.json — confirmed (0 vs 2 and 2 for the map and gantt). Not contract-bearing; the row is derived from sources. Carrier: the manifest regeneration. Answered.
  5. check:cross-package-test-inputs build-state sensitivity — not re-measured here; it touches no path of this diff and Lint & Repo Gates is green at head. Escalate as a filed, non-blocking defect.
  6. layout-dsl.mdx does not ride — confirmed to the number (ComponentPropsMap 1, object-calendar 0, object-form 0, object-metric 3, master-detail 3, kanban 2, grid 1). Answered.
  7. GanttConfigSchema has no dependencyField alias inside the block — confirmed (0 vs dependenciesField 1). view.zod.ts is untouched; out of scope. Answered.
  8. objectui#9239 closed; object-calendar.data is the array arm — confirmed (z.array(z.unknown())). Not this card. Answered.
  9. objectui's own gantt face carries the false getDataConfig attribution — confirmed at the pin: objectql.zod.ts :726, :738, :739, :838 name it for a file with 0 occurrences (the map's :580, :588, :634, :641-643 are TRUE), and record-source.ts :28, :92, :95, :97 quote it as the contract text. objectui's repo; escalate to the seat to file there. Not a defect of this PR.
  10. The gantt header's :499-501 range — verified as a true range; no change owed. Answered.
  11. The PR body carries no line-initial Clause-② (0; control object-gantt 7); the carriers are the card's claim comment (1) and the changeset (1), which is what the carrier gate reads. Consistent. Answered.
  12. "The 75 unrun derived gate families are CI's to run" — judged, not accepted: a declared narrowing is only discharged by CI actually running those families on this head. At head all 46 check runs completed with 0 failures and the six required contexts are success, on a merge ref computed after main's last commit; my own build and check:generated at head-merged-with-main exit 0. The declaration holds because CI's result discharges it, not on its own authority; the posture of declaring the narrowing in the report rather than skipping silently is the right one.
  13. "Not merging origin/main into the 18-commit-behind branch was right for a text-only round" — judged on measurement rather than on the stated reason: the branch is 19 behind at review time; git merge-tree --write-tree of origin/main and head exits 0 with no conflicts; main changed 96 files since the merge-base, 0 of them among the PR's 13 paths; main added no .zod.ts, so the reference index totals (1525) stay right; and check:generated on the merged tree is green with a clean status. So the call was safe. The dev's stated reason (that a merge would widen a text-only round into a regeneration) is not itself a justification — a stale shared artifact after merge would have been the very thing to fix — but the measured grounds above are, and the queue rebuilds against main regardless.
  14. os_dev_md_divergences — process notes, not contract; no action in this review.
  15. Observation for objectui#8348's remaining slice, not a flag on this PR: all three registry shells declare objectName with required: true while the protocol rows make it optional on the ladder's first two rungs; under the ruling the mirror follows the protocol.

Implemented-by: claude/issue-18305-component-props-map-map-gantt-tree
Reviewed-by: session_01KB5PFtxuy1x3dcR5gxudx6

VERDICT: PASS


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Provenance — at-tier PASS at the current head, carriers cleared, pre-check clear

domain:spec execution seat, session session_01KB5PFtxuy1x3dcR5gxudx6, 2026-09-16T12:01Z.

  • Review of record: comment 5697057114 on this PR — ## Contract review, VERDICT: PASS, Head-sha: c431c22e058f0f74be179cd2388911634d64c67f, this PR's current head. ⚠️ This is a fresh review of the new head, ⛔ not a re-check of the earlier FAIL's list: the reviewer was told the prior record (5696414075, FAIL at e7967ce543) is a claim about an older tree, and that its silence on a sentence is not evidence — it had missed six false attributions the first time.
  • Served tier, verified rather than asserted: the reviewer's transcript carries 119 "model" stamps, all claude-fable-5-1, 0 of anything else. Control: an os-dev round in this same session reads 156/156 claude-opus-5.
  • Independence pair: Implemented-by: claude/issue-18305-component-props-map-map-gantt-tree vs Reviewed-by: session_01KB5PFtxuy1x3dcR5gxudx6 — different identities, ⛔ not a self-review. Fed the card, its rulings and this PR only. Adopted verbatim.
  • Both carriers cleared in one stroke: needs:contract-review removed from this PR and from card spec(ui): ComponentPropsMap has no rows for object-map, object-gantt and object-tree — add them from the renderers' read points (#7751 method) so 「以协议为准」 resolves for all five ladder blocks (objectui#8348 Q1, ruled C) #18305, each by the four-step writer with a matching read-back.

Landing pre-check, all three, ⛔ every one re-read AFTER the carrier strip:

① the PASS above names this head. ② --pair 18403 → exit 0. ③ every check green as the latest run per check name — 49 runs over 35 names, 0 not-green. Governed predicate on the script-derived list: 0 of 13 paths ⇒ NOT governed.

⚠️ ③ was not taken from the pre-strip reading, and that mattered: the strip re-triggered Check Changeset, run count went 46 → 49, and it read in_progress for ~90 seconds. The 11:40Z reading (46 runs, all green) was stale the moment the label was written. This is the hazard recorded on #18446.

⚠️ What this landing does NOT settle

The review named four non-blocking imprecisions (N1-N4); two of them ship into the public reference — the object-map map describe marks the lat/lng pair required where every key is optional and locationField alone is accepted, and the navigation mode list omits new_window. They are carried on #18459, filed before this PR was enqueued so the record cannot be read as settling them. ⛔ The seat did not appeal the verdict; it adopted it verbatim and carried what the verdict left open.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 16, 2026 12:01
@os-warren
os-warren added this pull request to the merge queue Sep 16, 2026
Merged via the queue into main with commit a3d4c59 Sep 16, 2026
51 checks passed
@os-warren
os-warren deleted the claude/issue-18305-component-props-map-map-gantt-tree branch September 16, 2026 12:27
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
… key its five item-titled siblings already carry (objectstack-ai#18561)

Fixes objectstack-ai#16894

Clause-②: yes (widening)

`KanbanConfigSchema` now declares `titleField` as optional `z.string()`,
executing the director seat's decision batch objectstack-ai#87 (recorded at
objectstack-ai/objectui#8367 comment `5582071618`, confirmed by the
maintainer verbatim 「批 objectstack-ai#87 同意」). The direction was settled there; this
PR is the implementation only.

## What moved

| file | change |
|---|---|
| `packages/spec/src/ui/view.zod.ts` | `titleField:
z.string().optional()` on `KanbanConfigSchema`, plus the docblock that
records why it is optional |
| `packages/spec/src/ui/view.test.ts` | the card's four-leg probe (both
controls firing on the same call shape) and the optionality leg |
| `packages/spec/authorable-surface/ui.json` | regenerated — one added
row, `ui/KanbanConfig:titleField` |
| `content/docs/references/ui/view.mdx`,
`content/docs/references/api/protocol.mdx`,
`content/docs/references/data/object.mdx` | regenerated by `gen:docs` |
| `.changeset/16894-kanban-config-titlefield.md` | `@objectstack/spec:
minor` |

Regenerated, never hand-edited: `check:authorable-surface` wrote the
first and `pnpm --filter @objectstack/spec gen:docs` the rest.
`check:generated` reports all 15 artifacts up to date.

## Premise re-derivation — the card's sibling table does NOT match the
tree

Re-derived on this worktree at `origin/main` `79a046f8c` (the dispatch's
own derivation ran one commit behind, at `582d3e5`). `git grep -n
'titleField' -- packages/spec/src/ui/view.zod.ts` returns six carriers;
mapping each to the schema whose member list encloses it:

| line (pre-change) | owning schema | arity |
|---|---|---|
| `:1057` | `GalleryConfigSchema` (declared `:1050`) | optional |
| `:1071` | `TimelineConfigSchema` (declared `:1065`) | **required** |
| `:1407` | `CalendarConfigSchema` (declared `:1401`) | optional — the
ADR-0079 fallback docblock the ruling names |
| `:1490` | `GanttConfigSchema` (declared `:1484`) | required |
| `:1638` | `ListMapConfigSchema` (declared `:1631`) | optional |
| — | `KanbanConfigSchema` (declared `:1346`) | **absent — the defect**
|

Two corrections to the card body, neither of which disturbs the ruling:

- The card's table calls **Timeline optional**. It is **required** on
the tree.
- The card's table lists **five** schemas. There are **six** carriers:
`ListMapConfigSchema` also declares `titleField`, optional, and the card
does not mention it.

The ruling is unaffected: it prescribes the arity directly (`optional
z.string()`) and names `CalendarConfigSchema`'s docblock as the
reference, which is optional at `:1407`. So the shape landed here is the
ruled one, and the card's optional/required split was simply not
re-measured when it was written. The defect itself re-derives exactly as
filed: `KanbanConfigSchema` is a `strictObject` and refused `titleField`
by name.

## Evidence

| leg | command | verdict |
|---|---|---|
| build | `pnpm --filter @objectstack/spec build` | `VERDICT
command-exit 0` |
| ② typecheck | `pnpm --filter @objectstack/spec typecheck` |
`TYPECHECK_EXIT=0` — `check:test-typecheck: OK` |
| ② tests | `pnpm --filter @objectstack/spec test` | `TEST_EXIT=0` —
`Test Files 483 passed (483)` / `Tests 13775 passed (13775)` |
| targeted | `pnpm --filter @objectstack/spec exec vitest run
--maxWorkers=2 src/ui/view.test.ts` | `Test Files 1 passed (1)` / `Tests
376 passed (376)` |
| artifacts | `pnpm --filter @objectstack/spec check:generated` | 15 of
15 up to date |

① is empty by construction: `packages/spec` has no workspace
dependencies, so `--filter '@objectstack/spec^...' build` has an empty
closure. The package itself was built before any gate that reads
`dist/`.

③ Gate families were derived on this worktree, not inherited: `node
scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` over the real 7-path change set yields **108 families**. All
108 were run with each exit code landed to disk before being read, and
reconciled with `--ran`:

```
dispatch-gates --ran: 108 derived family(ies) accounted for — 105 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)
```

**103 green.** The five non-zero results, none of them a finding against
this diff:

- `pnpm check:dual-build-cjs-loads`, `pnpm check:lean-entry-closure`,
`pnpm check:type-check-debt` — **exit 3, PREREQUISITE NOT MET**, all
three refusing because the whole monorepo is not built on this worktree.
NOT MEASURED, declared to CI, which checks out and builds fresh.
- `node scripts/check-plugin-teardown-shape.mjs --self-test` — **exit 1,
but a prerequisite refusal in its own words**: "cannot read the positive
control at `621a487607881c66b2899b7e3477115229a156b4` … Deepen the
clone". This container's checkout is shallow. NOT MEASURED. The gate
itself (without `--self-test`) ran **green**.
- `pnpm check:cross-package-test-inputs` — **exit 1, pre-existing on the
base tree**, proven by control rather than asserted. With all seven
changed paths restored to `79a046f8c` (and the changeset removed), the
gate fails identically; the finding it prints names
`packages/cli/test/init-created-files-summary.e2e.test.ts` descending
`packages/spec/dist/`, and this diff touches neither that test, nor
`turbo.json`, nor any declaration table. Restore was proven by `git diff
HEAD` empty plus a `git hash-object` match against every HEAD blob.

**Lint, as a proven narrowing rather than a repo-wide sweep.** `pnpm
exec eslint --no-inline-config --format json` over the two changed
TypeScript files: **exit 0, 2 files, 0 errors, 0 warnings**, the file
count read from the JSON output's own length. The population it narrows
from is **6798** files — computed from ESLint's own resolved config via
`ESLint#isPathIgnored` over `git ls-files`, not estimated. The narrowing
excludes nothing, and that is a property of this repository's config
rather than a hope: `eslint.config.mjs` states it in its own comment —
this repo "runs one `eslint.config.mjs`, which never enables type-aware
linting (no `parserOptions.project`, no typed `@typescript-eslint`
rules) for ANY file, test or not." With no rule reading types across a
file boundary, a diff confined to these two files cannot move the
verdict on any of the other 6796. The repo-wide `pnpm lint` run remains
CI's.

Every reading above was taken at `6d01b4b`, this branch's final commit,
on a tree whose `git status --porcelain` is empty. `origin/main` had not
moved from the branch point (`79a046f8c`) when the PR was opened, so no
merge was owed.

## Acceptance notes

- **`packages/lint` does not field-check the key this PR declares.**
`POSITIONS.kanban` in
`packages/lint/src/validate-list-view-field-refs.ts` lists
`groupByField` (error), `summarizeField` (warning) and `columns`
(warning) — and no `titleField`. Every sibling face lists it:
`calendar.titleField` warns, `gantt.titleField` and
`timeline.titleField` error. So from this release a stale or misspelled
`kanban.titleField` names nothing and is reported by nothing, while the
identical typo one block away is caught. Read-only on this card by
dispatch, so it is reported and not edited; nothing in this PR turns it
red, and no gate reconciles that table against the spec's member lists.
Worth its own card.
- **The node-level half (objectstack-ai/objectui#7742) has no home here,
and the card's conditional resolves to "no".** `ObjectKanbanPropsSchema`
in `packages/spec/src/ui/component.zod.ts` **already declares**
`titleField: z.string().optional()`, described as "Legacy fallback for
`cardTitle` (the board reads `cardTitle || titleField`). Prefer
`cardTitle`". That is a different semantic on a different schema in a
different file — a deprecated alias for `cardTitle`, not a view-config
field binding — so one declaration cannot serve both faces and this card
is not widened. That file is also held by objectstack-ai#18305 / PR objectstack-ai#18403; it was
read, never touched.
- Noted, not filed: `KanbanConfigSchema` carries no object-level
`.describe()`, where `GalleryConfigSchema`, `TimelineConfigSchema`,
`CalendarConfigSchema` and `ListMapConfigSchema` all do, so its
nested-shape heading in the generated reference renders with no
description sentence. Cosmetic, generated-docs only, no accept set
involved — left for whichever card next edits this schema.
- The issue body was checked for sanitizer truncation: none found. It is
6732 characters through the REST read and terminates on its own `Refs:`
line.

## File surface

Two files beyond the dispatch's declared surface, both **generated by
the mandated `gen:docs` run** and neither hand-edited:
`content/docs/references/api/protocol.mdx` and
`content/docs/references/data/object.mdx`. The kanban config shape is
inlined on those two reference pages as well as on `ui/view.mdx`, so
each picks up `titleField?: string` in its `kanban` row. Omitting them
would leave `check:docs` red. Every hunk in all three files is this one
key and nothing else.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…t-map.map` back at it (objectstack-ai#18904)

Fixes objectstack-ai#18406

Clause-②: yes (widening) — the published accept set grows by exactly one
key, `ui/ListMapConfig:style`. Changeset: `@objectstack/spec` minor.

Director decision batch objectstack-ai#153 item 4, letter 1 (ruling comment 5724940537
on objectstack-ai#18406, maintainer 「其他同意」). ⛔ Not `mapStyle` (letter 2 declined: "a
second spelling the renderer would have to learn"). ⛔ No alias (letter 3
declined: "an alias is a permanent obligation for a key nobody has
written yet").

## The defect, measured

`ListMapConfigSchema` is a `strictObject` and `style` was the one member
of the map renderer's own documented config surface it omitted, so
`ListMapConfigSchema.safeParse({ style:
'https://tiles.example/style.json' })` answered `success: false` and a
map style could not be declared through the spec's list-view face at
all.

Measured at the `.objectui-sha` pin
`53ded82bf7a494f54e344e19099dbf00854b8694`:

| Reading | Where |
| :--- | :--- |
| `ObjectMapConfigSchema` declares eight keys, `style` among them |
`packages/types/src/zod/objectql.zod.ts:562`, the `style` row at `:570`
|
| `getMapConfig` reads `schema.mapStyle || schema.map?.style` |
`packages/plugin-map/src/ObjectMap.tsx:365` |
| the authored block is validated against that schema |
`packages/plugin-map/src/ObjectMap.tsx:373` |
| objectui's own docs publish `style` inside the block |
`content/docs/plugins/plugin-map.mdx:131` (that path is objectui's, and
the spec docblock's parenthetical is scoped to objectui — verified
present at the pin, so the citation is sound) |
| `FLAT_MAP_CONFIG_KEYS` is that shape MINUS `style` |
`packages/plugin-list/src/ListView.tsx:40-67` |

So the divergence was against the documented surface the spec docblock
itself cites, not merely against code.

⚠️ Line numbers were re-derived from the tree. The card and the ruling
both cite `view.zod.ts:1631` for `ListMapConfigSchema`; on this branch's
base it is at **`:1754`**.

## The change, file by file

**`packages/spec/src/ui/view.zod.ts`**
- `ListMapConfigSchema` (`:1754`) gains `style`, an optional string,
after `center`. The describe names it the map style URL, records that
the component-level `mapStyle` is read FIRST and wins when both are
present, and says it is not the inline CSS `style` record a component
node carries.
- The docblock's strictness paragraph (`:1746-1752`) gains a purely
ADDITIVE correction. `strictObject` stays and the rationale for closing
the block is untouched — the ruling does not fault strictness. What is
recorded is that the paragraph's parity claim was one key SHORT, which
key it was, and the pin it was measured at.

**`packages/spec/src/ui/component.zod.ts`**
- `map` (`:3272`) stops being `z.unknown()` and becomes
`ListMapConfigSchema.optional()`, spelled exactly as the sibling `gantt`
door is. `ListMapConfigSchema` joins the `./view.zod` import on line 4.
The registration `'object-map': ObjectMapPropsSchema` (`:3735`) is
unchanged.
- The "VALUE posture for `map`" paragraph is rewritten: it was the
record of WHY the door had to stay open, and that reason is now
discharged.
- The flat-guidance docblock gains "minus that same `style`" plus the
reason `style` is subtracted on both sides — flattened to the top level
it collides with `BaseSchema.style`, the node's inline CSS record, which
is why the renderer stopped reading a top-level `style` as a map style
at all (objectui#5017) and why the prescription routes it to `mapStyle`.
- `mapStyle` is untouched. It is the component-level spelling, read
first, and is not a member of the config block.

**`packages/spec/src/ui/component.test.ts` — PR objectstack-ai#18403's negative pins,
inverted (`:3534-3540`)**
- `ListMapConfigSchema.safeParse({ style: ... }).success` flips `false`
→ `true`.
- The door's acceptance of a `map` block carrying `style` stays `true`,
now through the spec's own schema rather than through an open value.
- ⭐ A third assertion is added, because the two above would pass just as
well against the `z.unknown()` they replaced: an authored `map` block
carrying `styl` is refused, and the refusal is asserted to land AT `map`
with `styl` echoed in the issue's `keys`.
- The flat-key-set equality (`:3507-3509`) now derives by SUBTRACTION —
the config block's shape minus `style` — rather than by hand-listing, so
a newly declared config key still lands in that assertion.

**`packages/spec/src/ui/view.test.ts`**
- The documented-surface test carries eight keys instead of seven, and
asserts the parse OUTPUT equals the input, so a declaration that
silently dropped a member could not satisfy it.
- A new pin takes the card's repro as its subject: `style` accepted
alone, the node-level object form of `style` refused, and both `styl`
and `mapStyle` still refused — `style` did not open the block.

**Generated (regenerated, never hand-edited)**
- `packages/spec/authorable-surface/ui.json` gains exactly one line,
`ui/ListMapConfig:style`. Written by `pnpm --filter @objectstack/spec
build`.
- `content/docs/references/ui/view.mdx` and
`content/docs/references/ui/component.mdx` via `gen:docs`. In the
component reference the `object-map.map` row changes from `any` to the
block's real shape and a nested-shape table for it appears — the
retraction of `z.unknown()` made visible in the published reference.
- `authorable-surface.base.json` is deliberately NOT touched; only
`gen:authorable-surface-base` writes it.

Of the 15 generated artefacts, `check:generated` proved exactly one
stale (`content/docs/references/**`) and `--fix` regenerated only that
one. After the `origin/main` merge and a full rebuild, all 15 report up
to date with a clean tree.

## Reverse verification

**Ablation (PLANT mode, `scripts/ablation-replace.mjs`).** The `style`
row was renamed to `styleAblated18406`, so the key is no longer declared
under the name the pins assert. On-disk evidence: anchor count 1 → 0,
planted marker 0 → 1, blob `1c83516951e3` → `d256ada1f791`. Restore
proved by blob equal to HEAD, `git diff HEAD` empty, and a WHOLE-TREE
`git status --porcelain` empty.

- **Source leg — RED, as predicted.** 4 of 782 tests fail, and they are
exactly the four this PR authors or modifies; 778 pass. So the new pins
are discriminating, not vacuously true.
- **Build leg — an independent third witness.** `gen:schema`'s
authorable-key ratchet also reds under the ablation and names
`ui/ListMapConfig:style` as having "disappeared from the contract" while
the committed baseline still records it. That is mechanical confirmation
that the key really landed in the tracked contract.
- **Dist leg — NOT MEASURED, and declared so.** Because the ablated
build failed at `gen:schema`, no ablated `dist/` was ever produced. The
temporary type probe consequently read the PRE-mutation artefact and
exited 0 — precisely the false green
`scripts/ablation-dist-preflight.mjs` exists to catch, and it caught it,
reporting the marker ABSENT from `dist/`. That green is discarded, not
recorded as a pass.

**Cross-package type reverse verification**, supplied separately because
the dist leg above could not supply it. A temporary probe inside
`packages/lint` resolves `@objectstack/spec/ui` through the package
`exports`, i.e. the BUILT declarations, so its verdict is a function of
build state:

- Positive leg, exit 0 — `style` is assignable on the config block, AND
a `ts-expect-error` directive on an undeclared member inside `map` is
USED, which is only true if the door really narrowed away from
`unknown`.
- Negative leg, exit 2 — both halves fail as required, and each would
have COMPILED against the pre-change declarations. The compiler names
`bogus` as not existing in the block type and prints that type inline
with its `style` member, and it reports the stale expectation-of-refusal
as an unused directive. A cached declaration file cannot produce either
error.

Both probe files were deleted; the tree is clean.

## Gates

`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived **107** command families from the real change set (8
paths, including the generated artefacts and tests). All 107 were run,
each exit code captured BEFORE any pipe and written to a file, and
`--ran FILE` reconciles to **107 derived / 107 run / 0 NOT-MEASURED / 0
UNRUN** — a DERIVED zero, since every line carries its code and none of
them is 3.

Eight families first returned a non-zero that was NOT a finding and are
green after their prerequisite was met: five reported `PREREQUISITE NOT
MET` for build state (cleared by a full `pnpm build`), one needed the
`MANIFEST` value CI supplies and was then run in CI's exact spelling,
and one — `check-plugin-teardown-shape.mjs --self-test` — refused
because its fixture commit is unreachable in a shallow clone, cleared by
a targeted `git fetch origin 621a487...` rather than an unshallow.

⛔ Not read as clearance: 13 of the roster families run only the
checker's own `--self-test`, 49 declare a tracked-file roster whose
silence is a fact about a list, 11 declare a population too wide to
place, and 6 CI jobs scheduled by these paths run steps no local command
covers. Those are CI's.

## Lint and tests

Both readings taken at `0af4dfc76`, the final commit.

- **`pnpm lint`** = `eslint . --no-inline-config`, run repo-wide rather
than narrowed: exit 0, **6853 files inspected** (counted from `--format
json`), 0 errors, 0 warnings, 75s. For the record, the config never
enables type-aware linting for any file — no `parserOptions.project` and
no typed rules — which `eslint.config.mjs:326-336` states with its own
positive control, so this diff could not move an untouched file's
verdict either way.
- **`pnpm --filter @objectstack/spec test`** — 488 files, 14128 tests,
all passing.
- **`pnpm --filter @objectstack/spec typecheck`** — green, test layer
included.
- **`check:generated`** — all 15 artefacts up to date.

Verify-lock readings, for the lane's baseline: acquisitions waited 0s /
5s / 0s / 4m05s / 1m18s / 0s, and the full `pnpm build` held the lock
4m56s.

## Acceptance notes

Observed, in scope of nothing here, and ⛔ not filed by this seat.

1. **The docblock clause "so an extra key here would be dropped there"
is measurably false at the pin**, and it is the clause immediately
beside the one this PR corrects. `ObjectMapConfigSchema` is a plain
`z.object`, not strict, so a `safeParse` of a block carrying an extra
key SUCCEEDS; `getMapConfig` uses that result only to decide whether to
emit a `console.warn`; and `ObjectMap.tsx:378` returns a spread of the
authored block, so the extra key is carried through rather than dropped.
An undeclared key inside `map` is therefore neither flagged nor dropped
by the renderer — the opposite of what the clause asserts. Left
untouched deliberately: the ruling's facts reach the `style` key and
objectui's read of it, and they do not reach this mechanism claim, so
correcting it would widen the diff past what the ruling authorises.
Passed to the seat for filing-class judgement rather than filed.

2. The retraction did **not** need to reach into PR objectstack-ai#18638's hunk
regions. Its `view.zod.ts` hunks start at old lines 53 / 2241 / 2771 /
2978 and its `component.zod.ts` hunks at 6 / 738 / 1591 / 1601; this
PR's regions are `view.zod.ts:1727-1766` and `component.zod.ts:4,
3163-3175, 3210-3218, 3272-3273`. The `origin/main` merge brought only
`content/docs/deployment/validating-metadata.mdx` and
`scripts/gen-sdui-manifest-node.mjs`, neither of which overlaps.

## 维护者速读(草稿)

### 改了什么

地图视图配置块 `ListMapConfigSchema` 新增一个可选键 `style` —— 地图底图样式的 URL。同时把组件契约里
`object-map` 的 `map` 属性重新指回这个块(上一个 PR
因为这个键缺失,被迫把它放成了"任意值")。就一个键,没有别名,没有第二种拼写。

### 为什么改

渲染器一直在读这个键,规格却一直不声明它。后果是:作者在平台的权威声明面上**根本无法声明地图样式** —— 写 `style`
被严格校验拒掉,写 `mapStyle` 规格也不认。代价已经付过一次:为了绕开这个拒收,上一个 PR
把一个组件属性的值契约整个放开成了"任意值",于是那个位置的拼写错误不再有人拦。这次两件事一起收回。

键名取 `style` 而不是 `mapStyle`,因为 Mapbox 与 MapLibre 这个行业本来就叫
`style`,也是渲染器在配置块里实际读的名字。

### 风险与代价(含回滚)

- **接受集只扩大,不缩小**:原先能通过的元数据,现在全部照旧通过。
- **一处收紧**:`object-map` 的 `map`
值从"任意值"变回真正的块契约,所以块内的拼写错误现在会被明确拒收并指名。这正是本次要恢复的保护,但它确实是一个行为变化 ——
如果线上有依赖"任意值"往 `map` 里塞非契约键的写法,会在这里被拒。仓内扫描未发现这类写法。
- **不涉及 objectui 仓**:objectui 那边教人写 `mapStyle` 的开发警告,由另一张 `domain:ui`
卡单独修,本 PR 不碰。
- **回滚**:单次 revert 即可。没有数据迁移,没有退役键,没有 ADR-0087 转换,没有墓碑。

### 席位意见

### 你要做的

本 PR 是 `Clause-②:
yes`(公开接受集扩大),按流程需要合约层评审后才可落地;评审席位与载体标签不由本席位挂摘。除评审外无需人工动作 ——
本地门禁已全绿,生成产物已按机制重生成。

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

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
… NavigationModeSchema modes (objectstack-ai#19675)

Part of objectstack-ai#18459 (item N2)

> ⚠️ **Closing keyword corrected by the seat from `Fixes` to `Part
of`.** Card objectstack-ai#18459 carries FOUR items. N1 is measured DEAD on main (PR
objectstack-ai#18904 replaced the enumerating describe with a pointer at
`ListViewSchema.map`, whose schema declares every key `.optional()`),
and **N2 is what this PR lands**. ⛔ **N3 and N4 are NOT fixed** — both
were re-measured at the live pin and deliberately left on the card, N3
with fresh numbers that supersede the card's own stale proposals. A
`Fixes` would have closed the card on merge and destroyed both. The seat
releases the card back to the queue on landing with a `Release:` naming
what landed and what remains.

Clause-②: no

## Summary

The card carried **two shipped imprecisions**. Re-measured at the LIVE
objectui pin
`87af769e9a3ee28ace099fdd653d3ebd79fe82e2` before anything was changed,
**one is dead and
one survives** — and the surviving one is wider than the card scoped it.

This PR fixes the surviving one. The falsification of the other is
recorded below rather
than acted on.

## Zone 2 — what the re-measurement found

### N1 (lat/lng pair marked required) — FALSIFIED, already fixed. No
change here.

At PR objectstack-ai#18403 the `object-map` `map` row read:

`'Map field config, the author face: { latitudeField, longitudeField,
locationField?, titleField?, descriptionField?, zoom?, center?, style?
}. Taken WHOLE when present ...'`

— the enumeration the card objected to. Commit `1aa5026e30` (PR objectstack-ai#18904,
"declare `style` on
`ListMapConfigSchema` and point `object-map.map` back at it") **deleted
that enumeration**.
On `main` today the row reads `'Map field config, the author face — the
same block
ListViewSchema.map declares ...'`, and the generated reference renders
the TYPE from
`ListMapConfigSchema` itself:

`| **map** | { latitudeField?: string; longitudeField?: string;
locationField?: string; titleField?: string; … } | optional | ...`

Every key optional, no required pair claimed. The statement the card
called false no longer
exists in the source or in the shipped reference.

Reproduce: `git log -S "Map field config, the author face: {
latitudeField" -- packages/spec/src/ui/component.zod.ts`
returns exactly two commits, `a3d4c59d69` (added, PR objectstack-ai#18403) and
`1aa5026e30` (removed, PR objectstack-ai#18904).

### N2 (navigation list omits `new_window`) — CONFIRMED, and it is four
rows, not three.

At the live pin, `object-grid`, `object-map`, `object-gantt` and
`object-tree` each hand
`schema.navigation` straight into the shared `useNavigationOverlay` hook
(`ObjectGrid.tsx:2321`, `ObjectMap.tsx:1041`, `ObjectGantt.tsx:1680`,
`ObjectTree.tsx:891`).
That hook binds its own mode union to this package's
`NavigationModeSchema` — its docblock
says so in as many words, *"the seven modes this hook switches on are
exactly the seven the
exported union publishes"*, held by a parity test on that side — and its
click router carries
a `new_window` branch that delegates to `onNavigate` and otherwise falls
through to a
`window.open`. `object-gantt` implements that action itself as well.

The four `.describe()` strings listed six of the seven (no
`new_window`), except `object-grid`
— the row the other three were copied from — which listed five (no
`popover` either). All four
doors are `z.unknown()`, so that sentence is the entire published
account of the shape, and
the reference page renders the type beside it as `any`.

### N3 (two comment citations in `component.test.ts`) — NOT fixed here,
and the card's own correction is stale.

Both citations still exist (`component.test.ts:3649` `label (:1849)`,
`:3750`
`ObjectGantt.tsx:501`). But they are HISTORICAL citations anchored to
the retired pin
`53ded82b`, and the corrections the card proposes (`:1871`, `:500`) were
measured at that
same retired pin. At the live pin the true positions are **`:503`** for
the
`GanttConfigSchema.safeParse` and **`:2181`** for the label resolution —
so applying the
card's numbers would replace one unverifiable pair with another.
`component.test.ts` is also
outside this dispatch's declared file surface. Left for the card.

### N4 (historical pin-citation spelling) — unchanged, measured.

`check:objectui-pin-citations --list` on this head: **48 historical / 29
asserting / 7
assertion** entries. **30 historical citations still name the retired
`53ded82b`**, ten of
them in `component.zod.ts`, including the three headers this card is
about
(`component.zod.ts:3561` map, `:3715` gantt, `:3906` tree). The gate
permits the historical
form by design, so nothing is violated and nothing reds — those anchors
simply are not
re-checked on a pin bump, which is exactly what the card recorded. N3's
re-measurement above
shows the drift is real and not hypothetical. Converting them is a
re-measurement job, not a
spelling change, and is left for the card.

## The change

Four `.describe()` strings, plus the reference page regenerated from
them with
`pnpm --filter @objectstack/spec check:generated --fix` (never by hand):

- `ObjectGridPropsSchema.navigation`
- `ObjectMapPropsSchema.navigation`
- `ObjectGanttPropsSchema.navigation`
- `ObjectTreePropsSchema.navigation`

Each now lists `page | drawer | modal | split | popover | new_window |
none` and names
`NavigationModeSchema` as the source of that list, so the next member
added to the enum has a
named edge into these rows instead of four independently drifting lists.

**Why `object-grid` is in scope.** The card names it as the precedent
the other three copied
and says "fixing it means fixing the family, not just these three rows".
It is the same defect
class, in the same file, in the same gate family, adds no verification
surface, and understates
by two rather than one. Correcting three while leaving the source of the
pattern intact would
leave the family in the state that produced the defect.

No accept set moves: the doors stay `z.unknown()`.

## Changeset — measured, not pattern-matched

`patch` on `@objectstack/spec`. The changed bytes ARE published bytes:

- `packages/spec/package.json` `files[]` contains `src/**/*.zod.ts`, so
  `src/ui/component.zod.ts` ships verbatim in the tarball.
- The compiled strings ship in `dist/ui/index.js` and
`dist/ui/index.mjs`.
- `npm pack --dry-run --json` in `packages/spec` lists all three of
those paths among its
2030 entries. `@objectstack/spec` is `private: false` at `17.4.0` — a
released package.
- Controls: a positive control (an untouched describe from the same
rows) is found in the same
dist files; a negative control (a string present nowhere) is found in
none.

So `skip-changeset` would have been wrong here, and no changeset would
have been wrong too.

## Verification

Head `55dd769453`. `origin/main` merged in at `f2101d0492` via
`scripts/pm/os-regen-merge.sh`
before the final measurements; all 15 spec generated artifacts
re-checked clean after it.

- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived
**102 families** from the actual changed paths. All 102 run, exit codes
captured to a file
BEFORE any pipe, reconciled with `--ran`: **102 run, 0 NOT MEASURED, 0
UNRUN**.
- The single non-zero is `pnpm --filter @objectstack/spec run
check:react-declaration-parity`
in its bare spelling, which exits 1 by design with no `MANIFEST`. Run as
CI runs it —
`MANIFEST="$PWD/sdui.manifest.json" ... --baseline
react-declaration-parity.baseline.json --strict`
— it exits **0**, with no new declaration divergence vs the accepted
baseline.
- `pnpm --filter @objectstack/spec test` — **512 files, 14953 passed, 1
todo**.
- `pnpm --filter @objectstack/spec typecheck` — exit 0 (`tsc --noEmit`,
`check:scripts-typecheck`,
  `check:test-typecheck`).
- `pnpm lint` (`eslint . --no-inline-config`, the whole repo, not
narrowed) — exit 0.
- `pnpm --filter @objectstack/spec check:generated` — all 15 artifacts
up to date.
- Every heavy run went through `scripts/pm/os-verify-lock.sh`; verdicts
read from its
  `VERDICT command-exit` line.

## Acceptance notes

Observed while measuring, deliberately NOT changed here and not filed:

1. **These four describes account for `mode` only.**
`useNavigationOverlay` also reads `size`,
`openNewTab` and `preventNavigation`, and the TYPED sibling rows
(`object-kanban`,
`object-calendar`, the list-view entry) name all four members. On a
`z.unknown()` door the
describe is the whole account, so naming one member of four understates
the block — a
separate imprecision from the one this card carries. Carrier: the next
PR touching these
rows, or a decision to point these four doors at
`NavigationConfigSchema` the way their
   typed siblings already do.
2. **A stale comment in the sibling repo.**
`plugin-tree/src/ObjectTree.tsx` at the live pin
states "measured on the installed spec: `navigation` is declared on
exactly one
`ComponentPropsMap` entry, `object-grid`". It is now declared on
several. A comment in
   another repository with no contract behind it. Carrier: objectui.
3. N3 and N4 above remain open on the card, with fresh measurements
recorded there.


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

---------

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 protocol:ui size/xl tests tooling

Projects

None yet

2 participants