Uh oh!
There was an error while loading. Please reload this page.
feat(plugin-list): read the spec's view-level map block on list views - #5908
Merged
Conversation
`ListMapConfigSchema` (objectstack#9340) has been authorable and validated since the @objectstack/spec 17.1.0 pin — it flows into this repo's own `ListViewSchema` by reference — but nothing read it. `ListView`'s `case 'map'` forwarded only the legacy `schema.options.map` bag, so declaring the typed block changed nothing at runtime and marker titles fell back to the renderer's placeholder. The block now merges over that bag, per key, which is the precedence the sibling visualization blocks in the same file already use. Both sources go through the existing objectui#5177 whitelist and the branch still emits the flat form, keeping `getMapConfig`'s objectui#5018 rule true. The switcher capability gate had the same hole with a sharper consequence: a view binding coordinates in the spec block was filtered out of its own `appearance.allowedVisualizations` and fell back to `['grid']`. It now asks the same merged config the render seam forwards. `InterfaceListPage` forwards the referenced view's block alongside the auto-derived binding rather than replacing it, so a partial authored block keeps the derived coordinates. No defaults are added for zoom/center — objectui#5000's fit-to-records behaviour is unchanged. Fixes#5042 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EuPCi56cnGyykygi3z9w4m
…ot a control Ablation A measured it RED: it authors a view-level `map` block, so removing the forward breaks it. Calling it a CONTROL claimed the opposite and would have misread the next ablation's output. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EuPCi56cnGyykygi3z9w4m
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This was referenced Aug 23, 2026
os-zhuang
marked this pull request as ready for review
August 23, 2026 19:51
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#5042
The spec's view-level
mapblock on a list view was authorable, validated, and never read. This makes it reach the renderer, and closes the same hole in the visualization switcher.The dispatch gate, re-measured on the installed pin
This card was stopped three times by "merged upstream ≠ published ≠ pinned here", so the probe was re-run against the build this repo actually consumes — not the framework's
main, and notnode_modulesabsence (the trap the 2026-08-18 probe recorded):Functionally, against the installed build:
All three states are true.
premise_still_valid: true.And the contract half is already live in this repo, which is worth stating because it is why this is a pure reader fix:
mapis absent fromLIST_VIEW_LOCAL_OVERRIDES, sospecFieldsExceptimports it into objectui's ownListViewSchemaby reference. Measured:No local mirror of
ListMapConfigSchemawas added or needed. (Stated from that direct measurement rather than fromcheck:spec-symbol-derivation's green, which #5899 measured as skipping non-exported declarations.)Precedence — read off the siblings, not invented
Every sibling visualization in
ListView.tsxalready puts the view-level block ahead of the legacyoptions.<kind>bag. The direction is unanimous across all seven; only the granularity differs:kanban{...(schema.options?.kanban || {}), ...(schema.kanban || {})}calendar...(schema.options?.calendar || {}), ...(schema.calendar || {})gallery{...(schema.options?.gallery || {}), ...(schema.gallery || {})}timeline{...(schema.options?.timeline || {}), ...(schema.timeline || {})}gantt...(schema.options?.gantt || {}), ...(schema.gantt || {})tree(schema as any).tree || schema.options?.tree || {}chart(schema as any).chart || schema.options?.chart || {}mapfollows the five that merge — which are also the five that flatten config into props the way the map branch does. The two that use||are the two that consume a single config object wholesale. This is reported as an open question in the structured report, since the siblings do disagree on granularity even while agreeing on direction.What changed
ListView.tsx— a sharedresolveListMapConfigmerges the view-level block over the bag, per key, through the existing objectui#5177 whitelist.case 'map'forwards it.ListView.tsx, the switcher — the capability gate that decides which visualizations are offered readoptions.mapalone, so a view binding its coordinates in the spec block was filtered out of its ownappearance.allowedVisualizations(whitelist ∩ resolvable) and fell back to['grid']. It now asks the same merged config the render seam forwards, so the gate and the seam cannot disagree — including for a binding split across the two sources.InterfaceListPage.tsx— the ADR-0047 interface-page path built its map binding fromoptions.mapalone, dropping the block one seam aboveListView. It now forwardsview.mapalongside the auto-derived binding rather than folding it in with??like the sibling bindings:??would let a partial authored block (map: { titleField: 'title' }) replace the derivation wholesale and drop the derivedlocationField, rendering no markers at all.Deliberately unchanged: the branch still emits the flat form. A nested
mapkey wins outright atgetMapConfig(objectui#5018), which would turn this per-key merge into whole-block replacement and would tripwarnOnShadowedFlatMapKeys; that rule is written around the flattener ("neither flattener emits amapkey at all"). No defaults are added forzoom/center— objectui#5000's "declared camera wins, no declaration means fit to the queried records" is untouched, and a test asserts the keys are absent when undeclared.Per the card's ruling, no second validation of the seven keys was added at the ListView seam; the reading stays
getMapConfig's.Verification
Union at
304ad7579(the pushed HEAD): 27 passed (4 files). Affected-package suites: plugin-list + plugin-map 757 passed (60 files); app-shell/views 2948 passed, 1 skipped (306 files).The discriminating pin is end-to-end, not a spy:
ObjectMap.listViewMapConfigReach.test.tsxdrives the realListViewinto the realObjectMapand asserts the consequence — marker titles and description read from the declared fields, and a declared camera suppressing the fit (boundsundefined, exact lat/lng/zoom). That seam needs its own test becausegetMapConfigreaches the flat form only throughif (schema.locationField || schema.latitudeField), a gateListViewcurrently satisfies via itslocationField … || 'location'default.Reverse verification — two ablations, each with a restore trap
Ablation A — remove the view-level read from
resolveListMapConfig. Mutation confirmed on disk by grepping the removed text (1 → 0) and the text that had to survive (pickFlatMapConfig(schema.options?.map)still1), never an editor exit code:Red: all seven-key/flat-form/precedence/whitelist/camera arms, both switcher arms that author the block, and all four
plugin-mapreach arms. Green and named non-discriminating:options.map-alone controls, the no-config control, the "merge keeps a bag-only key" arm (itslocationFieldcomes from the bag either way), the fit-to-records control, and everyInterfaceListPagearm — that seam is a different file.Ablation B — remove the
InterfaceListPageforward, the seam A leaves untouched:Exactly the two arms that author a view-level block on the referenced view; the two
InterfaceListPagecontrols and all 20 other tests stayed green. The two ablations are complementary, which is what shows each seam is independently load-bearing.Both scripts carry
trap '<restore>' EXIT INT TERM;git diff HEAD --statwas empty after each. The suites resolve@object-ui/plugin-listthrough the root vitest alias table topackages/plugin-list/src, so these read source, notdist/— which the red results themselves prove, since adist/-resolved suite would have stayed green under a source-only mutation.Gates
Each quoted from the gate's own verdict line, exit code captured before any pipe:
Re-derived from the actual diff and also run:
check-published-dist-tooling(EXIT=0 — "No published package's build output carries tooling material", which covers the newsrc/-level test file in plugin-map),check-package-self-import(EXIT=0),check:esm-specifiers(EXIT=0),sync-quick-reference-release --check(EXIT=0).type-checkfor all three packages after building their dependency closures: each echoed its package name (packages/plugin-list type-check: Done, and the same forplugin-map/app-shell), so this was not a zero-match--filterexiting 0.Lint was not narrowed. The full repo scan ran:
eslint . --no-inline-config --format json→ 3608 files, 89 errors in 74 files, none of them mine (counter-probe: all 5 of my changed files were present in that run). Those 89 are pre-existing in packages that carry nolintscript, whichturbo run lint— what CI runs — skips by design. The CI-equivalent path is green:plugin-list/plugin-map/app-shelleachDonewith 0 errors (392 / 103 / 2625 warnings, all pre-existing in kind;--max-warningsis deliberately unset in this repo).One dependency edge, deliberately
packages/plugin-mapgains@object-ui/plugin-listas a devDependency, for the end-to-end test file alone. It is dev-only (no runtime source in plugin-map imports it) and acyclic (plugin-list does not depend on plugin-map). It is declared rather than left phantom becausecheck-phantom-dependenciesjudges tooling files against the package's own dependency fields or the workspace root's, and neither declared it. The alternative homes cannot host the test:plugin-listcannot resolvereact-map-glto mock it, andapps/console— which declares both plugins — cannot either.Not done here, and why
The card's "Done when" also names the showcase map view. That metadata lives in the framework repo, not this one —
examples/app-showcase/src/ui/pages/task-visualizations.pages.ts— whereTaskMapPagealready declaresappearance.allowedVisualizations: ['map']. So theallowedVisualizationswiring the card asks for is already correct and is not an objectui change; what remains there is declaring amap: { titleField: 'title', locationField: 'location' }block on that page to exercise this forward. Filed separately rather than reached across repos from this PR.Generated by Claude Code