Skip to content

fix(spec): reportForm offers a joined block's dataset as the ref:dataset picker, marked required (#21714) - #21819

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-21714-joined-block-dataset-widget
Oct 5, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-21714-joined-block-dataset-widget

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21714
Clause-②: no

Claim 5987856715 (the card's Change, unblocked by the director seat 5987403710). In reportForm, the "Joined blocks" repeater's dataset row now declares widget: 'ref:dataset' and required: true. Studio's report inspector now draws a joined report's block dataset as the dataset picker, with the required marker, instead of a free-text cell with no marker. Since #21702 (PR #21712), the joined arm of ReportSchema refuses a block that binds no dataset, at blocks.N.dataset. The old cell therefore let an author add a block that was invalid by construction.

What changes

  • The row (packages/spec/src/ui/report.form.ts, the "Joined blocks" repeater): { field: 'dataset', label: 'Dataset' } becomes { field: 'dataset', label: 'Dataset', widget: 'ref:dataset', required: true }, with a comment saying why. No other row, section or form changes.
  • One pin (packages/spec/src/ui/report-joined-block-dataset.test.ts, beside the tests for the refusal itself): the row declares widget: 'ref:dataset' and required: true. The pin reads the exported reportForm, which is the output of defineForm's parse, so a form schema that stripped either key would also turn it red. The nested row is the same strict, recursive FormFieldSchema that already carries widget and required on top-level fields.
  • Changeset: @objectstack/spec patch (measurement below).
  • ⛔ No schema, accept-set, type or export change. JoinedReportBlockSchema.dataset stays optional on the block shape, and the joined arm's refusal is unchanged. defineForm returns FormViewParsed, so reportForm's exported type does not move. No objectui file.

The renderer side, measured at the pin

.objectui-sha on main is 9dfaca654311. Every reading below comes from git show 9dfaca654311:PATH in an objectui clone, read-only. git merge-base --is-ancestor b508ac50d9 9dfaca654311 exits 0, so objectui PR 11611's merge is in the pin. That reading is an exit 0 and therefore holds in a shallow clone too.

claim where, at objectui 9dfaca654311
ref:dataset is registered in WIDGETS packages/app-shell/src/views/metadata-admin/widgets.tsx:3035 ('ref:dataset': RefDatasetWidget). The widget is at :683, and it is declared 'control' at :3144.
RepeaterField resolves a row field's widget from the row spec packages/app-shell/src/views/metadata-admin/SchemaForm.tsx:2809: the grid cell calls resolveFieldWidget({ name: s.field, schema: sub, fieldSpec: s, widgetContext }). The card layout passes fieldSpec={s} to FieldRow (:2884), which resolves through the same function (:1838). inferWidget returns an explicit fieldSpec.widget first (:447), and a spec that pins a widget skips every name detector (:1031).
it passes the row field's required SchemaForm.tsx:2799 (grid cell) and :2882 (card row) both pass required={Boolean(s.required)}. The grid header draws the marker at :2772. For a non-boolean field, FieldRow draws the star (:1878, :1917) and passes aria-required through FieldControl (:1930).

The joined blocks repeater declares no widget, so useGrid is false (SchemaForm.tsx:2643) and its rows render in the card layout, as FieldRows. Three things connect the inspector to this row:

  • ReportDefaultInspector.tsx:374–:381 feeds the dataset catalog as widgetContext.datasets, and passes it to the spec form at :652.
  • report-schema.ts:80–:126 (getReportForm) prunes only top-level section fields, so the blocks row specs reach SchemaForm unchanged.
  • The inspector reads reportForm from @objectstack/spec/ui (report-schema.ts:25). objectstack's console build injects this tree's spec into that import (scripts/build-console.sh, OBJECTSTACK_SPEC_DIST).

Browser check: not done. It is optional on the card. It would need objectui's console built at the pin with this tree's spec injected, plus a backend and a logged-in Studio session, which is heavy on a shared container. The static chain above is the evidence. The browser reading of the card's second pin is still open.

Reverse verification

The fix was committed first (02307d05b6). Then node scripts/ablation-replace.mjs replaced the anchor widget: 'ref:dataset', required: true } with widget: 'ref:dataset' } in report.form.ts: anchor count 1 to 0, blob 8bc1038e3612 to b0da741da01a. The test imports ./report.form from source, so no dist/ is in the resolution path.

  • Predicted: the new pin turns red at expect(row?.required).toBe(true) and the other 16 stay green.
  • Observed: Tests 1 failed | 16 passed (17), AssertionError: expected undefined to be true at report-joined-block-dataset.test.ts:261. The direction is the usual one, red.
  • Restore (the tool's own leg, plus a shell trap): the blob equals HEAD (8bc1038e3612) and git diff HEAD is empty.

Regenerated artifacts

None needed. pnpm --filter @objectstack/spec build followed by check:generated reports all 15 generated artifacts up to date. The only generated files built from form specs are the nine packages' i18n bundles, which carry label and helpText. pnpm check:i18n reports OK (9 package(s) — all bundles in sync, no undeclared authoring keys).

Changeset measurement

@objectstack/spec's files[] ships dist and src/**/*.zod.ts. report.form.ts itself is not packed (npm pack --dry-run --json: 2068 entries, no report.form.ts), but its bundled bytes are. After the build, widget: "ref:dataset", required: true occurs in 6 packed files: dist/ui/index.{js,mjs}, dist/system/index.{js,mjs} and the two dist/browser/system twins. Positive control: an unchanged string of the same form, Additional dataset-bound blocks stacked into a single report, occurs in the same 6. The diff therefore publishes from @objectstack/spec, so it carries a patch changeset and no skip-changeset.

Tests and gates, at d7c811ff38

  • pnpm --filter @objectstack/spec test: Test Files 615 passed (615), Tests 18361 passed | 1 todo.
  • pnpm --filter @objectstack/spec typecheck exits 0: tsc --noEmit, check:scripts-typecheck and check:test-typecheck (52 file(s) / 246 error(s) / 135 pinned signature(s) held). Per tsc --listFilesOnly, the edited test file is in tsconfig.test.json's program (1 hit) and not in tsconfig.json's.
  • The three platform-objects tests that walk reportForm (repeater-row-properties, report-form-echo-decisions, dataset-panel-echo-decisions): 3 passed, 62 passed.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 86 commands.
    • Every exit code was recorded before any pipe.
    • --ran reconciliation: 86 derived, 86 run, 0 NOT-MEASURED, 0 UNRUN.
    • Five gates first exited 3 (PREREQUISITE NOT MET) and check:type-check-debt ran past my per-command timeout. All six were re-run after a full workspace build (turbo run build --concurrency=2, 72 of 72 tasks) and exit 0.
    • check-changeset-no-major --base origin/main exits 0. Its clause-② level axis is PR-scoped and not applicable on a local run; CI reads this body's Clause-② line.

Acceptance notes

  • reportForm reaches Studio from the @objectstack/spec that the console bundles. Through objectstack's console build that is this tree, because of the OBJECTSTACK_SPEC_DIST injection. A standalone objectui build uses its own lockfile's @objectstack/spec and gets the row once a spec release carrying it lands there.
  • The top-level dataset hint (report.form.ts:39) still never renders in the inspector, which prunes that field and draws its own curated picker (FORM_FIELDS_OWNED_ELSEWHERE, report-schema.ts:33). That was already known on the card, and nothing here changes it.

Generated by Claude Code

claude added 2 commits October 5, 2026 04:08
…set picker, marked required

The "Joined blocks" repeater's `dataset` row declared only a label, so Studio's
report inspector drew a free-text, unmarked cell for a key the joined arm of
ReportSchema now refuses when absent (#21702). The row now declares
`widget: 'ref:dataset'` (registered in objectui's WIDGETS at the console pin)
and `required: true`, and a pin beside the refusal's own tests holds both.

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation protocol:ui tests tooling labels Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 18c7dfd2e68cd2630420080b49a5f6a62fe60a6a → packageMentionDocs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(ui): reportForm's "Joined blocks" row offers a block's dataset as free text with no required, so Studio saves the block #21702 now refuses

2 participants