Skip to content

fix(fields): a boolean field draws a checkbox and a toggle field a switch, on the canvas and in the form - #27665

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-6ybbdj
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-6ybbdj

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

Requested by Bill · project thread
Fixes #27519

Clause-②: yes (widening) — @object-ui/fields gains two public exports, resolveBooleanControl and type BooleanControl
Metadata-protocol: none

维护者速读

  • 改了什么:Studio 里以「Checkbox — shown as a checkbox」(boolean)创建的字段,现在在 Studio 表单画布、运行时新建/编辑表单和表格行内编辑里都画成复选框;只有「Toggle — shown as a switch」(toggle)才画开关。字段显式写了 widget(checkbox/boolean 或 switch/toggle)时仍以 widget 为准。
  • 为什么改:两种类型都落到同一个 field:boolean 控件,它只认 widget: 'checkbox',否则一律画开关;画布又各自手写一份,toggle 还掉成了文本框。分诊裁定:画布与运行时表单共用一张「类型→控件」映射,不做分面覆盖。
  • 风险与回滚:所有表单里声明为 boolean 的字段(含 Setup 的元数据表单)都会从开关变成复选框,这正是分诊要求的行为;想保留开关的字段应声明为 toggle 或写 widget: 'switch'。只改前端渲染,元数据与存储值不变。回滚即 revert 本 PR。
  • 先看:ui/packages/fields/src/widgets/booleanControl.ts(唯一的映射);BooleanField.tsx 里改为读它的那一行;ObjectFormDesigner.tsx 的 FieldControlPreview 布尔分支。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/fields/src/__tests__/booleanControl-27519.test.tsx packages/app-shell/src/views/studio-design/ObjectFormDesigner.booleanControl-27519.test.tsx,预期全部通过。
  • 需要你做的:无,席位落地。

What changes

One type-to-widget resolution for the two boolean types, resolveBooleanControl (new ui/packages/fields/src/widgets/booleanControl.ts, exported from @object-ui/fields), answering 'checkbox' | 'switch':

  1. an explicit widget naming a boolean face wins: checkbox / boolean draw a checkbox, switch / toggle a switch;
  2. otherwise the field's type, through the same four names: boolean draws a checkbox, toggle a switch;
  3. anything else that reaches the boolean widget (the bare field:boolean id of a hand-authored form entry, no type) draws a checkbox, the boolean type's own face.

A widget naming anything else is not a boolean face and leaves the type to answer, which is the spec's FieldSchema.widget degrade.

