Repository navigation
fix(types,plugin-map): declare what ObjectMap reads; the map block outranks the flat spelling - #5156
Conversation
…outranks the flat spelling `object-map` carried three disagreeing shapes for one component: `ObjectMapSchema` declared four keys, `ObjectMap.tsx` read about fifteen, and `ObjectMapProps.schema` was typed `ObjectGridSchema` — so every map-specific read went through `(schema as any)`. A TypeScript author could not write the `map` block the docs teach, and misspelling `latitudeField` as `latitudeFieId` was caught by nothing. - `ObjectMapSchema` gains `data`, `staticData`, `filter`, `sort`, `map`, `enableClustering`, `navigation` — each with a read site in `ObjectMap.tsx`. - The package-private `MapConfigSchema` is lifted into `@object-ui/types` as `ObjectMapConfig` / `ObjectMapConfigSchema`, so the declared face and the runtime validation are one schema. The `Object` prefix avoids `@objectstack/spec/automation`'s `MapConfigSchema` (check:spec-symbols). - `ObjectMapProps.schema` is `ObjectMapSchema`; the `as any` map reads are gone. - Precedence flip (maintainer ruling on objectui#5018, 2026-08-17): the `map` block wins over the internal flat spelling, with a dev-mode warning naming the ignored top-level keys. Safe for the producer path — neither flattener emits a `map` key, now pinned in plugin-view. - The `/zod` subpath is aliased before the bare package in the four source-alias vite configs, mirroring the root vitest config. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NYgmGheCzM6NrHZN436Cxf
|
PM review — compensating measure delivered, three questions ruled, and one correction I owe. The compensating measure: PASSED#5018 fired clause ② (published type widened, accept/reject behaviour changed) and ran at opus because fable is exhausted. The price I recorded was a key-by-key check of declared vs consumed, hunting one specific failure: a key declared but read by nothing, which re-creates this card's own defect pointing the other way. The
|
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
…breaking the console-starter alias-closure gate PR #5231's CI (job `Test (shard 3/4)`, run 32160288416) went red on `examples/console-starter/test/vite-alias-closure.test.ts`: "@object-ui/types/zod (unresolvable under src, imported by packages/plugin-list/src/ListView.tsx)" "@object-ui/types/zod (unresolvable under src, imported by packages/plugin-view/src/ObjectView.tsx)" Root cause: that test's walker resolves a bare `@object-ui/*` specifier using plain `index.<ext>` conventions and cannot find `@object-ui/types/zod`'s real barrel file (`zod/index.zod.ts` — a non-standard name), regardless of whether Vite's own alias table covers it (it already does — an explicit `@object-ui/types/zod` entry has been in `examples/console-starter/vite.config.ts` since PR #5156). `ObjectView.tsx` and `ListView.tsx` are both reachable from console-starter's own `src/`, so the runtime import made them part of the walked graph for the first time; `ObjectMap.tsx` carries the identical import today without tripping the gate only because nothing in that graph reaches `@object-ui/plugin-map`. Per instruction, `vite-alias-closure.test.ts` itself is untouched. Fix: `FLAT_MAP_CONFIG_KEYS` in `ObjectView.tsx` and `ListView.tsx` is now HAND-LISTED (no runtime `@object-ui/types/zod` import in either file) rather than derived from `ObjectMapConfigSchema.shape`. `ObjectMap.tsx` in `plugin-map` keeps the runtime-derived version — it is safely unreached by the walker, and it already imports the schema for `safeParse` regardless. Anti-drift for the two hand lists is a TEST, not just a comment, matching the established `packages/core/src/actions/__tests__/actionKeys.pin.test.ts` pattern: each of `ObjectView.mapFlatten.test.tsx` / `ListView.mapFlatten.test.tsx` now imports `ObjectMapConfigSchema` from `@object-ui/types/zod` (a TEST file, which the alias-closure walker explicitly excludes from traversal) and asserts the exported `FLAT_MAP_CONFIG_KEYS` equals the schema's keys minus `style`. A key added to or removed from the declaration still fails loudly, by name, without reintroducing the runtime edge that breaks the console-starter gate. Both `FLAT_MAP_CONFIG_KEYS` constants are now independently hand-listed (each pin-tested against the same canonical schema) rather than one literal array physically shared by both call sites — the walker constraint above rules out every within-scope way to share one runtime value without either re-tripping this gate or statically pulling `@object-ui/plugin-map`'s `maplibre-gl` / `react-map-gl` weight into `ObjectView` / `ListView` (that package's single lib entry bundles everything together, and console-starter aliases it whole to `src`). Noted explicitly in the PR body as a deviation from strict single-sourcing, with the reasoning above. Reverified locally: `pnpm exec vitest run examples/console-starter/test/vite-alias-closure.test.ts` green, plus the three package suites, at the new HEAD, rebased onto current origin/main. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
…stack-ai#5162) `content/docs/plugins/plugin-map.mdx` 的 "API Provider" 节把 `data: { provider: 'api', endpoint, method }` 教成三个可互换 provider 之一。 `ObjectMap` 在这条路上没有取数实现:命中即 `console.warn('API provider not yet implemented for ObjectMap')` + `setData([])`, `endpoint` / `method` 在整个包里没有任何读取点。照抄该节得到的是一张空地图加一条 控制台告警,页面上没有一个字解释为什么。缺 `DataSource` 时更早一步抛 `DataSource required for object/api providers`。 该节改为如实陈述当前行为,并把读者指回 Object / Value provider;配置未实现分支的 示例删除。不实现 `provider: 'api'`(能力扩张,非文档对齐),也不写任何预告。 同一份报告里的另一处(`MapConfig` 从 `@object-ui/types` 的虚构导入)由 PR objectstack-ai#5156 (落实 objectstack-ai#5018 裁定,把配置抬进已发布类型面为 `ObjectMapConfig`)接管,本提交不碰; 该页 `map: {…}` 键面同属 PR objectstack-ai#5156 的活动 diff,一行未动。 Co-authored-by: Claude <noreply@anthropic.com>
…list, not a raw spread (objectstack-ai#5231) * fix(plugin-view,plugin-list): flatten the `map` block through a whitelist, not a raw spread `ObjectView`'s and `ListView`'s `case 'map'` flatteners built the `object-map` schema with `...(options.map || {})` — spreading the CONTENTS of an untyped bag (`NamedListView.options?: Record<string, any>`) to the top level. Whatever an author wrote in the `map` block landed there unfiltered, while `ObjectMap`'s own `FlatMapConfigKeys = Omit<ObjectMapConfig, 'style'>` declares `style` OUT of this flat form — `style` is ALSO `BaseSchema.style` (inline CSS, legal on every node), so the raw spread collapsed the two namespaces onto one key. Both flatteners now pick only the keys `ObjectMapConfigSchema` declares, minus `style`. The whitelist is DERIVED at each call site from `ObjectMapConfigSchema` (`@object-ui/types/zod` — already a dependency of both packages, and read-only at runtime, not walked), rather than hand-listed, so a key added to or removed from the declaration reaches both flatteners without a second edit. `ObjectMap`'s own `FLAT_MAP_CONFIG_KEYS` (plugin-map/src/ObjectMap.tsx) now derives from the same schema for the same reason, so all three sites read one canonical set of keys. Deliberately NOT a static import of `@object-ui/plugin-map`: that package is loaded lazily (`ComponentRegistry.registerLazy('object-map', ...)`) precisely so its `maplibre-gl` / `react-map-gl` weight is paid only when a map view renders; importing from it here would defeat that for every ObjectView/ListView. Tests (both packages): a declared flat key still reaches the flattened product; `style` in the `map` block does NOT reach the top level (the specimen the card measured, confirmed red before this change); an arbitrary undeclared key does not either. Fixes objectstack-ai#5177 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE * fix(plugin-view,plugin-list): hand-list FLAT_MAP_CONFIG_KEYS to stop breaking the console-starter alias-closure gate PR objectstack-ai#5231's CI (job `Test (shard 3/4)`, run 32160288416) went red on `examples/console-starter/test/vite-alias-closure.test.ts`: "@object-ui/types/zod (unresolvable under src, imported by packages/plugin-list/src/ListView.tsx)" "@object-ui/types/zod (unresolvable under src, imported by packages/plugin-view/src/ObjectView.tsx)" Root cause: that test's walker resolves a bare `@object-ui/*` specifier using plain `index.<ext>` conventions and cannot find `@object-ui/types/zod`'s real barrel file (`zod/index.zod.ts` — a non-standard name), regardless of whether Vite's own alias table covers it (it already does — an explicit `@object-ui/types/zod` entry has been in `examples/console-starter/vite.config.ts` since PR objectstack-ai#5156). `ObjectView.tsx` and `ListView.tsx` are both reachable from console-starter's own `src/`, so the runtime import made them part of the walked graph for the first time; `ObjectMap.tsx` carries the identical import today without tripping the gate only because nothing in that graph reaches `@object-ui/plugin-map`. Per instruction, `vite-alias-closure.test.ts` itself is untouched. Fix: `FLAT_MAP_CONFIG_KEYS` in `ObjectView.tsx` and `ListView.tsx` is now HAND-LISTED (no runtime `@object-ui/types/zod` import in either file) rather than derived from `ObjectMapConfigSchema.shape`. `ObjectMap.tsx` in `plugin-map` keeps the runtime-derived version — it is safely unreached by the walker, and it already imports the schema for `safeParse` regardless. Anti-drift for the two hand lists is a TEST, not just a comment, matching the established `packages/core/src/actions/__tests__/actionKeys.pin.test.ts` pattern: each of `ObjectView.mapFlatten.test.tsx` / `ListView.mapFlatten.test.tsx` now imports `ObjectMapConfigSchema` from `@object-ui/types/zod` (a TEST file, which the alias-closure walker explicitly excludes from traversal) and asserts the exported `FLAT_MAP_CONFIG_KEYS` equals the schema's keys minus `style`. A key added to or removed from the declaration still fails loudly, by name, without reintroducing the runtime edge that breaks the console-starter gate. Both `FLAT_MAP_CONFIG_KEYS` constants are now independently hand-listed (each pin-tested against the same canonical schema) rather than one literal array physically shared by both call sites — the walker constraint above rules out every within-scope way to share one runtime value without either re-tripping this gate or statically pulling `@object-ui/plugin-map`'s `maplibre-gl` / `react-map-gl` weight into `ObjectView` / `ListView` (that package's single lib entry bundles everything together, and console-starter aliases it whole to `src`). Noted explicitly in the PR body as a deviation from strict single-sourcing, with the reasoning above. Reverified locally: `pnpm exec vitest run examples/console-starter/test/vite-alias-closure.test.ts` green, plus the three package suites, at the new HEAD, rebased onto current origin/main. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE --------- Co-authored-by: Claude <noreply@anthropic.com>
…pelling `getGanttConfig` checked the flattened spelling first and returned early, so a node carrying both spellings rendered the flat one and every key inside an authored `gantt` block was discarded with no diagnostic at all — not even the `GanttConfigSchema.safeParse` warning, which sat behind that early return. `plugin-map` had the identical two-faces shape ruled the other way (maintainer on objectui#5018, 2026-08-17, landed PR #5156): the block wins, with a dev-mode warning naming the ignored top-level keys. objectui#6469 inherits that ruling. - `getGanttConfig` checks the `gantt` block FIRST and returns it whole; the flat branch is unchanged and is consulted only when no block is present. - `warnOnShadowedFlatGanttKeys` names the shadowed flat keys once per distinct shadowing. It is raised only from the block branch, which neither flattener's output ever enters — `ObjectView` / `ListView` emit the flat keys and no `gantt` key — so it cannot fire on the hot path. - `FLAT_GANTT_CONFIG_KEYS` derives the spec half from `GanttConfigSchema.shape`; objectui's ten `GanttConfig` extensions and the legacy `dependencyField` alias are listed, with a compile-time coverage pin in the new test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q
…pelling (objectstack-ai#6509) * fix(plugin-gantt): let the `gantt` block outrank the flat top-level spelling `getGanttConfig` checked the flattened spelling first and returned early, so a node carrying both spellings rendered the flat one and every key inside an authored `gantt` block was discarded with no diagnostic at all — not even the `GanttConfigSchema.safeParse` warning, which sat behind that early return. `plugin-map` had the identical two-faces shape ruled the other way (maintainer on objectui#5018, 2026-08-17, landed PR objectstack-ai#5156): the block wins, with a dev-mode warning naming the ignored top-level keys. objectui#6469 inherits that ruling. - `getGanttConfig` checks the `gantt` block FIRST and returns it whole; the flat branch is unchanged and is consulted only when no block is present. - `warnOnShadowedFlatGanttKeys` names the shadowed flat keys once per distinct shadowing. It is raised only from the block branch, which neither flattener's output ever enters — `ObjectView` / `ListView` emit the flat keys and no `gantt` key — so it cannot fire on the hot path. - `FLAT_GANTT_CONFIG_KEYS` derives the spec half from `GanttConfigSchema.shape`; objectui's ten `GanttConfig` extensions and the legacy `dependencyField` alias are listed, with a compile-time coverage pin in the new test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q * docs(plugin-gantt,types): state the flipped precedence, and un-blind the key-coverage guard - `packages/types/src/objectql.ts` and its zod mirror documented the OLD order ("the flat branch is checked first and RETURNS EARLY", "no equivalent ruling exists for gantt"). Both now state that the block wins per objectui#6469. - `KnownGanttConfigKey` strips `GanttConfig`'s inherited index signature. A bare `keyof GanttConfig` widens to `string`, so the `satisfies` on the extension list and the coverage pin in the test constrained NOTHING while reading as guards — measured: the pin came back `string`, not `never`. Same blind instrument objectui#6051's declaration pin records. - Adds the changeset, and a pin for the shape published authoring guidance produces (GanttConfig keys hoisted beside a `gantt` block). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q * docs(plugin-gantt): rewrap the viewMode precedence paragraph Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4q --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #5018
Implements the maintainer ruling recorded on the card (2026-08-17, 「同意」): direction 1, full alignment, with the
mapblock as the author face and the flat top-level spelling as an internal form.Declared vs consumed, key by key
Derived from
packages/plugin-map/src/ObjectMap.tsxatorigin/main, not from the card's list. Every key inObjectMapSchemahas a read site; nothing plausible-looking was added.ObjectMap.tsxtype:170,:173(warn memo)objectNamegetDataConfig:115;:497;:555datagetDataConfig:104;:491ViewData)staticDatagetDataConfig:108any[])filter:161(legacy probe);:467($filter)any[])sort:468($orderby)string | SortConfig[])map:199(.style);:217ObjectMapConfig)enableClustering:570boolean)navigation:554ViewNavigationConfig)locationField:202,:204— internal flat formtitleField:207— internal flat formmapStyle:199style:199BaseSchemaConsumed but deliberately NOT declared, per the ruling: the flat
latitudeField/longitudeField/descriptionField/zoom/center(:202-213). They are the ObjectView / ListView flatten product, declared in the consumer asFlatMapConfigKeysinstead.Discrepancies with the card's key list
style/mapStyleamong the keys to reckon with.mapStylewas already declared;styleisBaseSchema's inline-CSS key and its collision is ObjectMap 把顶层schema.style当 MapLibre style URL 读 —— 与BaseSchema.style(内联 CSS)撞车,而 types 里mapStyle这个名字正是为避开它才起的 #5017. The read at:199keeps its exact prior runtime behaviour here, cast and all, with a comment pointing at ObjectMap 把顶层schema.style当 MapLibre style URL 读 —— 与BaseSchema.style(内联 CSS)撞车,而 types 里mapStyle这个名字正是为避开它才起的 #5017. Nothing about that collision is settled by this PR.type, read by the legacy-warning memo. Already declared, so no action.What changed
ObjectMapSchemagains the seven keys above (TS interface + zod mirror).ObjectMap.tsx:41-50is lifted into@object-ui/typesso the declared face and the runtime validation are one schema.ObjectMapProps.schemaisObjectMapSchema. Every(schema as any)map read is gone; the only remainingas anyin the file are onrest(props),dataConfig.itemsand aref.getMapConfig: themapblock is checked first and wins outright, with a dev-mode warn-once diagnostic naming the ignored top-level keys.content/docs/plugins/plugin-map.mdxhadimport type { ObjectGridSchema, MapConfig }in its TypeScript section — a line that did not compile against anything. Corrected, plus the declared keys added to the Schema API block andobjectNamemarked required. Themapblock teaching is unchanged.Naming:
ObjectMapConfig, notMapConfigThe ruling names the lifted schema
MapConfigSchema. That name cannot be used in@object-ui/types:@objectstack/spec/automationalready exportsMapConfigSchemafor an unrelated automation concept, andpnpm check:spec-symbolsrefuses a local declaration under a spec export's name — verbatim, on the first run:The gate stayed silent before this PR because the plugin-map declaration was module-private. Exported as
ObjectMapConfig/ObjectMapConfigSchema, which the spec does not own; the gate is green.The stop-and-report condition was NOT reached
The ruling asked for a stop if the flip is unsafe for ObjectView's producer path. It is safe, and the reason is structural rather than a judgement call: neither flattener emits a
mapkey.plugin-view/src/ObjectView.tsxcase 'map'andplugin-list/src/ListView.tsxcase 'map'both build the schema as{ ...baseProps, locationField: options.map?.locationField || 'location', ...(options.map || {}) }— the block's CONTENTS spread flat, nomapkey in the product. So the branch the flip reorders is never reached for their output, and the flip cannot change what any ObjectView / ListView map renders. That property is now pinned rather than left to be re-derived:plugin-view/src/__tests__/ObjectView.mapFlatten.test.tsx.The
ObjectMap.filterConfig.test.tsx:164pinRead before touching it, and it does not record the old precedence. It is
it('keeps the top-level latitudeField branch intact'), and its schema carries nomapblock at all — so the flat branch is the only branch there is, and the assertion is unchanged by the flip. The old top-level-wins order was unpinned; the card's reading of this pin is the one thing in it that did not survive verification.Kept and annotated rather than rewritten: a history note citing the ruling, saying what it does and does not pin, and pointing at where the precedence is now pinned in both directions. The test still guards the thing that matters most about that branch — the internal flat form has to keep rendering, or every ObjectView / ListView map goes blank.
Reverse verification
Direction predicted before each run; all four matched.
getMapConfigexpected '1' to be '40'andexpected '' to contain '[ObjectMap]'warnOnShadowedFlatMapKeyscallmap: viewOptions.mapObjectView.mapFlattenexpected true to be falseon thehasOwnProperty('map')assertionslatitudeFieIdtolatitudeFieldin the type pintscred, TS2578 unused directivesrc/ObjectMap.schemaAlignment.test.tsx(121,7): error TS2578: Unused '@ts-expect-error' directive.Each ablation was restored with
git checkoutagainst the committed branch; the tree was confirmed byte-identical (git status --porcelainempty) before the gate union ran.A measured boundary, stated because it limits the claim
The first draft of the type pins asserted that a misspelled TOP-LEVEL key is a compile error too.
tscsaid otherwise — TS2578 on both pins.BaseSchemacarries[key: string]: any, so excess-property checking is off for every component schema in the repo, andenableClusterngtype-checks. What #5018 buys is the half that matters for its symptom:ObjectMapConfigis a plain interface with no index signature, so themapBLOCK is closed andlatitudeFieIdis rejected. The pins now record the real boundary instead of the assumed one. Filed as an observation-class finding: #5155.Bounding sweep, named
Lifting a runtime schema into
@object-ui/types/zodbroke the workspace build: a vite string alias matches by PREFIX, so in the four configs that alias@object-ui/typesto source,@object-ui/types/zodresolved to thesrc/zodDIRECTORY, which has noindex.ts(the barrel isindex.zod.ts).The correct form is already pinned in the repo: the root
vitest.config.mts:250-251aliases the/zodsubpath immediately before the bare package. The same two-line pairing is added toapps/console/vite.config.ts,examples/console-starter/vite.config.ts,packages/runner/vite.config.tsandpackages/fields/vite.config.ts— mechanical, one form, closing the class rather than only the config that happened to fail first.Verification
All at
aad4609, the final commit, tree clean.pnpm --filter '@object-ui/plugin-map^...' build— exit 0 (dependency closure built before anything was judged)pnpm --filter @object-ui/plugin-map test— exit 0, 8 files / 52 tests passedpnpm --filter @object-ui/plugin-view test— exit 0, 12 files / 114 tests passedpnpm exec turbo run type-check— exit 0, 81/81 tasks (the DOWNSTREAM consumer sweep: every package depending on@object-ui/types)pnpm exec turbo run build --filter='!@object-ui/site'— exit 0, 43/43@object-ui/typeshas notestscript; its suites run from the root vitest project.npx vitest run packages/types/src/__tests__/view-navigation-config-spec-parity.test.ts— exit 0, 5 passed (that file gained a fourthnavigationidentity pin forObjectMapSchema).Gate union, derived from the actual changed paths (
node scripts/…viapnpm check:*), every gate inpackage.jsonrun ataad4609:check:control-bytescheck:spec-symbolscheck:phantom-depscheck:self-importcheck:published-distcheck:doc-typescheck:i18n-keyscheck:i18n-driftcheck:i18n-dead-keyscheck:action-forward-paritycheck:skills-pathsChangeset
.changeset/objectmap-schema-alignment-5018.md,minorfor@object-ui/typesand@object-ui/plugin-map.minorrather thanpatchbecause this adds published exports (ObjectMapConfig,ObjectMapConfigSchema) and widens a published interface — the level this repo uses for declared-surface additions.@object-ui/plugin-viewis not listed: its only change is a new test file.Deliberately not pulled in
#5017 (the
style/mapStylecollision), #5003 and #5020.Generated by Claude Code