Repository navigation
feat(spec): declare style on ListMapConfigSchema and point object-map.map back at it - #18904
Conversation
…t-map.map` back at it `ListMapConfigSchema` is a `strictObject` and `style` was the one member of the map renderer's own documented config surface it omitted, so a strict parse refused the map style URL the renderer reads and an author could not declare a map style through the spec's list-view face at all. Measured at the `.objectui-sha` pin `53ded82b`: objectui's `ObjectMapConfigSchema` (`packages/types/src/zod/objectql.zod.ts:562`) declares all eight keys, `getMapConfig` reads `schema.mapStyle || schema.map?.style` (`packages/plugin-map/src/ObjectMap.tsx:365`), and objectui's own `content/docs/plugins/plugin-map.mdx:131` documents `style` inside the block. In the same stroke the `object-map` component door stops being `z.unknown()`: that posture existed only because pointing it at a schema missing `style` would have refused a value the renderer honours. The two assertions that recorded the divergence are inverted, and the flat-key-set equality now subtracts `style` explicitly — flattened to the top level it collides with `BaseSchema.style`, the node's inline CSS record, which is why objectui's own `FLAT_MAP_CONFIG_KEYS` subtracts it too. ⛔ Not `mapStyle`, and no alias: one name, the one the renderer reads. Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 8b7f4ae2aec11968444f8524842a0b1e8b4c34aa && git checkout 8b7f4ae2aec11968444f8524842a0b1e8b4c34aa
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 84ba4a847600dbecb55fcd2e31322ada19496f58 0af4dfc76ed5ab0a1994296554e7f44d102cfe0f && git checkout -B drift-repro 84ba4a847600dbecb55fcd2e31322ada19496f58 && git merge --no-ff 0af4dfc76ed5ab0a1994296554e7f44d102cfe0f
node scripts/docs-audit/affected-docs.mjs --json 84ba4a847600dbecb55fcd2e31322ada19496f58
|
… 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>
Fixes #18406
Clause-②: yes (widening) — the published accept set grows by exactly one key,
ui/ListMapConfig:style. Changeset:@objectstack/specminor.Director decision batch #153 item 4, letter 1 (ruling comment 5724940537 on #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
ListMapConfigSchemais astrictObjectandstylewas the one member of the map renderer's own documented config surface it omitted, soListMapConfigSchema.safeParse({ style: 'https://tiles.example/style.json' })answeredsuccess: falseand a map style could not be declared through the spec's list-view face at all.Measured at the
.objectui-shapin53ded82bf7a494f54e344e19099dbf00854b8694:ObjectMapConfigSchemadeclares eight keys,styleamong thempackages/types/src/zod/objectql.zod.ts:562, thestylerow at:570getMapConfigreads `schema.mapStylepackages/plugin-map/src/ObjectMap.tsx:373styleinside the blockcontent/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_KEYSis that shape MINUSstylepackages/plugin-list/src/ListView.tsx:40-67So the divergence was against the documented surface the spec docblock itself cites, not merely against code.
view.zod.ts:1631forListMapConfigSchema; on this branch's base it is at:1754.The change, file by file
packages/spec/src/ui/view.zod.tsListMapConfigSchema(:1754) gainsstyle, an optional string, aftercenter. The describe names it the map style URL, records that the component-levelmapStyleis read FIRST and wins when both are present, and says it is not the inline CSSstylerecord a component node carries.:1746-1752) gains a purely ADDITIVE correction.strictObjectstays 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.tsmap(:3272) stops beingz.unknown()and becomesListMapConfigSchema.optional(), spelled exactly as the siblingganttdoor is.ListMapConfigSchemajoins the./view.zodimport on line 4. The registration'object-map': ObjectMapPropsSchema(:3735) is unchanged.map" paragraph is rewritten: it was the record of WHY the door had to stay open, and that reason is now discharged.style" plus the reasonstyleis subtracted on both sides — flattened to the top level it collides withBaseSchema.style, the node's inline CSS record, which is why the renderer stopped reading a top-levelstyleas a map style at all (objectui#5017) and why the prescription routes it tomapStyle.mapStyleis 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 #18403's negative pins, inverted (:3534-3540)ListMapConfigSchema.safeParse({ style: ... }).successflipsfalse→true.mapblock carryingstylestaystrue, now through the spec's own schema rather than through an open value.z.unknown()they replaced: an authoredmapblock carryingstylis refused, and the refusal is asserted to land ATmapwithstylechoed in the issue'skeys.:3507-3509) now derives by SUBTRACTION — the config block's shape minusstyle— rather than by hand-listing, so a newly declared config key still lands in that assertion.packages/spec/src/ui/view.test.tsstyleaccepted alone, the node-level object form ofstylerefused, and bothstylandmapStylestill refused —styledid not open the block.Generated (regenerated, never hand-edited)
packages/spec/authorable-surface/ui.jsongains exactly one line,ui/ListMapConfig:style. Written bypnpm --filter @objectstack/spec build.content/docs/references/ui/view.mdxandcontent/docs/references/ui/component.mdxviagen:docs. In the component reference theobject-map.maprow changes fromanyto the block's real shape and a nested-shape table for it appears — the retraction ofz.unknown()made visible in the published reference.authorable-surface.base.jsonis deliberately NOT touched; onlygen:authorable-surface-basewrites it.Of the 15 generated artefacts,
check:generatedproved exactly one stale (content/docs/references/**) and--fixregenerated only that one. After theorigin/mainmerge and a full rebuild, all 15 report up to date with a clean tree.Reverse verification
Ablation (PLANT mode,
scripts/ablation-replace.mjs). Thestylerow was renamed tostyleAblated18406, so the key is no longer declared under the name the pins assert. On-disk evidence: anchor count 1 → 0, planted marker 0 → 1, blob1c83516951e3→d256ada1f791. Restore proved by blob equal to HEAD,git diff HEADempty, and a WHOLE-TREEgit status --porcelainempty.gen:schema's authorable-key ratchet also reds under the ablation and namesui/ListMapConfig:styleas 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.gen:schema, no ablateddist/was ever produced. The temporary type probe consequently read the PRE-mutation artefact and exited 0 — precisely the false greenscripts/ablation-dist-preflight.mjsexists to catch, and it caught it, reporting the marker ABSENT fromdist/. 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/lintresolves@objectstack/spec/uithrough the packageexports, i.e. the BUILT declarations, so its verdict is a function of build state:styleis assignable on the config block, AND ats-expect-errordirective on an undeclared member insidemapis USED, which is only true if the door really narrowed away fromunknown.bogusas not existing in the block type and prints that type inline with itsstylemember, 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 --commandsderived 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 FILEreconciles 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 METfor build state (cleared by a fullpnpm build), one needed theMANIFESTvalue 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 targetedgit fetch origin 621a4876...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 — noparserOptions.projectand no typed rules — whicheslint.config.mjs:326-336states 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 buildheld the lock 4m56s.Acceptance notes
Observed, in scope of nothing here, and ⛔ not filed by this seat.
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.
ObjectMapConfigSchemais a plainz.object, not strict, so asafeParseof a block carrying an extra key SUCCEEDS;getMapConfiguses that result only to decide whether to emit aconsole.warn; andObjectMap.tsx:378returns a spread of the authored block, so the extra key is carried through rather than dropped. An undeclared key insidemapis therefore neither flagged nor dropped by the renderer — the opposite of what the clause asserts. Left untouched deliberately: the ruling's facts reach thestylekey 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.The retraction did not need to reach into PR feat(spec)!: every engine-evaluated expression slot requires a non-blank
source#18638's hunk regions. Itsview.zod.tshunks start at old lines 53 / 2241 / 2771 / 2978 and itscomponent.zod.tshunks at 6 / 738 / 1591 / 1601; this PR's regions areview.zod.ts:1727-1766andcomponent.zod.ts:4, 3163-3175, 3210-3218, 3272-3273. Theorigin/mainmerge brought onlycontent/docs/deployment/validating-metadata.mdxandscripts/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里塞非契约键的写法,会在这里被拒。仓内扫描未发现这类写法。mapStyle的开发警告,由另一张domain:ui卡单独修,本 PR 不碰。席位意见
你要做的
本 PR 是
Clause-②: yes(公开接受集扩大),按流程需要合约层评审后才可落地;评审席位与载体标签不由本席位挂摘。除评审外无需人工动作 —— 本地门禁已全绿,生成产物已按机制重生成。Generated by Claude Code