Readers:

  • BooleanField (the record form's field:boolean widget, and the grid's inline editor through FieldEditWidget's boolean / toggle rows) draws the control the resolution names. It used to draw a switch unless widget === 'checkbox', so the authored boolean was lost.
  • The Studio canvas's FieldControlPreview (in ObjectFormDesigner.tsx; only that function and one import line) previews boolean as a checkbox and toggle as a switch from the same resolution. It used to draw the switch pill for boolean and the empty text box for toggle.

Widget spellings: checkbox and switch are what paramToField and ScreenView stamp and what form.tsx's built-in arms are named; in the form those two still take the built-in checkbox / switch arms before the registry (resolveRenderableWidget, unchanged), and they draw the same control the resolution names. toggle and boolean are accepted as widget names so a widget naming a type means that type's face.

Not changed: field-type-alias.ts keeps both types on the one field:boolean widget (comment updated to say the widget splits them); form.tsx needed no change, since BOOLEAN_WIDGET_TYPES only seeds false for two-state rows and its built-in arms are the explicit widgets above; BooleanCellRenderer (see Acceptance notes).

Changeset: @object-ui/fields and @object-ui/app-shell patch. Clause-②: yes (widening): the published @object-ui/fields surface gains resolveBooleanControl and type BooleanControl; nothing is removed or narrowed. Metadata-protocol: none: what an author can write and what boolean / toggle mean are unchanged (the spec already declares them distinct UIs); the renderer now honours the declared type.

Tests

  • New ui/packages/fields/src/__tests__/booleanControl-27519.test.tsx: the resolution's table (type defaults, the four widget overrides, a non-boolean widget leaving the type to answer, missing input), BooleanField drawing checkbox / switch per type and per widget override, FieldEditWidget drawing the same per type.
  • New ui/packages/app-shell/src/views/studio-design/ObjectFormDesigner.booleanControl-27519.test.tsx: a boolean and a toggle field on the canvas preview a checkbox and a switch, equal to resolveBooleanControl's answer.
  • Updated, not deleted, to the new faces: boolean-label-association-e2e.test.tsx (the form-hosted suite now uses the ObjectForm entry shape, field:boolean plus the object field, as a toggle; the two-field case hosts a boolean checkbox beside a toggle switch; the standalone and host-id cases take toggle for the switch variant and boolean for the checkbox variant), standard-widgets.test.tsx (BooleanField cases cover both types), formPageRequiredMarker-10178.test.tsx and FormPage.sharedWidgets-10179.test.tsx (a boolean row is a checkbox), InlineFieldInput.delegation.test.tsx (boolean routes to a checkbox; the toggle switch case unchanged), RequiredFieldsDialog.ariaInvalidText-7126.test.tsx (one checkbox and one switch carry aria-invalid). field-type-coverage.test.ts needed no change and is green.

Ablation (scripts/ablation-replace.mjs, the resolution's return replaced by the old widget === 'checkbox' ? 'checkbox' : 'switch', anchor 1 to 0, restored to the HEAD blob with git diff HEAD empty): the two new files and the label-association file go red, 13 of 28 cases; the canvas case is among them, which shows the canvas reads the shared resolution.

Acceptance notes

  • Record detail "empty circle". Not the same collapse, and not reproduced in a browser here. An editable boolean row on the record page draws BooleanCellRenderer's disabled checkbox (DetailSection, "An editable boolean keeps the checkbox"), whose primitive is a square (rounded-sm) at disabled:opacity-50; a value that is not a boolean draws the em-dash EmptyValue. The only round boolean face in the tree is the completion indicator, gated on the names completed / is_completed / done / is_done, which Urgent is not. Recorded as a finding for the seat, unfiled.
  • Grid and detail cells. BooleanCellRenderer draws a disabled checkbox for both boolean and toggle; a toggle cell therefore reads as a checkbox in the grid. The ruling names the canvas and the runtime form, so the read-only cell face is left as it is; whether a toggle cell should draw a disabled switch is the seat's call.
  • Canvas and an explicit widget. FieldControlPreview is handed the field's type only (its caller in SortableField passes type={type}), so the canvas previews by type; an authored widget override would need that one caller line, outside the function agreed with seat 3.
  • paramToField and ScreenView still stamp widget: 'checkbox' on boolean params; with the type default now a checkbox the stamp is redundant but harmless, and both files are outside this card.

Verification

Targeted tests and typecheck
  • Ten boolean-related files (the two new ones and the eight above): Test Files 10 passed (10), Tests 227 passed (227).
  • pnpm --dir ui exec vitest run --maxWorkers=2 packages/fields/ packages/plugin-detail/ packages/plugin-kanban/ packages/plugin-form/ packages/components/src/renderers/form/: Test Files 989 passed | 2 skipped (991), Tests 10835 passed | 43 skipped (10878) (before the origin/main merge).
  • After merging origin/main (head 302bcb25, closure rebuilt): packages/components/src/renderers/form/, packages/plugin-form/, packages/app-shell/src/views/studio-design/ and the ten boolean files: Test Files 555 passed (555), Tests 4955 passed | 18 skipped (4973).
  • type-check for @object-ui/fields, @object-ui/app-shell, @object-ui/plugin-detail, @object-ui/plugin-kanban: exit 0.
  • eslint --no-inline-config on the 13 changed ui files: 0 errors (warnings only, pre-existing any).
  • pnpm check:new-line-citations: 0 new citations.
Derived gates

node scripts/pm/dispatch-gates.mjs --commands on head 302bcb25: 34 families, each run with its exit code recorded, all exit 0 (including node scripts/check-sdui-manifest.mjs, pnpm check:type-check-debt under the verify lock, and pnpm --dir ui exec vitest run scripts/: 169 files passed). --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN.

Current head f2a547f9 adds one commit on top of 302bcb25 that changes only the changeset's Clause-② line; on it the changeset families were re-run, all exit 0: check-adr-0087-registration --base origin/main, check-changeset-no-major --base origin/main, check-empty-changeset --base origin/main, check:changeset-gate-self-tests, check:pm-changeset-deadline-census, check:closed-ceremony-text, check:nul-bytes. The tests above were not re-run for it (no code or test line moved).


Generated by Claude Code

…vas and in the form

One type-to-widget resolution, resolveBooleanControl, read by BooleanField
(record form, inline edit) and the Studio canvas preview.

Claude-Session: https://claude.ai/code/session_01Utp6CY2n1JmZZKrHgtp9jC
Co-authored-by: Claude <noreply@anthropic.com>
…nd canvas; changeset

Claude-Session: https://claude.ai/code/session_01Utp6CY2n1JmZZKrHgtp9jC
Co-authored-by: Claude <noreply@anthropic.com>
…, inline edit and dialogs

Claude-Session: https://claude.ai/code/session_01Utp6CY2n1JmZZKrHgtp9jC
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Oct 29, 2026
…ject-ui/fields exports

Claude-Session: https://claude.ai/code/session_01Utp6CYXVQFhXabW3ZQcxWt
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

Contract review

Served-tier: TIER_DEFAULT
Head-sha: f2a547f93433efb1f3c77c044bb3816a2e933284
Local-runs: none

Face: only the Clause-②: yes (widening) declaration, which appears in the PR body, the changeset and the corrected claim 6499211485. None of the PR's fourteen files is under packages/spec/** or a governed path, so no CONTRACT_REVIEW_TIER face applies.

① Derived judgments

  • Widened, now correctly declared: @object-ui/fields adds two exports, resolveBooleanControl(field) and type BooleanControl ('checkbox' | 'switch'), via export { resolveBooleanControl, type BooleanControl } from './widgets/booleanControl.js' in index.tsx. That grows the public surface, so yes (widening) is the correct line. The first head declared no. The seat corrected it from the diff, and the dev's commit f2a547f9 brought the changeset and the body into line.
  • No existing export changed shape. mapFieldTypeToFormType still maps both boolean and toggle to field:boolean; only its comment changed. BooleanField's props are unchanged.
  • Behaviour change, disclosed: a boolean field without an explicit widget now draws a checkbox where it drew a switch. This is triage 6495088373's ruling ("boolean draws a checkbox and toggle draws a switch. No per-surface override"). The changeset tells authors how to keep a switch (toggle, or widget: 'switch').
  • One map: BooleanField (the record form and the inline FieldEditWidget) and Studio's FieldControlPreview both read resolveBooleanControl, so there is no second copy on the canvas.
  • Metadata-protocol: none holds. Authors write the same keys, and boolean / toggle are existing spec types read as declared.

② Semver level

patch on @object-ui/fields and @object-ui/app-shell. This follows the repo's rule that the level follows Metadata-protocol: (kind none ⇒ patch). The changeset body names the two new exports and the visible default change.

③ Boundary flags

  • There are no open_questions. The seat accepted these deviations: one import line in ObjectFormDesigner.tsx beside FieldControlPreview (seat 3 agreed), and four existing tests outside the surface updated from switch to checkbox for boolean rows. None was deleted.
  • The dev noted these unfiled findings, which the seat carries:
    • BooleanCellRenderer draws a checkbox for toggle cells.
    • The canvas previews by type alone.
    • The detail page's "empty circle" was not reproduced.
  • The PR author is the relay (objectstack-fleet[bot]).

Implemented-by: claude/project-thread-6ybbdj
Reviewed-by: session_01Utp6CY2n1JmZZKrHgtp9jC

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

席位核对

ACCEPT · head f2a547f93433efb1f3c77c044bb3816a2e933284 · 2026-10-29T17:09Z

  1. Shape: draft → main, Fixes #27519 only, attribution block on top, 14 files. No assignee, no reviewer.
    • fields: booleanControl.ts (new), BooleanField.tsx, index.tsx (one export line), and field-type-alias.ts (a comment).
    • app-shell: ObjectFormDesigner.tsx (FieldControlPreview plus its import).
    • New tests beside them, four updated tests, and one changeset.
    • The attribution link points at the card thread inside the same project; the dev's harness requires that form.
  2. Premise measured: the seat read the diff.
    • resolveBooleanControl is the one map. An explicit widget of checkbox/boolean or switch/toggle wins. Otherwise a toggle type gives a switch and a boolean type gives a checkbox, and anything else falls back to checkbox.
    • BooleanField (record form and inline edit) and Studio's FieldControlPreview both read it. toggle no longer falls to the canvas's text box.
    • This is triage 6495088373's "one type-to-widget map, shared by the canvas and the runtime form". No packages/spec/** path.
  3. Gates (check-runs on this head): 46 success, 13 skipped, 0 red.
    • Named runs: Lint & Repo Gates 123600772741, TypeScript Type Check 123599264938, Test UI 123604707268, Lint · PM dispatch-gates 123596920073.
    • The dev's derived gates were 34/34 exit 0 on 302bcb25. The only commit after it, f2a547f9, changes the changeset line alone, and its changeset gate families exit 0.
  4. Contract review: declaration face, TIER_DEFAULT, PASS on this head (comment 6502998669). The seat corrected the claim's Clause-② line to yes (widening) (6499211485), because @object-ui/fields gains resolveBooleanControl and type BooleanControl.
  5. Accepted deviations / out of scope:
    • Accepted: the import line beside FieldControlPreview, and four existing tests outside the surface updated from switch to checkbox for boolean rows. None was deleted.
    • Carried by the seat, unfiled until reproduced: BooleanCellRenderer draws a checkbox for toggle cells; the canvas previews by type only; the detail page's "empty circle".
    • The changeset matches the diff, including the visible default change and how to keep a switch.
  6. Landing: not governed (check-governed-merges --pr 27665: NOT governed, 0 of 14 paths), 362 lines < 3000. The seat lands it: ready + auto-merge now.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 29, 2026 17:10
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit 50d05cb Oct 29, 2026
60 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-6ybbdj branch October 29, 2026 18:16
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 size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A field authored in Studio as "Checkbox — shown as a checkbox" renders as a toggle switch on the Studio canvas and in the runtime create form

1 participant