Repository navigation
feat(spec)!: a type: 'chart' list view whose effective binding names no dataset is refused at every list-view door - #22528
Conversation
…et is refused at every list-view door Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
… catalogue its export Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…r, its changeset and the REST door pins Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…s for the chart-binding check Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…now requires Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 139 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 30f69404c4a11894961989125dbd165b0bcb54d4 && git checkout 30f69404c4a11894961989125dbd165b0bcb54d4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b53b949a1518ccd5bbfb2f3b8a6fac4eaa9c2da2 2c0f7aa07f660e869af379fc18b76b9bb15782b2 && git checkout -B drift-repro b53b949a1518ccd5bbfb2f3b8a6fac4eaa9c2da2 && git merge --no-ff 2c0f7aa07f660e869af379fc18b76b9bb15782b2
node scripts/docs-audit/affected-docs.mjs --json b53b949a1518ccd5bbfb2f3b8a6fac4eaa9c2da2
|
…art-view-needs-dataset
Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…anges.json and the protocol upgrade guide (protocol 18) Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…eckListViewChartBinding Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…tracker id; regenerate its projections Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…art-view-needs-dataset
… could not text-merge Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…hanges.json and the upgrade guide carry view-chart-binding-dataset-required beside main's entries Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…art-view-needs-dataset
…hart-binding-dataset-required beside main's flow-text-slot-unbound-dollar-root-refused Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: PR #22528 (card #22491). Net diff of the branch against its merge-base with ① Derived judgmentsAccept-set changes (all narrowings, every one judged on
Public-surface changes:
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Landing pre-checks at
|
Fixes #22491
Clause-②: yes (narrowing)
Claim
6083229542(sessionsession_01KNKBCRDJCu5tGy3TEbvtrF, branchclaude/issue-22491-chart-view-needs-dataset), triage direction6082420385. Every list-view door now refuses atype: 'chart'list view whose effective chart binding names no dataset, with the binding to declare. No default binding is fabricated.What changes
The rule (
packages/spec/src/ui/view.zod.ts):checkListViewChartBinding, exported, attached by identifier at the same three list-view doors ascheckListViewCalendarVisualization:ListViewSchema, which also coversviews[].list/listViews, a view itemconfiganddefineView;ObjectListViewSchema(objects[].listViews);PUT /api/v1/meta/view/:name).One rule judges every door. There is no door-specific second contract.
The effective binding is the renderer's: the
chartblock, else the legacyoptions.chartbag, with the block replacing the bag whole. objectuipackages/plugin-list/src/ListView.tsxresolveListChartBinding,schema.chart || schema.options?.chart || {}::207at this repo's.objectui-shapinf0268ad7,:260at objectui2a48bd40. Read only; nothing in objectui was touched.customissue atchart. Its first sentence is "This list view istype: 'chart'but declares nochartblock, so it binds no dataset and there is nothing to plot." The remedy nameschart.datasetandchart.values.datasetorvalues: onecustomissue per missing key, atoptions.chart.dataset/options.chart.values. Only the overlay carries the bag; the two authoring doors refuseoptionsby name.chartblock: left to its own strict schema, which already requires both keys atchart.dataset/chart.values, so nothing is reported twice.The overlay reads
typeon the input side, ascheckListOverlayTypeNeedsColumnsdoes. A patch that names notype(the console's toolbar save) is not judged; the view it shadows decides.The
chartslot gains a.describe()saying a chart view must bind one. The reference pages regenerate with it.Ledger: the D3 semantic entry
view-chart-binding-dataset-required(step 18). It has no rationale fragment, so the hand-written region ofregistry.tsis untouched and only the generated region moved. There is no D2 conversion, because only the author knows the dataset. The changeset ismajoron@objectstack/spec, because pre modenexttakes a breaking change atmajor. It carries the registered ADR-0087 marker.Premise, measured on
origin/main@e148ca98(before)ViewMetadataSchemaunionListViewSchemaObjectListViewSchematype: 'chart', no blockoptions.chart: { chartType }onlyoptionsunknown key)chart: { chartType }chart.dataset/chart.valuesAfter the change, the first two rows are refused at
chartand atoptions.chart.dataset+options.chart.values. The container member (list) and the view item member (config) refuse them too, atlist.chart/config.chart.Stored rows (Zone 2 item 4), measured
applyConversionsToStoredItemis the one rehydration primitive. It replays only the conversion registry, and no entry touches a list-view chart block. The read door does not re-validate: it decorates.meta-view-chart-binding.test.tsmeasures this through the realPUT/GET /api/v1/meta/view/:nameover SQLite. A row stored with no binding, planted directly insys_metadata, is served200as stored, with_diagnostics.valid: falseand the same issue atchart. Re-saving the body that was read answers422 INVALID_METADATAatchart. So no stored view becomes unreadable, and each one is refused on its next save. The ADR-0087 disposition isregistered view-chart-binding-dataset-required; the changeset states it, along with this read-side behaviour.Pins
packages/spec/src/ui/view-chart-binding.test.ts, across all three doors:type: 'chart'with no block is refused atchart. The test checks thecode, the path and the message's first sentence, and that the remedy names both keys.chartinallowedVisualizations.chartTypeis refused at both keys;datasetbut novaluesis refused atvalues;typeis not judged;ListChartConfigSchema's required keys and asks the bag for exactly those.packages/spec/src/ui/object-refinement-check-exports.test.tscatalogues the export:packages/rest/src/meta-view-chart-binding.test.tsexercises the real door (RestServerroute, thensaveMetaItem, then ObjectQL with SQLite):422+INVALID_METADATA, the issue path, and an empty store for both refusals;200, with the stored row carrying its binding;Fixture triage (consumer radius, not just
packages/spec)I grepped
type: 'chart'acrossexamples/**,packages/**andskills/**:examples/app-showcase(project.view.ts,task.view.ts) bind a dataset and measures. Importing the showcase'ssrc/ui/viewsagainst the built spec runsdefineViewat import and parsed 26 list views, 2 of them charts.showcase-shape.fixtures.ts,validate-chart-bindings.test.ts) bind a dataset and a measure.additional-typeshits are dashboard widgets and a metadata type name, not list views.Two spec pins parsed a block-less chart view as a stand-in for "every type". Each was re-judged as missing a declaration, and now carries the binding:
view.test.ts, "keeps every surviving view type accepting exactly as before";view-form-pagination.test.ts, "is backed by the door: type 'chart' parses a pagination block". Studio's view form does have achartsection (view.form.ts), so a Studio author can declare the binding.Reverse verification
I ran this once in a second worktree at
7836b326, after committing the fix. The mutation went throughscripts/ablation-replace.mjs: the check's guard line became an unconditionalreturn, the anchor count went 1 to 0, and the blob went0915637ftod75f30ac. The two spec pin files then ran 10 failed / 204 passed:The direction was the expected one: the pins turned red. The restore was proven: the blob equals HEAD and
git diff HEADis empty. These spec tests resolve./view.zodfrom source, so nodistrebuild was involved. The REST pin reads the built spec and was not ablated.Verification (local; first round at
7836b326, patch round ate295f361; CI owns the full farm)Build:
pnpm --filter @objectstack/spec build, thencheck:generatedfound 3 of 15 artifacts stale (api-surface,export-origins,docs).--fixregenerated only those three, and a re-check reported all 15 up to date. After that I built the dependency closure ofrest,lintandmetadata*with turbo: 24/24 tasks.@objectstack/spectests, 4 shards (--project local)7836b326, and the four touched spec test files re-run at7836b326gave 4/4 files, 732 tests.@objectstack/spectypecheck (tsc+ scripts + test layer)@objectstack/linttests@objectstack/resttests (--project local, full)meta-view-chart-binding.test.ts@objectstack/resttypecheck (tsc+ test layer)@objectstack/metadata-core/metadata-fstests@objectstack/metadata/metadata-protocol(narrowed, see below)examples/app-showcaseviews, imported against the built spec.tsfiles,--format jsondispatch-gates --commands(114 derived) + the 49 artifact-roster commandscheck:skill-examplesre-ran at 0 after itsclient-reactprerequisite was built.check:dual-build-cjs-loadsis NOT MEASURED: it exits 3 with PREREQUISITE NOT MET because it needs every package built. Three roster guards (closing-target-claim,partof-closing-keyword,single-claim-paths) exit 2 NOT WIRED without a PR;partof-closing-keywordwas re-run on this body and passed (exit 0).--ranreports "114 derived famil(ies) accounted for — 113 run, 1 NOT-MEASURED".Declared narrowing (coordinator-approved):
metadataandmetadata-protocolran their view-door test files only; the full suites are CI's.type === 'chart'.git grep -nE "['\"]chart['\"]"overpackages/metadata,packages/metadata-protocol,packages/metadata-core,packages/metadata-fs,packages/restandpackages/objectql, leaving out my own new test, finds two hits. Neither is a list view: one is a report container inprotocol.invalid-metadata-422-face-inventory.test.ts, the other a zod union member inzod-union-fields.test.ts.typeenum over the same packages (shape\.type|overlayTypeValues|'gantt', ?'map') finds onlypagetype reads. The control term in the same corpus,viewKind|ListViewSchema|ViewMetadataSchema, hits 4 test files inmetadataand 29 inmetadata-protocol.protocol.stored-conversions,protocol.invalid-metadata-422-face-inventoryandprotocol.read-decorations(31 inmetadata-protocolonce de-duplicated).metadata:metadata-manager-views-by-object-container,plugin-artifact-view-container-object,view-container-name,view-expand.eslint.config.mjs's ownfiles: ['**/*.{ts,…}']objects; the file count comes from--format json. That config never enables type-aware linting (noparserOptions.project; it says so at:327), so the diff cannot move the verdict on any file it does not touch.Patch round, at
e295f361.origin/mainatee8751d4withscripts/pm/os-regen-merge.sh(no rebase) into merge commitbe6f3e23. That brought in protocol 18 from4e9fe9ff.d3900905regeneratedcontent/docs/references/data/object.mdxfrom the merged tree, after a spec build. That build ran after the merge commit, never in the MERGE state.742960d6contains onlygen:spec-changes+gen:upgrade-guideoutput.origin/mainis still present after the merge; the only id added isview-chart-binding-dataset-required.b83e8aaf):Clause-②: yes (narrowing), and one sentence naming the new exportcheckListViewChartBinding.check-changeset-no-major,check-adr-0087-registration("registered view-chart-binding-dataset-required (new here)"),check-empty-changesetandcheck:changeset-gate-self-testseach exit 0.e295f361): the D3 entry'sreasonnamed an objectui tracker id, which themigrate metaguidance must not print. That wording is gone, and the projections were regenerated withgen:migration-registry/gen:spec-changes/gen:upgrade-guide. No other#plus 4–5 digit id is left in this diff's printed text (refusal messages,.describe(), changeset, entry strings).b83e8aaf);b83e8aaf);restmeta-view-chart-binding.test.tsafter a turbo build of@objectstack/rest^...(24/24): 1/1 file, 4 tests (atb83e8aaf);packages/clitest/migrate-meta-engine-guidance.test.ts(--project integration), after a turbo build of@objectstack/cli...(59/59): 1/1 file, 3 tests (ate295f361).e295f361changes only the entry's prose and the three projections, so theb83e8aafruns read identical inputs.e295f361: all 161 union commands (dispatch-gates --commands, 114 derived, plus the 49 artifact-roster commands) were run, each exit code captured before any pipe.check:spec-changes,check:upgrade-guide,check:migration-registry,check:generated,check:error-code-provenance,check:skill-examplesandcheck:dual-build-cjs-loads.check-closing-target-claim,check-partof-closing-keyword,check-single-claim-paths) exit 2 without a PR. Re-run against PR feat(spec)!: atype: 'chart'list view whose effective binding names no dataset is refused at every list-view door #22528, each exits 0.--ran: "✓ dispatch-gates --ran: 114 derived famil(ies) accounted for — 114 run, 0 NOT-MEASURED (a DERIVED zero — all 114 recorded an exit code and none of them is 3)."spec,lint,rest,metadata*andclisuites at the merged head.Acceptance notes
.refine()that rejects existing metadata at registration". This rule sits at the parse doors (authoring,defineStack, the write door). That is the same placement as the calendar binding check and the joined-reportblocks[i].datasetrefusal. The runtime registration seam is untouched: views do not register through it, and stored rows keep loading, with_diagnostics.type: 'chart'is judged. A grid that only offers a chart inappearance.allowedVisualizationsis a degrade, not a dead screen: objectui'savailableViewsgate asks the same resolver and never offers an unbound chart. This is recorded and pinned as a deliberate non-rule.checkViewCompletenessstays silent onchart. The schema doors now refuse the block-less view, so a completeness finding would double-report it.packages/app-shell/src/views/ObjectView.tsx:3162reads onlyviewDef.chartfor atype === 'chart'view, notoptions.chart. A view whose only binding is a complete bag therefore renders the unbound refusal on that route, but plots onListView's. That makes two precedences in one renderer repo. It belongs with objectui'sdomain:specseat, which files theListViewSchemamirror after this lands.#22491names only this card. objectui's mirror is not part of this PR.Generated by Claude Code