…chart blocks, and its own allowPrinting; a census pins every member ListView reads
InterfaceListPage hand-projects the list schema it hands ListView. The
projection carried the kanban/calendar/gallery/timeline/gantt/map blocks
but not tree or chart, so a page whitelisting either kind reached the
renderer with viewType tree/chart and no block. Both are now relayed whole
(no page-derived default exists for either kind, so derives() has nothing
to gate). The page config's own allowPrinting was never written either;
it is now relayed from the page config, like showRecordCount.
The new relay census derives, with the TypeScript parser, every top-level
member ListView.tsx reads off its schema (accounting for every whole-schema
escape), and requires each to be relayed from the view or answered in a
ledger whose kinds carry mechanical evidence (page config keys, spec view
keys, the ListView element's props).
Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2
Co-authored-by: Claude <noreply@anthropic.com>
Fixes #11572
Clause-②: no
What changed
InterfaceListPagebuilds the list schema it handsListViewitself, key by key, from its page config and the view its deprecatedsourceViewresolves to. Measured onorigin/main0685646before any edit: a stored row{ type: 'tree', tree: { parentField: 'parent_id' } }on a page whitelisting['tree']reachedListViewwithviewType: 'tree'andtreeundefined, so the tree branch had noparentField. A spec-valid chart row (chart: { dataset, dimensions, values }, whitelisted['chart']) lost its block the same way. The object page relays both, so one stored view drew two ways.treeandchartare relayed whole (a pointer, not a projection of their keys, as the object page forwards the chart block), as conditional spreads beside themapblock. Both are written only when the view declares them, so a view without them hands down the same key set as before.derives(kind): no page-derived default exists for either kind (nothing on this page can guess a parent pointer or a measure), so the rule has nothing to gate. The declared block alone is relayed, and a whitelisted kind with no declared block still reachesListViewwith none. A control case pins that nothing is invented.allowPrinting(declared onInterfacePageConfigSchemaas "Allow users to print the page") was never written, so the toggle drew no print button. The census below found it, and it is now relayed from the page config only, besideshowRecordCount; the source view's copy does not stand in for it. This is the honest disposition the dispatch asked for instead of a ledger entry: a page-declared key the renderer reads and this page dropped. It uses the bounded in-place rule (same defect class, mechanical one-line fix, same file under this claim, covered by the same pin).The census (the card's acceptance)
InterfaceListPage.relayCensus-11572.test.ts, beside the 10638 pin. Its population is derived, never listed:ListView.tsxreads off its list schema, found with the TypeScript parser, so comments never count. The scan follows every read shape (schema.X,schema?.X,(schema as any).X,schema['X'], destructuring) and accounts for every place the schema escapes whole: a call into a helper declared inListView.tsxwhose parameter is itself namedschema(its reads join the scan), the one foldnormalizeListViewSchema(propSchema), and hook dependency lists. Any other escape (a child handedschema={schema}, an alias, a helper with a differently named parameter) fails by name, because reads behind it would be invisible. A fixture control runs the same scan on an inline source and checks it answers both ways (sees code, not prose; flags an unfollowable escape). At23030ffthe population is 56 members; the pin asserts no count.viewDefor a local derived from it, to any depth (view,appearance,allowed,kanban, ...).kindand a one-line reason, and each kind carries mechanical evidence checked on every run:23030ffpage-ownedInterfacePageConfigSchemaaddRecord,showRecordCount,allowPrinting,userActions,inlineEditpage-propcfg.recordActionand the ListView element carriesonRowClicknavigationpage-bindingcfg.sourceobjectName,datapage-textpage.label/page.descriptionitselflabel,descriptionpage-constant/closed-surfaceallowExportis still the literalfalseallowExport,exportOptionsnot-inheritedaria,compactToolbar,conditionalFormatting,filterableFields,resizable,rowHeight,selection,rowActions,bulkActions,bulkActionDefs,sharingoff-spec/host-runtimegroupBy,groupBy2,columnState,wrapHeaders,operations,rowActionDefs,id,onDensityChange,onNavigate,refreshTriggerPlus: no ledger entry may name a member that is relayed or no longer read, every reason must have content, the tree/chart (11572) and hiddenFields/fieldOrder (10638) relays are named checks, and a write-direction check refuses a key the literal writes that
ListViewdoes not read (the node discriminatortypeis the one named exception), which also keeps a legacy spelling out of the literal.Where the line sits, and why. The spec describes
sourceViewas "@deprecated Back-compat only. Pre-revision pages inherited columns/filter/sort from a named object view". So the census treats as relayable the inherited data (column composition, filter, sort) and the visualization bindings the page whitelists and has no slot for (kanban...tree,chart). Spec view keys outside both, with no page slot, are answerednot-inherited, each with a page-layer reason as well. This is the spec's own line, not a way to make the pin green; the one member that failed that test (allowPrinting) was relayed instead. The open question in the report asks whether the maintainer wants that line moved.Ablations (each committed first, mutated through
ablation-replace.mjs, restored and proven againstHEAD)Both pins resolve their subject from source on disk (a relative import and
readFileSync), not through a packageexports/dist, so no build leg applies.treerelay inInterfaceListPage.tsx: anchor 1 to 0, blobaebbafato10e1955. The census went red namingtree("expected [ 'tree' ] to deeply equal []") plus its named check; the three tree behavioral cases went red; chart and all controls stayed green. Restored: blob equalsHEAD(aebbafa),git diff HEADempty.schema.newKeyread inListView.tsx: the first attempt was a no-op, refused by the tool because my replacement re-contained the anchor (anchor 1 to 1); vitest never ran and that reading is void. Re-anchored so the anchor is consumed: anchor 1 to 0, blob5295667toac7671a; the census went red naming exactlynewKey(1 failed, 17 passed). Restored: blob equalsHEAD,git diff HEADempty.Verification (union run after the final commit,
23030ff)0685646had 5 failed / 4 passed (the three tree/chart fix cases, the precedence case and theallowPrintingfix case red; the four controls green).pnpm exec turbo run build --filter='@object-ui/app-shell^...' --concurrency=2: 28 of 28 tasks successful.pnpm --filter @object-ui/app-shell type-check(tsc --noEmit && tsc -p tsconfig.test.json): exit 0;--listFilesOnlyon the test project lists both new test files.pnpm exec vitest runover everyInterfaceListPage*/ObjectView*/PageView*test in app-shell (55 files) plus every other test file that namesInterfaceListPage(15 files, across app-shell, components, core, data-objectstack, i18n, plugin-calendar, plugin-list, types): 70 files, 864 tests passed. The fullpnpm testis CI's.check:control-bytes,check:new-line-citations(0 new),check:changeset-claims,check:pending-changeset-literals,check-changeset-presence,-no-major,-fixed,-overwrite,check:test-path-roots,check:vi-mock-specifiers,-inherit,-override-shape,check:phantom-deps,check:spec-symbols,check:unreferenced-sources,check:handler-key-reads,check-hand-rolled-comment-mask,check:self-import,check-type-check-coverage.check:changeset-claimsasked for one re-read:.changeset/7218-rowcolor-host-relay.mdnamesInterfaceListPage.tsx("has shippedrowColor: view.rowColornext togroupingandpagination"). Re-read against this diff: still true, those rungs are untouched.eslint --format jsonfrompackages/app-shell(asturbo run lintruns it) over the 3 changed source files: 3 files in the output, 0 errors (warnings areno-explicit-anyon pre-existing lines and in the test mocks, as in the 10638 sibling;--max-warningsis deliberately unset inlint.yml). Population read from the config: the rooteslint.config.jsis the only config. Invariance: it enables no type-aware linting (noprojectService/parserOptions.project), so this diff cannot move a verdict on an untouched file. Repo-widepnpm lintis CI's.Acceptance notes (observations, not filed)
grouping,rowColor,pagination,searchableFields,emptyState, and the view'sappearanceas the page's fallback. They predate this change; the census counts them as relayed and neither retires nor extends them. Carrier: none.levels("Number of hierarchy levels to display"), and nothing in this tree reads it. Dormant, outside this population (ListViewdoes not read it). Carrier: none.content/docsdocuments what an interface page takes from itssourceView, so there is no doc to bring in line; the census is the instrument that says it.ObjectView.tsxandListView.tsxare untouched (the latter only mutated temporarily, and restored, for ablation 2).Out of scope and reported for the seat to file (not filed here):
CreateViewDialogwrites a chart view's block as{ chartType, xAxisField, yAxisFields }, andbuildSaveAsViewSpecspreads that payload into the persisted spec, while@objectstack/spec17.6.0'sListViewSchemarefusesxAxisField/yAxisFieldsonchartby name and requiresdatasetandvalues(measured withsafeParse; the save door itself was not exercised).Implemented by session
session_01FjqrwXPfSMkSfkKYDSRkN2(domain:ui seat 1, os-dev dispatch).Generated by Claude Code