Repository navigation
Commit 6e9c8d2
fix(types,app-shell,plugin-dashboard,plugin-designer): the widget width / height editors write a whole four-number layout (objectui#11388) (#11398)
Fixes #11388
Clause-②: yes
The three dashboard widget width / height editors now write a whole
four-number `layout` through one shared helper, and the casts past
`DashboardWidgetSchema['layout']` are gone, so the compiler judges each
write.
## What changed
- **`@object-ui/types`** gains two exports on its main entry, in a new
zod-free module `dashboard-widget-layout.ts`:
- `defaultWidgetPlacement(index)`: the box the dashboard grid
auto-places a widget with no `layout` in. It is the fallback the spec
states in the doc comment of its widget `layout` member: `x: (i % 4) *
3`, `y: Math.floor(i / 4) * 4`, `w: 3`, `h: 4`.
- `completeWidgetLayout(layout, patch, placement)`: the whole box after
an edit. Each coordinate comes from the edit, else from the stored
`layout`, else from the placement.
- **The three writers** all call it, with
`defaultWidgetPlacement(index)` as the placement:
- app-shell, Studio's `DashboardWidgetInspector` (width and height
inputs);
- plugin-dashboard, `DashboardWithConfig`'s live writer
(`handleWidgetFieldChange`, for `layoutW` / `layoutH`);
- plugin-designer, `DashboardEditor`'s `WidgetPropertyPanel`, which now
takes the widget's `index`.
- **`DashboardGridLayout`**'s `buildDefaultLayouts` places a widget with
no `layout` through the same `defaultWidgetPlacement`. Where widgets
land does not change. The grid's default is now stated once, and the
editors seed from that same statement instead of a copy.
- **What the inputs show.** Each width / height input, and
`DashboardWithConfig`'s two sliders, shows the same completed box the
writer stores. For a widget with no `layout` that box is 3 × 4, where
the inputs used to show 1 × 1. Without this, editing the width would
store `h: 4` while the height input still showed 1. In
`DashboardWithConfig` it would be worse: the slider seed also feeds the
save payload, so the live schema would hold `h: 4` and the saved
`layoutH` would be 1.
- Docs: a "Writing a widget's `layout`" section in the
`@object-ui/types` README (its snippet is judged by
`check:readme-exports`), and one paragraph each in the plugin-dashboard
README and `content/docs/plugins/plugin-dashboard.mdx`. Changeset
`.changeset/11388-widget-layout-complete.md`: types minor, app-shell,
plugin-dashboard and plugin-designer patch.
## Where the helper lives, and the export it adds
I checked each package's `package.json`. The production `@object-ui/*`
deps that all three writers share are `components`, `core`, `i18n`,
`react` and `types`. plugin-designer does not depend on
plugin-dashboard. `@object-ui/types` is the lowest of these (core
depends on it). It already holds the type the helper returns
(`DashboardWidgetLayout`), and it already exports zod-free runtime
helpers from its main entry, so no new dependency edge is added.
Because `@object-ui/types` is published, this is **a new export from a
published package's entry point**. Under the claim's own condition, its
`Clause-②: no` goes back to the seat to re-judge, so this body carries
no `Clause-②` line. The new module stays clear of
`packages/types/src/zod/objectql.zod.ts` (objectui#11276). The only
other `types` file touched is one export line in `src/index.ts`, which
that branch does not touch.
## What each writer can actually see
No writer can see a react-grid-layout placement:
- Studio's preview and `DashboardWithConfig` both render
`DashboardRenderer`, which places widgets by span and array order and
computes no `x` / `y`.
- `DashboardEditor`'s own grid places widgets by array order.
So all three seed from `defaultWidgetPlacement(index)`. That is exactly
where `DashboardGridLayout` puts a widget with no `layout`. Each writer
says this in a comment at the call.
## Measurements
- **Premise** (`@objectstack/spec` 17.5.0, installed): `layout: { w: 6
}` → REFUSE, `invalid_type` at `layout.x`, `layout.y`, `layout.h`. `{ h:
3 }` → refused at `x`, `y`, `w`. Four numbers → ACCEPT. No box → ACCEPT.
- **Red through each real writer.** The three writer files were checked
out from base `5ad9f5dc` with the new pins kept, all inside an `EXIT INT
TERM` trap. Result: `Tests 8 failed | 3 passed`. The three that pass are
the controls, which hold on both trees. The stored boxes were `{ w: 6 }`
(Studio), `{ h: 2 }`, and `{ w: 2 }` (the `DashboardWithConfig` slider,
seeded at 1). The inputs showed `'1'`. Restored with `git checkout
HEAD`, then confirmed: `git diff HEAD` empty and all three blob hashes
equal HEAD.
- **Green:** the four pin files, `Test Files 4 passed (4)`, `Tests 18
passed (18)`.
- **Reverse type check.** I put the cast-free spread back into
`DashboardEditor` (`{ ...widget.layout, w: … }`) and ran `tsc --noEmit`:
exit 2, `TS2322: Type '{ w: number; x?: number | undefined; y?: number |
undefined; h?: number | undefined; }' is not assignable to type
'DashboardWidgetLayout'`. So the cast was what hid the write. Restored
and checked by hash.
- **Browser, through the real Studio inspector.** Console preview
gallery (`?only=dashboard`), Chromium, no backend. I clicked the
`kpi_revenue` widget (it has no `layout`), filled Width = 6, and read
the stored draft:
- this branch: inputs `3` / `4` before the edit; stored `layout: { x: 0,
y: 0, w: 6, h: 4 }`; the renderer drew `gridColumn: span 6`, `gridRow:
span 4`; parsing with the spec gives no `layout.*` issue, and the box
parses on a spec-valid widget.
- unfixed control (base inspector, same driver): inputs `1` / `1`;
stored `layout: { w: 6 }`; the renderer drew `gridRow: span undefined`;
the spec refuses with `invalid_type` at `layout.x`, `layout.y`,
`layout.h`.
- **NOT MEASURED: the server leg** (Studio save → reload → validate
against a backend). The sibling `objectstack` checkout in this container
has no install and no build, and standing up its dev server was outside
this run's foreground and shared-box budget. The browser legs above
measure the same stored draft up to the point where it would be sent.
## Gates
Toolchain: Node v22.22.2 from `/opt/nvm` via `PATH`. A plain `pnpm
install` passed engine-strict, with no override.
All gates below were read on head `a831be79` (main `d59f11c0` merged
in).
- **Build:** `turbo run build --filter='@object-ui/app-shell^...'
--concurrency=2`, 28/28 tasks. This closure contains types,
plugin-dashboard and plugin-designer. I used turbo instead of `pnpm
--filter` so a capped run could resume from cache. The other unbuilt
packages were then built so `check:readme-exports` could judge every
README.
- **`type-check`** for types, plugin-dashboard, plugin-designer and
app-shell: all `Done`, exit 0. Each package's `tsconfig.test.json` lists
its new pin (`--listFilesOnly`). The consumers read the rebuilt
`@object-ui/types` dist that carries both exports.
- **vitest:**
- `packages/plugin-dashboard/`, `packages/plugin-designer/`, and the
outside suites that import a touched module: `Test Files 196 passed
(196)`, `Tests 1733 passed | 6 skipped`. The outside suites are
schema-catalog `dashboard-root-title-retired-7624`, components
`inline-locale-label-read-sites`, two plugin-charts dashboard suites,
and sdui-parser `dashboard-widget-options-census`.
- types `dashboard-*` plus the export-surface and parity pins, and
app-shell's `Dashboard*` inspector / preview suites plus every inspector
suite that imports `DashboardWidgetInspector`: `Test Files 31 passed
(31)`, `Tests 486 passed (486)`.
- A first attempt at the whole `packages/types/` plus all of app-shell
`metadata-admin/` hit the 530 s cap (exit 124). It measured nothing and
was narrowed to the set above. CI runs the full set.
- **Gates, exit 0 each:** `check:new-line-citations` (`0 new
citation(s)`), `check:control-bytes`, `check-changeset-presence` (`11
source file(s) of 4 releas…`, 1 changeset added), `changeset:check`,
`check-changeset-overwrite`, `check-changeset-claims`,
`check:readme-exports` (`561 real, 0 wrong-path, 0 fabricated`, 0
unbuilt), `check-vi-mock-specifiers` / `-inherit` / `-override-shape`,
`check-test-path-roots`, `check:doc-examples`,
`check-doc-snippet-types`, `check-doc-links`,
`check-doc-fence-languages`, `check:unreferenced-sources`,
`check:self-import`, `check:esm-specifiers`, `check:phantom-deps`,
`check-type-check-coverage`, `check:eager-closure`.
- Before the merge, these were also exit 0: `check:doc-example-ids`,
`check:doc-example-readers`, `check-doc-component-types`,
`check-doc-expression-carriage`, `check-lint-coverage`,
`check:published-dist`, `check:side-effects-array`, `check:entry-guard`,
`check:spec-symbols`.
- `check:node-esm-load` exited 1 on provenance: it refused two entries
the shared turbo cache replayed from other worktrees (`@object-ui/auth`,
`@object-ui/react-runtime`). Neither is touched here, and both are NOT
MEASURED. Its load leg imported 32 of 39 entries, including
`@object-ui/types` and `@object-ui/plugin-designer`. app-shell and
plugin-dashboard are unbundled-Node unsupported by ruling.
- **Lint, narrowed to the changed files.** `eslint --format json` on the
11 changed `.ts` / `.tsx` files: 11 results, 0 errors, 31 warnings. The
warnings are the same per-file counts base `5ad9f5dc` gives the five
modified files. The two new files add none.
- Population: each package's `lint` is `eslint .` under the root
`eslint.config.js`.
- Why the narrowing cannot change another file's verdict: that config
enables no type-aware linting (no `parserOptions.project` /
`projectService`), and no rule in `eslint-rules/` reads the filesystem.
The full `pnpm lint` is CI's.
- Governed surface: `check-governed-queue-guard --test` over the 15
changed paths → NOT GOVERNED.
## Acceptance notes
- **Two renderers, two defaults.** In the positioned grid,
`DashboardRenderer` gives a widget with no `layout` a span of its own: 3
× 2 for a metric, 6 × 4 otherwise. That is not the spec-stated
auto-placement (3 × 4) that `DashboardGridLayout` uses and that the
editors now seed from, though its comment says it matches. One visible
effect in Studio: after a width edit, a metric that had no `layout` gets
`h: 4`, where the preview had drawn it 2 rows tall. Not changed here.
Carrier: none.
- **`DashboardWithConfig`'s save payload** is the documented flattened
`layoutW` / `layoutH` with no `x` / `y`, and the host un-flattens it.
Neither objectui nor objectstack has a host for it. The shape is
unchanged. For a widget with no `layout` that the author did not resize,
the values are now 3 / 4 where they were 1 / 1. Carrier: none.
- `DashboardEditor`'s `addWidget` seeds its own complete box (`x: 0`,
`y` = index, `w` = `columns ?? 2`, `h: 1`). It is spec-valid and not
changed here.
- One existing pin was touched. The map-title round trip in
`DashboardWidgetInspector.test.tsx` used "height = 4" as its unrelated
edit. The input now shows 4 for a widget with no `layout`, so that edit
fired no change. It now edits 2, with a comment saying why.
Session: `https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42` (seat
`domain:ui#2`, claim on the card).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent d59f11c commit 6e9c8d2
15 files changed
Lines changed: 672 additions & 30 deletions
File tree
- .changeset
- content/docs/plugins
- packages
- app-shell/src/views/metadata-admin/inspectors
- plugin-dashboard
- src
- __tests__
- plugin-designer/src
- __tests__
- types
- src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
422 | 422 | | |
423 | 423 | | |
424 | 424 | | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
425 | 432 | | |
426 | 433 | | |
427 | 434 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
385 | 385 | | |
386 | 386 | | |
387 | 387 | | |
388 | | - | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
389 | 391 | | |
390 | 392 | | |
391 | 393 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
33 | 34 | | |
34 | 35 | | |
35 | 36 | | |
| |||
196 | 197 | | |
197 | 198 | | |
198 | 199 | | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
199 | 204 | | |
200 | 205 | | |
201 | 206 | | |
| |||
488 | 493 | | |
489 | 494 | | |
490 | 495 | | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
491 | 507 | | |
492 | 508 | | |
493 | 509 | | |
494 | 510 | | |
495 | 511 | | |
496 | 512 | | |
497 | | - | |
| 513 | + | |
498 | 514 | | |
499 | 515 | | |
500 | | - | |
501 | | - | |
502 | | - | |
503 | | - | |
| 516 | + | |
504 | 517 | | |
505 | 518 | | |
506 | 519 | | |
| |||
511 | 524 | | |
512 | 525 | | |
513 | 526 | | |
514 | | - | |
| 527 | + | |
515 | 528 | | |
516 | 529 | | |
517 | | - | |
518 | | - | |
519 | | - | |
520 | | - | |
| 530 | + | |
521 | 531 | | |
522 | 532 | | |
523 | 533 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
601 | 601 | | |
602 | 602 | | |
603 | 603 | | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
604 | 610 | | |
605 | 611 | | |
606 | 612 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
| |||
107 | 108 | | |
108 | 109 | | |
109 | 110 | | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
110 | 117 | | |
111 | 118 | | |
112 | 119 | | |
113 | 120 | | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
| 121 | + | |
118 | 122 | | |
119 | 123 | | |
120 | 124 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
104 | 105 | | |
105 | 106 | | |
106 | 107 | | |
107 | | - | |
| 108 | + | |
108 | 109 | | |
109 | 110 | | |
110 | | - | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
111 | 117 | | |
112 | 118 | | |
113 | 119 | | |
| |||
127 | 133 | | |
128 | 134 | | |
129 | 135 | | |
130 | | - | |
131 | | - | |
| 136 | + | |
| 137 | + | |
132 | 138 | | |
133 | 139 | | |
134 | 140 | | |
| |||
164 | 170 | | |
165 | 171 | | |
166 | 172 | | |
167 | | - | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
168 | 182 | | |
169 | | - | |
170 | | - | |
171 | | - | |
172 | | - | |
173 | | - | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
174 | 186 | | |
175 | 187 | | |
176 | 188 | | |
| |||
0 commit comments