You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 5ad8183
Browse filesBrowse the repository at this point in the historyBrowse files
refactor(plugin-designer): the dashboard designer picks with the shared Select (objectui#11865) (#12011)
Part of #11865
Clause-②: no
This is slice i of claim `6069665700` on objectui#11865: the dashboard
designer. `DashboardEditor`'s widget property panel picks a widget's
Type and Color Variant, and `PropertyEditor` draws a `select` field,
with the shared `Select` (`packages/components/src/ui/select`) instead
of a browser-native select element. The card stays open for the other
native selects; slices h and j of the same claim are separate PRs.
## What changed
- `packages/plugin-designer/src/DashboardEditor.tsx`: the Type and Color
Variant pickers go through one module-private `WidgetPropPicker`,
modelled on the earlier slices' pickers (objectui#11976 `RulePicker`,
#12005 `CreatePicker`).
- Items carry their option's index, not its value, so the picked option
is resolved against the list that rendered the items.
- An option may be disabled, as a native option element was: the Type
picker still disables the types the widget door refuses the widget's
measures under (objectui#8894), and the write guard beside it is kept.
- A stored value no option carries gets an item of its own, labelled
with the value.
- `id` lands on the trigger, so each label `htmlFor` still names its
control.
- The colour options are a typed list (`COLOR_VARIANTS`), so a pick
reaches `onChange` as the widget's own `colorVariant` type, with no cast
of a DOM string. The value written is the same string.
- `packages/plugin-designer/src/components/PropertyEditor.tsx`: a
`select` field goes through one module-private `PropertySelect`, the
same index-token pattern, so an option whose value is empty needs no
empty `SelectItem`. The value is matched as the native control matched
it: the field's value as a string, empty when it has none.
- New pins: `DashboardEditor.sharedSelect-11865.test.tsx` (31 tests) and
`PropertyEditor.sharedSelect-11865.test.tsx` (13 tests).
- Three existing suites drove the native controls and now pick through
the trigger, with their assertions unchanged in meaning:
- `DashboardEditor.measureDoor-8894`: it reads the options off the
opened list (each item carries its type as `data-option-value`, a
disabled one says so with `aria-disabled`), and a pick of a refused type
still writes nothing;
- `DashboardEditor.i18nTitle` and
`DashboardEditor.slotComponentArm-11598`: one colour pick each.
- `.changeset/11865-i-dashboard-editor-shared-select.md`:
`@object-ui/plugin-designer: patch`.
No export, prop or i18n key moves: `DashboardEditorProps`,
`PropertyEditorProps` and `PropertyField` are unchanged, and
`packages/components/src/ui/**` is not edited.
## The dispatch's hypotheses, measured
- **H1, value parity: holds.** A scratch probe (uncommitted, deleted
before the first commit) fired a change event on every native option on
base `902fbe190` and read what `onChange` received. The new pins compare
each row as JSON text against those literals:
- Type: 5 options on a one-measure widget, 5 on a two-measure widget. On
the two-measure widget KPI Metric and Pie Chart are disabled and write
nothing, on base as now.
- Color Variant: 8 options on a widget with none set, 3 on a `teal`
widget. Default writes `colorVariant: "default"`, as before.
- `PropertyEditor`: 6 rows, the empty-value option included (it writes
`onChange("kind", "")`).
- Caveat: the probe's change event fired for the current option too,
which a browser's native select does not do, so the re-pick rows
("writes nothing") pin the primitive.
- **H2, names, keyboard and read-only.**
- Names: Type and Color Variant are found by `getByRole('combobox', {
name })`, read on base the same way. `PropertyEditor`'s field had no
accessible name on base (its caption is a sibling label that names no
control), and it keeps none, as the dispatch asks.
- Keyboard: Enter opens a picker and Enter on an option selects it,
pinned on both surfaces.
- Read-only: `DashboardEditor`'s `readOnly` disables both triggers. They
wear the primitive's own `disabled:` classes and do not open, with a
writable CONTROL. `PropertyEditor` has no read-only state (its props and
fields declare none).
- **H3, a stored value outside the options: holds.** A stored `area`
type shows `area`, where base showed "KPI Metric". A `metric-card` entry
shows `metric-card` (base: "KPI Metric"). A stored colour outside the
list is shown (base: "Default"). A `PropertyEditor` value outside its
options is shown (base: the first option), and a field with no value and
no empty option shows nothing (base: the first option). Re-picking the
outside item writes nothing.
- **H4, first-load bytes: byte-neutral.** It was measured before the
first edit: base `902fbe190`, `apps/console` `CI=true vite build` under
the lock, then `dist/eager-closure.json`.
- Both files ship in the lazy chunk `src-Bg0yZV49.js`, which the entry
chunk reaches only through `lazy(() => import(...))`. It is not among
the 336 eager members.
- Base reads eagerGzipBytes 3391326, eagerRawBytes 11496190.
- Head `56dea3f5e` reads 3391325 and 11496190, with the same 336 eager
chunks: 0 raw bytes and −1 gzip byte. The one eager chunk that moves is
the entry chunk: identical raw bytes, with the lazy designer chunk's
hashed name inside it changed. The designer code ships in the lazy chunk
`src-kZPNwCPn.js` on head.
- `pnpm check:eager-closure` on head: "Console eager closure is 3311.8
KB gzipped across 336 of 2458 chunks (budget: 3312.0 KB, headroom: 0.2
KB)."
- The budget bot's reading on this PR is for the seat to quote after CI.
## Evidence on `56dea3f5e`
- Repo-root `pnpm exec vitest run --maxWorkers=2
packages/plugin-designer/
packages/core/src/utils/__tests__/column-identity.ratchet.test.ts
scripts/__tests__/one-authority-per-exported-name-6273.test.ts`: "Test
Files 47 passed (47)", "Tests 369 passed (369)". That is every
plugin-designer suite (45 files: everything that mounts
`DashboardEditor`, `DashboardDesignPage` or `PropertyEditor`) plus the
two repo-wide pins. Lock VERDICT command-exit 0.
- `pnpm --filter @object-ui/plugin-designer type-check` (echoed `tsc
--noEmit && tsc -p tsconfig.test.json`), after the
`@object-ui/plugin-designer^...` build (Scope: 17 of 47): VERDICT
command-exit 0. By `--listFilesOnly`, both sources are in the main
program and all five touched suites are in the test program.
- `pnpm exec eslint --format json` on the 7 touched files: 0 errors, 4
warnings. All 4 are `react-hooks/exhaustive-deps` in `DashboardEditor`'s
untouched main component, and the base blob gives the same 4 via
`--stdin`.
- `node scripts/check-changeset-presence.mjs`: "7 source file(s) of 1
released package(s) changed, and this change declares 1 changeset(s)".
`node scripts/check-changeset-no-major.mjs`: exit 0.
- `pnpm check:new-line-citations`: "0 new citation(s)". `pnpm
check:control-bytes`: OK.
- Derived by hand from objectui's `package.json` and workflows, all exit
0:
- `check-changeset-fixed`, `check-changeset-overwrite`,
`check-changeset-claims`, `check-lint-coverage`,
`check-type-check-coverage`;
- `check:pending-changeset-literals`, `check:test-path-roots`,
`check:vi-mock-specifiers`, `check:vi-mock-inherit`,
`check:vi-mock-override-shape`, `check:phantom-deps`,
`check:unused-deps`, `check:unreferenced-sources`,
`check:esm-specifiers`, `check:self-import`,
`check:designer-field-key-parity`, `check:metadata-write-doors`,
`check:handler-key-reads`, `check:i18n-keys`;
- `check:component-surface-parity` (report-only).
- Not measured locally: `check:readme-exports` needs every package's
dist and stopped on that prerequisite. This diff touches no README and
no export. Repo-wide `pnpm lint` and the full test farm are CI's.
**Reverse leg.** The implementation was committed first (`6a01f72f3`).
Both source files were then written from their base blobs: `08bce46f`
and `776f26e8`, landing proven by hash, with 2 and 1 native selects and
0 pickers. The new pins and the three converted suites read "Tests 48
failed | 12 passed (60)", VITEST-EXIT 1.
- The green 12: both name pins, the writable CONTROL, and the nine
untouched cases of the converted suites. That is the predicted
direction.
- `56dea3f5e` corrected the `DashboardEditor` pin's header, which had
named the name pin alone.
- The trap restored with `git checkout HEAD --` on absolute paths.
Hashes `99f0f1a3` and `24606bf0` equal the HEAD blobs; `git diff HEAD`
was 0 bytes and the status had 0 entries.
**ProcessDesigner markup.** `ProcessDesigner.readers-11434` compares
drawn markup with React ids normalised, and its property panel now draws
a Radix trigger. A scratch probe (deleted) rendered one document twice
with the panel open: raw markup equal, no generated id in it. That suite
needs no change, and it passes in the run above.
## Acceptance notes
- `carrier: none`: `PropertyEditor`'s field caption is a label beside
the control that names no control, so a `select` field's trigger has no
accessible name, on base as now. Measured with testing-library's name
computation. This is a11y polish with no public door measured.
- `carrier: none`, read-only inference: the Type picker is offered on a
`metric-card` entry too, and a pick writes `type` onto that entry.
Whether the resulting entry is refused at publish was not measured. This
PR leaves that write path as it was.
- Browser: NOT MEASURED in this slice. Keyboard and read-only are pinned
in the test DOM only.
Commits carry objectui `AGENTS.md`'s model-free trailer pair. Session:
`https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
The dashboard designer's widget property panel picks a widget's Type and Color Variant with the shared `Select`, and the designers' property panel (`PropertyEditor`) draws a `select` field with it too: the control the rest of the console picks with (objectui#11865, the dashboard designer's part of that card).
6
+
7
+
The three pickers were browser-native selects, so they looked and behaved differently from the console's other dropdowns. They now use the shared Radix `Select`: the same trigger, dropdown and keyboard behaviour.
8
+
9
+
What they write is unchanged. Each option hands the panel the same value as before: a type pick still writes `type`, a colour pick still writes `colorVariant`, and a `select` field still calls `onChange` with the field's name and the option's value, an option whose value is empty included. A type the widget door refuses the widget's measures under is still a disabled option and still writes nothing. Re-picking the current option writes nothing. The Type and Color Variant pickers keep the accessible name their labels gave the native selects; a `select` field's caption named no control before and names none now. Read-only disables the Type and Color Variant triggers in the shared `Select`'s own disabled look; `PropertyEditor` has no read-only state, as before.
10
+
11
+
One display change: a value none of a picker's options carries now shows as itself (a stored `area` type, a `metric-card` entry's type, a field value no option carries). The native select showed its first option instead ("KPI Metric", "Default"), which is not what the widget or the field holds. A `select` field with no value and no empty option shows nothing rather than its first option.
12
+
13
+
**Clause-②: no.** No published face moves: the package entry exports the same names, `DashboardEditorProps`, `PropertyEditorProps` and `PropertyField` take the same members, and no i18n key is added. What moves is the panels' own markup, described above: a test that drove one of these controls as a native select now picks through the trigger.
0 commit comments