feat(app-shell): register the ref:dataset spec-form widget, fed by the report inspector's dataset catalog (objectui#11601) - #11611
Conversation
…eport inspector's dataset catalog (objectui#11601) A field declaring `widget: 'ref:dataset'` resolved to the raw-JSON face because WIDGETS had no entry for it. RefDatasetWidget renders the dataset picker over a new optional WidgetContext `datasets` catalog, writes the dataset name, and degrades to a labelled text input when no host feeds a catalog. The report inspector passes its useDatasetCatalog result to the spec form; datasetPickerOptions moves to a pure shared module so the widget registry can import it without a cycle. Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
… widening of SchemaForm's widgetContext (objectui#11601) SchemaForm is exported from the package index and its widgetContext prop type gains an optional `datasets` member, measured on the built dist. An additive published widening reads as minor. Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Contract reviewServed-tier: Written 2026-10-04T11:35Z by the seat's at-tier review subagent, against GitHub and objectui's tree by ref ( Check-runs on the head. First read 2026-10-04T11:20Z: 42 runs, 29 success, 3 skipped, 10 in progress ( ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Final check-run read before this record was written, 2026-10-04T11:33Z: 43 runs, 40 success, 3 skipped ( |
|
Generated by Claude Code |
Fixes #11601
Clause-②: yes (widening).
SchemaFormis exported frompackages/app-shell/src/index.ts, and itswidgetContextprop typeWidgetContextgains one optional member,datasets. Measured on the builtdist:index.d.tsexportsSchemaForm,SchemaForm.d.tsdeclareswidgetContext?: WidgetContext, andwidgets.d.tsnow declaresdatasets?: LoadStateofDatasetCatalogEntry[]besideobjectNames(the positive control). The member is additive and optional, so no code that type-checked before stops type-checking. The claim'snocounted the index's metadata-admin exports as the page components and did not count theSchemaFormre-export. The changeset isminorfor the same reason.What this does
ref:datasetis a widget hint the spec already declares (the report form's top-leveldataset), butWIDGETShad no renderer for it.resolveFieldFacetherefore sent a field declaring it to the announced raw-JSON face: a JSON textarea under "widget ref:dataset — falling back to JSON until a custom renderer is registered." This PR registers that renderer.RefDatasetWidgetinwidgets.tsx, registered inWIDGETSasref:datasetand declared'control'inWIDGET_LABELLING. It reads the catalog from a new optionalWidgetContext.datasets(aLoadStateof the catalog entries), the same wayref:objectandview-reftake theirs. It handles the four catalog states asRefObjectWidgetdoes:PickerLoadFailurenotice (ref-dataset-load-failed) beside a text box that stays editable;SelectoverdatasetSelectOptions, which showsLABEL (name)and then— description(the objectui#11161 author text). It writes the dataset name. A stored name that the catalog does not offer stays as its own option, flagged throughengine.form.flaggedValueandengine.form.notFound, so it is not blanked and does not look like an offered dataset.inspectors/dataset-picker-options.ts(new, pure):labelBesideNameanddatasetPickerOptionsmoved here unchanged fromReportDefaultInspector.tsx, plusdatasetSelectOptions, the select-item text form the inspector already built inline. Importing them from the inspector would have created a cycle: the inspector rendersSchemaForm, which imports the widget registry. The inspector re-exportsdatasetPickerOptionsforDashboardWidgetInspector, which imports it from there. There is one definition.ReportDefaultInspectorpasses its existinguseDatasetCatalogresult to the spec form aswidgetContext.datasets, built withmapLoaded(rosterFrom(...))so a failure is read before loading, as for its own pickers.conditionScopeis required on everyWidgetContext. This PR states'none', which is thereportrow ofCONDITION_SCOPE_BY_METADATA_TYPE. The bundledReportSchemahas no key with a predicate-style name (counted over its derived JSON Schema: 73 property paths, none matched), so the report form's existing fields render unchanged.engine.form.*key.Not an inspector-local override, and this PR alone does not end the joined-block symptom
The "Joined blocks" row spec belongs to objectstack (
reportForm), and it is the only source of truth for which control a block'sdatasetgets. This PR does not patch that row in the inspector. The bundled@objectstack/specrow is still{ field: 'dataset', label: 'Dataset' }, so a joined report's blockdatasetstill renders as free text until objectstack-ai/objectstack#21714 declareswidget: 'ref:dataset'andrequired: trueon the row and that declaration reaches this inspector: through objectstack's.objectui-shapin moving past this PR, and, for this repository's own console, through an@objectstack/specrelease that carries objectstack-ai/objectstack#21714. This PR is the widget that row will resolve to. Read alone, it does not end the joined-block symptom on the card. That half belongs to objectstack-ai/objectstack#21714.Every other
SchemaFormhost leavesdatasetsabsent. A field declaringref:datasetthere now renders the labelled text input where it rendered the raw-JSON face before. This is pinned below.SchemaForm.tsxis not edited. Both repeater layouts already route a row field'swidgetthroughresolveFieldWidget: the grid cell passesfieldSpec: s, and the card row goes throughFieldRow. The pins below exercise both layouts.Tests
Measured at
9ebe77b, the final commit, unless noted.b92329c, before any edit):pnpm exec vitest run packages/app-shell/src/views/metadata-admin/SchemaForm.refDatasetWidget-11601.test.tsxgaveTests 9 failed (9). Every case found the announced fallback notice and atextarea, which is the raw-JSON face.SchemaForm.refDatasetWidget-11601.test.tsx(9 cases): with a catalog, the picker in a section and in a repeater row (grid and card) writes the dataset name, the options are the author text, and an out-of-catalog stored name stays visible and flagged. With no catalog (noWidgetContext, or one withoutdatasets), the field degrades to a text input named by its label, in a section and in both repeater layouts, writes the typed name, and shows no raw-JSON face.inspectors/ReportDefaultInspector.blockDatasetPicker-11601.test.tsx(2 cases): the bundledreportFormis mocked with exactly the spec(ui):reportForm's "Joined blocks" row offers a block'sdatasetas free text with norequired, so Studio saves the block #21702 now refuses objectstack#21714 declaration added to the joined-blocksdatasetrow. The fixture throws unless it declares exactly one row. The inspector's catalog then reaches the block row's picker, and picking writes{ ...draft, blocks: [{ name: 'won_deals', dataset: 'support_load' }] }with nothing else changed.WIDGET_LABELLING, so adding the widget requires a row in each):SchemaForm.widgetLabelling.test.tsx(a loaded case and a no-catalog case),SchemaForm.controlWidgetGridNaming-9889.test.tsx(the grid cell is named by the column header), and__tests__/SchemaForm.controlWidgetFailureArmNaming-9931.test.tsx(the failure arm keeps a labelable control in both layouts, the banner ledger readsref-dataset-load-failed, and the loaded leg shows no banner).b58a18b(the source commit;9ebe77bonly edits the changeset):pnpm exec vitest run --maxWorkers=2overpackages/app-shell/src/views/metadata-admin/plus every test that imports the three edited source files from outside it, or reads them as source text:spec-symbol-parity, the fourReportConfigPanel/ReportViewhost tests,view-filter-fold.ratchet(readswidgets.tsx),packages/corecolumn-identity.ratchet(scanswidgets.tsx/SchemaForm.tsx), andpackages/i18nreport-editor-retired-4145. Result:Test Files 389 passed (389),Tests 4550 passed | 1 skipped (4551). Re-run at9ebe77bfor the two ratchets and the two new pin files:Test Files 4 passed (4),Tests 27 passed (27). The narrowing selected tests by import and by source-text read. Tests that reach these files only transitively (for example theStudioDesignSurfacesuites) were not run locally. CI'spnpm testruns them.pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' buildpassed, thenpnpm --filter @object-ui/app-shell type-checkpassed (tsc --noEmit && tsc -p tsconfig.test.json).--listFilesOnlyon the test project lists all five touched test files and the new module.Ablations (each one ran on committed
b58a18bthroughablation-replace.mjs, which mutates through an anchor that must hit and restores by blob hash)WIDGETSentry (anchor 1 to 0, blobfbfd4a2d90afto83ddbf6c965c): 17 failed and 138 passed across the two new pin files and the three registry pins. Every picker pin and every degrade pin failed, because the field fell back to the raw-JSON face. The registry gates failed ("declares exactly the registered keys", "probes every registered key"), and so did the 9889 and 9931 rows forref:dataset. Restored: blobfbfd4a2d90afequals HEAD, andgit diff HEADis empty.8a6b57240b1dto76637674f521): both inspector picker pins failed (Unable to find … role "combobox" and name "Dataset"), and the block row rendered a textbox named "Dataset", which is the degrade face. All 9SchemaFormpins, including the no-catalog degrade pins, stayed green. Restored: blob8a6b57240b1dequals HEAD, andgit diff HEADis empty.Gates
All exited 0 at
9ebe77bunless noted:pnpm check:control-bytes,check:new-line-citations(0 new citations),check:changeset-claims,check:pending-changeset-literals,node scripts/check-changeset-presence.mjs,pnpm changeset:check. These also exited 0 atb58a18b:check:icon-record-names(it readswidgets.tsx),check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape(the new test adds avi.mock),check:test-path-roots,check:unreferenced-sources(the new module),check:i18n-keys,check:i18n-drift,check:i18n-dead-keys,check:i18n-designer-parity,check:designer-field-key-parity,check:handler-key-reads,check:metadata-write-doors,check:spec-symbols,check:self-import,check:esm-specifiers,check:phantom-deps,check:registry-bare-names,check:lint-rule-coverage.ESLint (
--no-inline-config) over the eight touched.ts/.tsxfiles: 8 files linted, 0 errors. Warning counts are unchanged from the base for the two edited source files (3 and 3, 37 and 37). The new files have none. Type-aware linting is not enabled (tseslint.configs.recommended, with noprojectorprojectService), so this diff cannot change the lint verdict of any file it does not touch. CI'spnpm lintruns the repo-wide scan.NOT MEASURED:
check:eager-locale-cataloguesandcheck:sdui-registration-pins. Reason: both read a builtapps/consolebundle (PREREQUISITE NOT MET), and a console build was not run locally. The same applies tocheck:eager-closure. CI runs them.Acceptance notes
RowCell, the cell renderer insidemaster-detailrows inwidgets.tsx, special-cases onlyref:objectandref:component. Aref:datasetcolumn inside amaster-detailwidget would therefore not render the picker. No producer declares that shape (the joined-blocks row is arepeater, which this PR covers), so this is noted here and not filed.content/docspage or package README lists the metadata-admin widget hints, so there is no doc to update.Generated by Claude Code