|
| 1 | +--- |
| 2 | +"@objectstack/spec": major |
| 3 | +--- |
| 4 | + |
| 5 | +refactor(spec)!: retire `dashboard.widgets[].responsive` — the straggler of the #3896 inert-key sweep (#4876, ADR-0049) |
| 6 | + |
| 7 | +`DashboardWidgetSchema.responsive` let an author declare per-breakpoint layout |
| 8 | +overrides on a dashboard widget — `breakpoint`, `hiddenOn`, `columns`, `order` — |
| 9 | +and no renderer ever read them. The value parsed, validated, and then did |
| 10 | +nothing: `DashboardRenderer`, `DashboardEditor` and `plugin-designer` name |
| 11 | +`responsive` only in comments, and the one genuine per-breakpoint consumer in |
| 12 | +objectui (`useResponsiveConfig`) is fed by `page.components[].responsive`, never |
| 13 | +by a widget. Re-measured 2026-08-03 across both repos, plus zero authored |
| 14 | +instances anywhere in this repo's examples, apps and tests. |
| 15 | + |
| 16 | +Four days earlier, #3896 retired the **literally same-named** `view.responsive` |
| 17 | +on exactly this evidence. This embed survived that sweep for a reason that is |
| 18 | +worth stating plainly, because it is not "we looked and it was live": the |
| 19 | +liveness ledger declares no `children` on `dashboard.widgets`, and the walk |
| 20 | +drills only one level through an explicit `children` — so **no widget-level key |
| 21 | +has ever been classified at all** (22 of them). The instrument had a hole, not |
| 22 | +the key a mandate. That gap is filed and fixed separately as **#4956**. |
| 23 | + |
| 24 | +Leaving it would have shipped v17 with one word and two fates — `view.responsive` |
| 25 | +a `tsc` error, `dashboard.widgets[].responsive` silently accepted — which no |
| 26 | +author or authoring agent could be expected to explain, on a key that today |
| 27 | +accepts *any* content on both sides (objectui types it a documented `any`). That |
| 28 | +is precisely where AI-authored metadata errors hide and multiply. |
| 29 | + |
| 30 | +FROM → TO: |
| 31 | + |
| 32 | +| Removed | Replacement | |
| 33 | +| :--- | :--- | |
| 34 | +| `dashboard.widgets[].responsive` (key) | **none** — delete it; the grid reflows by `columns` + `gap` on the dashboard and the `layout` box on each widget | |
| 35 | + |
| 36 | +**The shape is NOT removed — only this embed.** `ResponsiveConfigSchema` / |
| 37 | +`ResponsiveConfig` stay exported and stay live on `page.components[].responsive`, |
| 38 | +whose renderer genuinely reads them. Nothing that imports the shape breaks, and |
| 39 | +an author who needs breakpoint behaviour today has a real place to put it. This |
| 40 | +narrowness is deliberate: the maintainer's ruling covers the dashboard widget |
| 41 | +surface only. |
| 42 | + |
| 43 | +The retirement kit: |
| 44 | + |
| 45 | +- **Tombstone.** `retiredKey()` on the widget key. `DashboardWidgetSchema` *is* |
| 46 | + `.strict()`, so a plain delete would still be loud — but only as a generic |
| 47 | + "unrecognized key". The tombstone keeps the key declared so the rejection |
| 48 | + carries the **prescription**, and types the key `never` so authoring it fails |
| 49 | + `tsc` first. A pin asserts the message is the prescription and *not* |
| 50 | + `Unrecognized key`. |
| 51 | +- **ADR-0087 D2 conversion + D3 chain step** |
| 52 | + (`dashboard-widget-responsive-removed`, `retiredFromLoadPath`): |
| 53 | + `os migrate meta --from 16` deletes the key from author sources, and stored |
| 54 | + dashboards replay clean instead of meeting the tombstone at load. A lossless |
| 55 | + delete — the key never had an effect to lose. Kept as its own entry rather than |
| 56 | + folded into `dashboard-inert-keys-removed`, whose identity is the #3896 sweep: |
| 57 | + this removal rests on its own 2026-08-03 measurement and should say so in |
| 58 | + `spec-changes.json` and the upgrade guide. |
| 59 | +- **No liveness row is added**, matching `widgets[].performance` in the #3896 |
| 60 | + sweep — a widget-level row would be an ORPHAN, not a classification, until |
| 61 | + #4956 lands the drill. The ledger `_note` records the removal and why the row |
| 62 | + is absent. |
| 63 | +- Baselines moved at KEY level only, as the shape's survival implies: |
| 64 | + `authorable-surface.json` gains `ui/DashboardWidget:responsive [RETIRED]`; |
| 65 | + `json-schema.manifest.json`, `api-surface.json` and |
| 66 | + `api-surface-signatures.json` are unchanged by construction — no def stopped |
| 67 | + being emitted and no export was removed. |
| 68 | + |
| 69 | +No runtime behaviour changes — that impossibility is the reason for the removal. |
| 70 | + |
| 71 | +**objectui shard:** the outcome is retirement, not the fallback clause, so |
| 72 | +objectui#3235's conditional pin-bump item is permanently de-listed; the `any` |
| 73 | +declaration on that repo's side can be cleaned on its own schedule. |
0 commit comments