Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .changeset/page-component-responsive-removed.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
---
"@objectstack/spec": minor
---

feat(spec): retire `page.components[].responsive` and the `ResponsiveConfig` layout vocabulary; repair every shipped text that prescribed it (#11027, ADR-0049 D2)

<!-- adr-0087: registered page-component-responsive-removed -->

**BREAKING** accept-set narrowing, landing after the v17.0.0 cut (the lockstep
launch-window convention ships it as `minor`; the migration prescription is
registered under protocol major 18, where `os migrate meta` users will look).

`page.components[].responsive` was the LAST carrier of the per-breakpoint
LAYOUT block (`ResponsiveConfig`: grid columns / visibility / display order on
the Tailwind `xs…2xl` axis) — and the destination the
`dashboard.widgets[].responsive` tombstone (#4876) prescribed verbatim as the
live alternative ("which objectui `useResponsiveConfig` really does read").
Measured across objectstack + objectui with the tsc-probe methodology
(positive and negative controls; objectui `3b147a367`, objectstack
`8d21f7a76`): that claim was false. objectui's two complete, published
implementations of the contract — `useResponsiveConfig` (`@object-ui/mobile`)
and `ResponsiveProtocol` (`@object-ui/core`) — had ZERO callers, nothing in
either repo read `.responsive` off a page component, objectui's own
`BaseSchema` node interface never declared the key, and zero authored
instances exist. An author following the shipped prescription moved an inert
key to an inert key and was told it now works. Identical disposition to
`view.responsive` (#3896) and `dashboard.widgets[].responsive` (#4876) on
identical evidence.

**What is refused:** an authored `responsive` on a page component. The key is
a `retiredKey()` tombstone, so authoring it is a `tsc` error and a parse error
carrying the prescription.

**What leaves with it:** `ResponsiveConfigSchema` / `ResponsiveConfig`,
`BreakpointColumnMapSchema` / `BreakpointColumnMap`,
`BreakpointOrderMapSchema` / `BreakpointOrderMap`, and the `BreakpointName`
enum — no other authorable carrier existed, and an exported value schema with
no consumer reads as a capability (#3950; the `PerformanceConfigSchema`
precedent). Importing any of them is TS2305 from this release.

**What stays:** `responsiveStyles` (ADR-0065, `ResponsiveStylesSchema` /
`StyleMapSchema`) — the per-breakpoint channel objectui really compiles to
id-scoped CSS — is untouched and is what every repaired text now points at.

The redirect repair (the reason this ships as one change): four author-facing
surfaces shipped the false redirect and are corrected together — the #4876
tombstone prescription (`dashboard.zod.ts`), the generated widget reference
page, the protocol upgrade guide prose, and the protocol-17 migration
rationale that `os migrate meta --from 16` prints. The `dashboard.json`
liveness note and the #4876 conversion summary carried the same claim and are
corrected too; `responsive.zod.ts`'s `hidden → hiddenOn` alias curation, which
justified itself by `useResponsiveConfig`'s return shape, leaves with the
schema that hosted it.

The retirement kit:

- `retiredKey()` tombstone at the schema (`packages/spec/src/ui/page.zod.ts`),
prescription pointing at `responsiveStyles` with the CSS translations for
`columns` / `hiddenOn` / `order`
- ADR-0087 registration: retired-key entry `ui/PageComponent:responsive`, four
retired-def entries (`ui/ResponsiveConfig`, `ui/BreakpointColumnMap`,
`ui/BreakpointOrderMap`, `ui/BreakpointName`), and the D2 conversion
`page-component-responsive-removed` (protocol 18), wired into the step-18
chain — `os migrate meta --from 17` strips the key from authored pages at
every component position, region, slot, or nested container (pure lossless
delete; it never had an effect to lose)
- pin tests (`page.test.ts` — refusal pin asserts the prescription; a positive
pin parses a component without the key and asserts `responsiveStyles`
survives; `dashboard.test.ts`'s #4876 pins now assert the corrected
prescription instead of the false redirect)
- `ResponsiveStylesSchema` guidance for `columns` / `hiddenOn` / `order` now
names the retirement and the CSS that IS applied, instead of prescribing the
dead sibling key
- generated baselines/docs follow the schema (authorable surface, JSON-schema
manifest, api-surface, export-origins, spec-changes, upgrade guide,
reference docs, skill references)
- objectui's two dead consumer implementations are the other half of this
measurement and are queued under objectui#4773 (this package's texts no
longer point authors at them)

## FROM → TO

```ts
// before — parsed green; no renderer ever applied any of it
{
type: 'element:text',
id: 'kpi_label',
responsive: {
columns: { xs: 12, lg: 4 },
order: { xs: 2, lg: 1 },
hiddenOn: ['xs'],
},
}

// after — express per-breakpoint behaviour as scoped CSS (ADR-0065), which
// objectui compiles and applies (desktop-first buckets)
{
type: 'element:text',
id: 'kpi_label',
responsiveStyles: {
large: { gridColumn: 'span 4', order: '1' },
small: { gridColumn: 'span 12', order: '2' },
xsmall: { display: 'none' },
},
}
```
23 changes: 4 additions & 19 deletions content/docs/protocol/objectui/layout-dsl.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -791,22 +791,6 @@ interface FormField {
}
```

### Responsive Columns

{/* os:check */}
```typescript
// Source: packages/spec/src/ui/responsive.zod.ts (BreakpointColumnMapSchema)
// Grid columns (1-12) per named breakpoint. All entries optional.
interface ResponsiveColumns {
xs?: number; // ~480px
sm?: number; // ~640px
md?: number; // ~768px
lg?: number; // ~1024px
xl?: number; // ~1280px
'2xl'?: number; // ~1536px
}
```

### Visibility Rule

Visibility is a single **CEL expression** string, not a structured rule object.
Expand DownExpand Up@@ -844,8 +828,9 @@ error names the offending key and, when it looks like a visibility predicate,
points you back at `visibleWhen`.

Breakpoint-based show/hide is handled separately via the component's
`responsive.hiddenOn` array (e.g. `hiddenOn: ['xs', 'sm']`), see
`packages/spec/src/ui/responsive.zod.ts`.
`responsiveStyles` scoped CSS (ADR-0065), e.g. `xsmall: { display: 'none' }` —
see `packages/spec/src/ui/responsive.zod.ts`. (The former `responsive.hiddenOn`
layout key was retired in v17.x; no renderer ever applied it.)

#### Position-gated visibility

Expand DownExpand Up@@ -1032,4 +1017,4 @@ block. See

- [Page Reference](/docs/references/ui/page) - Page, region, and component schemas
- [View Reference](/docs/references/ui/view) - Form, list, kanban, and calendar views
- [Responsive Reference](/docs/references/ui/responsive) - Breakpoint layout and scoped responsive styles
- [Responsive Reference](/docs/references/ui/responsive) - Per-breakpoint scoped responsive styles (ADR-0065)
10 changes: 5 additions & 5 deletions content/docs/references/index.mdx
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
---
title: Protocol Reference
description: Every schema published by @objectstack/spec — 1586 schemas across 14 protocol modules
description: Every schema published by @objectstack/spec — 1582 schemas across 14 protocol modules
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand DownExpand Up@@ -32,8 +32,8 @@ counts are sums of the rows they head. Regenerate with
| [Shared Protocol](/docs/references/shared) | 8 | 31 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. |
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
| [System Protocol](/docs/references/system) | 36 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
| [UI Protocol](/docs/references/ui) | 16 | 156 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
| **Total** | **199** | **1586** | 14 protocol modules |
| [UI Protocol](/docs/references/ui) | 16 | 152 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
| **Total** | **199** | **1582** | 14 protocol modules |

---

Expand DownExpand Up@@ -365,7 +365,7 @@ The runtime environment — logging, jobs, cache, metrics, notifications, i18n a

## UI Protocol

**Source:** `packages/spec/src/ui/` · **Import:** `@objectstack/spec/ui` · **16 pages, 156 schemas**
**Source:** `packages/spec/src/ui/` · **Import:** `@objectstack/spec/ui` · **16 pages, 152 schemas**

Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer.

Expand All@@ -384,7 +384,7 @@ Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI lay
| [`notification.zod.ts`](/docs/references/ui/notification) | `NotificationPosition`, `NotificationSeverity`, `NotificationType` |
| [`page.zod.ts`](/docs/references/ui/page) | `ElementDataSource`, `InterfacePageConfig`, `Page`, `PageComponent`, `PageComponentType`, `PageRegion`, `PageType`, `PageVariable` |
| [`report.zod.ts`](/docs/references/ui/report) | `JoinedReportBlock`, `Report`, `ReportChart`, `ReportSort`, `ReportType` |
| [`responsive.zod.ts`](/docs/references/ui/responsive) | `BreakpointColumnMap`, `BreakpointName`, `BreakpointOrderMap`, `ResponsiveConfig`, `ResponsiveStyles`, `StyleMap` |
| [`responsive.zod.ts`](/docs/references/ui/responsive) | `ResponsiveStyles`, `StyleMap` |
| [`sharing.zod.ts`](/docs/references/ui/sharing) | `SharingConfig` |
| [`view.zod.ts`](/docs/references/ui/view) | `AddRecordConfig`, `AppearanceConfig`, `CalendarConfig`, `ColumnPrefix`, `ColumnSummary`, `ColumnSummaryConfig`, `FormButtonConfig`, `FormField`, `FormFieldPublicPicker`, `FormSection`, `FormView`, `GalleryConfig`, `GanttConfig`, `GanttQuickFilter`, `GroupingConfig`, `GroupingField`, `HttpMethodSubset`, `HttpRequest`, `KanbanConfig`, `ListChartConfig`, `ListColumn`, `ListMapConfig`, `ListView`, `NavigationConfig`, `NavigationMode`, `ObjectListView`, `ObjectUserFilters`, `PaginationConfig`, `RowColorConfig`, `RowHeight`, `SelectionConfig`, `TimelineConfig`, `TreeConfig`, `UserActionsConfig`, `UserFilterField`, `UserFilters`, `View`, `ViewData`, `ViewFilterRule`, `ViewItem`, `ViewItemName`, `ViewItemWire`, `ViewKind`, `ViewScope`, `ViewSharing`, `ViewTab`, `VisualizationType` |

Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/ui/dashboard.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -110,7 +110,7 @@ Dashboard header action
| **options** | `{ dateGranularity?: Enum<'day' \| 'week' \| 'month' \| 'quarter' \| 'year'>; sortBy?: string; sortOrder?: Enum<'asc' \| 'desc'>; limit?: integer; … } & Record<string, any>` | optional | Widget specific configuration |
| **filterBindings** | `Record<string, string \| false>` | optional | Per-widget dashboard-filter bindings: filter name → this widget's field, or false to opt out |
| **suppressWarnings** | `string[]` | optional | Build diagnostic rule ids suppressed on this widget |
| **responsive** | `never` | optional | [REMOVED] `dashboard.widgets[].responsive` was removed in @objectstack/spec 17.0.0 (#4876, ADR-0049 D2) — no renderer ever read it, so per-widget breakpoint overrides were never applied: the value parsed, validated, and then did nothing. The dashboard grid reflows by its own layout rules (`columns` + `gap` on the dashboard, the `layout` box on each widget). Delete the key. The shared `ResponsiveConfig` shape is NOT gone — it stays live on `page.components[].responsive`, which objectui `useResponsiveConfig` really does read; move the layout there if you need breakpoint behaviour today. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
| **responsive** | `never` | optional | [REMOVED] `dashboard.widgets[].responsive` was removed in @objectstack/spec 17.0.0 (#4876, ADR-0049 D2) — no renderer ever read it, so per-widget breakpoint overrides were never applied: the value parsed, validated, and then did nothing. The dashboard grid reflows by its own layout rules (`columns` + `gap` on the dashboard, the `layout` box on each widget). Delete the key. This message used to point at `page.components[].responsive` as the live home of the shared `ResponsiveConfig` shape; that key was measured equally unread and removed with the shape in #11027. For breakpoint behaviour that IS applied, use `responsiveStyles` on a page component (ADR-0065) — per-breakpoint CSS maps compiled to id-scoped CSS at render. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
| **aria** | `never` | optional | [REMOVED] `dashboard.widgets[].aria` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 D2) — no renderer ever applied it, so ARIA attributes declared on a widget silently did not reach the DOM: the key promised accessibility compliance it did not deliver. This is the same removal the dashboard-level `aria` got in 17.0.0 (#3896). Delete the key. The dashboard renderer emits its own `aria-*` attributes for the widget grid; author a `title` (and `description`) on the widget instead — those ARE what the renderer labels the card with. The shared `AriaProps` shape is NOT gone: it stays live on `page.aria`, `page.components[].aria` and the list view `aria`. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |

### Allowed Values: `DashboardWidget.type`
Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/ui/page.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -116,7 +116,7 @@ Interface-level page configuration (Airtable parity)
| **visibleWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Visibility predicate (CEL) — component rendered only when TRUE. Contract-bound roots: `record`, `current_user` (ADR-0068 aliases `user` / `ctx.user` — one object, three spellings), and page state as `page.<var>`. The shipping renderer additionally mounts `app`, `features`, `os.user` and binds `data` to the data-source ADAPTER here — renderer behaviour, NOT contract-guaranteed (ADR-0068 rules the user object only). ⚠️ `data` is surface-dependent: on a `page:tabs` item `visibleWhen` it is the record ROW instead. e.g. "page.selectedProjectId != ''" |
| **visibility** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | [DEPRECATED → `visibleWhen`] Visibility predicate (CEL). Normalized to `visibleWhen` at parse. |
| **dataSource** | `{ object: string; view?: string; filter?: any; sort?: object[]; … }` | optional | Per-element data binding for multi-object pages |
| **responsive** | `{ breakpoint?: Enum<'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'>; hiddenOn?: Enum<'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'>[]; columns?: object; order?: object }` | optional | Responsive layout configuration |
| **responsive** | `never` | optional | [REMOVED] `page.components[].responsive` was removed in @objectstack/spec 17 (#11027, ADR-0049 D2) — no renderer ever read it, so per-breakpoint layout overrides (columns/order/visibility) parsed, validated, and then did nothing. Delete the key. For breakpoint behaviour that IS applied, use the sibling `responsiveStyles` (ADR-0065) — per-breakpoint CSS maps compiled to id-scoped CSS at render, e.g. `responsiveStyles: { xsmall: { display: 'none' } }` to hide a component on the narrowest screens. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
| **aria** | `{ ariaLabel?: string \| Record<string, string>; ariaDescribedBy?: string; role?: string }` | optional | ARIA accessibility attributes |


Expand Down
72 changes: 3 additions & 69 deletions content/docs/references/ui/responsive.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,79 +12,13 @@ description: Responsive protocol schemas
## TypeScript Usage

```typescript
import { BreakpointColumnMapSchema, BreakpointName, BreakpointOrderMapSchema, ResponsiveConfigSchema, ResponsiveStylesSchema, StyleMapSchema } from '@objectstack/spec/ui';
import type { BreakpointColumnMap, BreakpointName, BreakpointOrderMap, ResponsiveConfig, ResponsiveStyles, StyleMap } from '@objectstack/spec/ui';
import { ResponsiveStylesSchema, StyleMapSchema } from '@objectstack/spec/ui';
import type { ResponsiveStyles, StyleMap } from '@objectstack/spec/ui';

// Validate data
const result = BreakpointColumnMapSchema.parse(data);
const result = ResponsiveStylesSchema.parse(data);
```

---

## BreakpointColumnMap

Grid columns per breakpoint (1-12)

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **xs** | `number` | optional | |
| **sm** | `number` | optional | |
| **md** | `number` | optional | |
| **lg** | `number` | optional | |
| **xl** | `number` | optional | |
| **2xl** | `number` | optional | |


---

## BreakpointName

### Allowed Values

* `xs`
* `sm`
* `md`
* `lg`
* `xl`
* `2xl`


---

## BreakpointOrderMap

Display order per breakpoint

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **xs** | `number` | optional | |
| **sm** | `number` | optional | |
| **md** | `number` | optional | |
| **lg** | `number` | optional | |
| **xl** | `number` | optional | |
| **2xl** | `number` | optional | |


---

## ResponsiveConfig

Responsive layout configuration

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **breakpoint** | `Enum<'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'>` | optional | Minimum breakpoint for visibility |
| **hiddenOn** | `Enum<'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'>[]` | optional | Hide on these breakpoints |
| **columns** | `{ xs?: number; sm?: number; md?: number; lg?: number; … }` | optional | Grid columns per breakpoint |
| **order** | `{ xs?: number; sm?: number; md?: number; lg?: number; … }` | optional | Display order per breakpoint |


---

## ResponsiveStyles
Expand Down
2 changes: 1 addition & 1 deletion content/docs/ui/pages.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -188,7 +188,7 @@ The `type` field is a union of the standard `PageComponentType` enum and any cus
- **AI:** `ai:chat_window`, `ai:suggestion`
- **Elements:** `element:text`, `element:number`, `element:image`, `element:divider`, `element:button`, `element:record_picker`, `element:text_input` (`element:filter` and `element:form` were retired in v17.x — no renderer ever shipped for either. List surfaces own their filtering via a view's `userFilters` quick-filter bar or the list toolbar's filter builder; for forms use the object-bound `object-form` block, which is rendered and designer-publishable)

Components may also carry `dataSource` (per-element object binding for multi-object pages), `responsive`, and `aria` configuration. Custom string types are also accepted for project-specific widgets.
Components may also carry `dataSource` (per-element object binding for multi-object pages), `responsiveStyles` (per-breakpoint scoped CSS, ADR-0065), and `aria` configuration. Custom string types are also accepted for project-specific widgets. (The former `responsive` layout block was retired in v17.x — no renderer ever applied it; see the upgrade guide.)

## Variables

Expand Down
Loading
Loading