Skip to content

Commit 23d0bc7

Browse files
fix(spec): page.print and the flow terminal-toast translations read live in the liveness ledger, so a print page is no longer warned that nothing applies it (#23776)
Fixes #23710 Clause-②: no The console at the `.objectui-sha` pin (`f96f771140f1`) prints a page that declares `print` and shows the translated flow terminal toasts. The liveness ledger still graded both `planned`, so `lintLivenessProperties` told every author of a print page that printing it prints the screen layout. This PR flips those verdicts to `live`, citing each objectui reader at the pin, drops `print`'s `authorWarn`, and rewrites `PagePrintSchema`'s describes, which said "until it ships, nothing applies it". The accept set does not change. The `parentDataSource` leg stays with #23585. ## Evidence per flipped verdict (objectui re-read at `f96f771140f1`, the pin this lands against) Each reader's landing commit is an ancestor of the pin: `git merge-base --is-ancestor 2312bd9 f96f771` gives exit 0 (objectui#11958), and `git merge-base --is-ancestor 42d8e8d f96f771` gives exit 0 (objectui#12075). Both were read in a full, non-shallow objectui clone. **`page.json` `props.print`: 13 rows, all read, all flipped.** That is the container, its 8 keys, and the 4 `margins` sides. The card counted 12 children, because `margins` is both a key and a container. No row stays `planned`. | Row | Reader at the pin | |:---|:---| | `print` (container) | `packages/components/src/renderers/layout/page.tsx#PageRenderer` mounts `buildPagePrintStylesheet(print)` as a `style` element, only when `schema.print` is an object, and stamps the page root with `PRINT_PAGE_ATTRIBUTE`. The refusals stay cited as before: the spec's `checkPagePrintComposition` and lint's `validatePrintPageBlocks`. | | `paperSize`, `orientation` | `page-print.ts#buildPagePrintStylesheet` writes `@page { size }` through `pageRule`, and only for an enum member. | | `margins` and `margins.top` / `.right` / `.bottom` / `.left` | `pageRule` writes one `margin-SIDE: Nmm` for each declared finite, non-negative side. | | `pageNumbers` | `pageRule` writes the `@bottom-right` margin box with `counter(page)` / `counter(pages)`. | | `repeatHeader`, `repeatFooter` | `page.tsx#RunningRegions` puts the region in a real `thead` / `tfoot`. `runningRegionRules` turns that table back into a table on paper. | | `repeatTableHeaders` | `thead { display: table-header-group }`, or `table-row-group` when the key is `false`. | | `avoidBreakInside` | `break-inside: avoid` on every block of a region. | **`translation.json` `flows.successMessage` / `flows.errorMessage`.** `packages/app-shell/src/utils/flowResponse.ts#translatedFlowMessage` calls the spec's `translateFlow` for `flows.FLOW.KEY` and falls back to the served string. `interpretFlowResponse` (the terminal-success arm) and `flowFailureMessage` call it. `views/FlowRunner.tsx#FlowRunner` passes the run's flow name and bundle. The spec-side evidence in each row is unchanged. `authorWarn` / `authorHint` are dropped from `print` in the same edit, because `check:liveness` refuses them on a `live` row. With that, **no shipped ledger row opts into `authorWarn` any more**: `print` was the last one. ## Describes (`packages/spec/src/ui/page.zod.ts`) Only text changed: the 8 key describes, `PageSchema.print`'s describe, the `PagePrintSchema` docblock, the `print` property's docblock, and the section comment above the printable subset. Each one now names the console's browser print rendering as the consumer that applies the key. The server-side PDF renderer (card ③) is still "to follow". No schema shape, enum, alias or refinement was touched. `check:authorable-surface` is in the gate run below. ## Pins - `packages/lint/src/lint-liveness-properties.test.ts` - **Reversed.** The #21127 control used `page.print` as its "warned `planned` row". A new `#23710` block pins the issue's criterion on the real ledger: a page declaring `print` gets no `liveness-planned-property`. The anti-vacuity check is that the same call still raises the `externalSharingModel` dead row. - **Moved to the planted ledger.** The "a warned `planned` row still warns" control now uses the planted translation group that the file already uses for exactly this shape. - **Re-anchored.** The hint-leak pin's anti-vacuity line asserted that at least one shipped opt-in row exists. That row was `print`. The line is removed, and its comment now says that both opt-in classes are held by the SYNTHETIC case right below it. - `packages/spec/scripts/liveness/check-liveness.test.ts`: the census control asserted that the shipped walk reaches at least one warned row, which was `print`. The reach is now proved on a copy with one shipped `planned` row marked `authorWarn`: the gate stays green and the census counts exactly one more row. ## Generated `content/docs/references/ui/page.mdx`, `liveness/state-counts/page.md` (planned 11 → 0) and `liveness/state-counts/translation.md` (planned 2 → 0) were regenerated with `gen:docs` and `gen:liveness-counts`. Those are the two that `check:generated` proved stale. Nothing was hand-written in them. ## Verification (head `6375c0cb15`) - `pnpm --filter @objectstack/spec test`: 679 files, 20,756 passed, 1 todo, exit 0. Run under `os-verify-lock` on head `6375c0cb15`. - `pnpm --filter @objectstack/lint test`: 139 files, 6,767 passed, 5 skipped, exit 0. Run on `d03703eb`. The lint package has not changed since; the only later commit edits a spec script test. - `typecheck` exit 0 for `@objectstack/lint` (`tsc --noEmit` + `check:test-typecheck`) and `@objectstack/spec` (`tsc --noEmit` + `check:scripts-typecheck` + `check:test-typecheck`). - `check:generated` on the built spec: the first run named exactly two stale artifacts (`content/docs/references/**`, `liveness/state-counts/`), both regenerated. It is in the derived run below at exit 0. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths) gives 110 commands. Each was run with its exit code captured before any pipe, on head `6375c0cb15` after a full workspace build: **110 exit 0**. The `--ran` reconciliation over the `COMMAND :: exit N` record reports "110 derived, 110 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero)". An earlier pass on `9c9f0a3e` returned 7 `PREREQUISITE NOT MET` exit 3s (unbuilt `dist/` and a shallow clone). Those are not measurements; they cleared after `git fetch --unshallow` and the build. - Local-only families: `check:dev-prereqs` exit 0, `check:skill-frame-freshness` exit 0. - CI owns everything else (the artifact rosters, wide-population families, path-scheduled jobs and type-check lanes that `dispatch-gates` lists outside the derived total). - No ablation: no runtime behaviour changed. What the ledger flip did change was observed directly: against the flipped ledger, the two anti-vacuity pins that counted on a shipped opt-in row went red (`lint-liveness-properties.test.ts` hint-leak pin, "expected 0 to be greater than 0"; `check-liveness.test.ts` census, "a walk reaching zero warned rows is a degraded walk"). Both were re-anchored as described above. I rewrote the `print` control before running it against the new ledger, so its red was not observed. ## Acceptance notes - `packages/spec/liveness/README.md`: the `page` and `translation` prose cells described the old verdicts, so I updated them. These two cells are outside the claim's listed file surface, but leaving them would keep a published statement this PR makes false. `packages/spec/scripts/liveness/check-liveness.test.ts` is outside the listed surface for the same reason: its pin asserted the old census. - `.changeset/pre/22158-print-page-spec.md` still says nothing applies `print`. That file is pre-mode release state, so this PR leaves it alone. - `check-liveness.mts` has a comment recording an old count ("3 opt in with `authorWarn`"). It is a dated measurement, not a claim about the current tree, and is left as is. - PR #23708 edits other regions of `page.zod.ts` and `liveness/page.json` and also regenerates `page.mdx`. It was not on `main` when this was pushed, so whichever lands second merges `main` and regenerates through `scripts/pm/os-regen-merge.sh`, keeping both changes. - Changeset: `@objectstack/spec` patch. The ledger (`files[]` includes `liveness`) and the describes ship in the package, and `@objectstack/lint` reads the ledger from it. `@objectstack/lint`'s published code is unchanged; only its tests changed. --- _Generated by [Claude Code](https://claude.ai/code/session_01QVkaSahaBPN9mkBRwLJ44o)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9d4fb3c commit 23d0bc7

10 files changed

Lines changed: 215 additions & 118 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): a page that declares `print` is no longer warned that nothing applies it, and the flow terminal-toast translations read `live` (#23710)
6+
7+
Clause-②: no
8+
9+
**What was wrong.** The console now prints a page that declares `print`: its `PageRenderer` mounts a print stylesheet built from every key of the declaration (paper size and orientation, the four margins, the running header and footer, page numbers, and the two page-break hints). But the liveness ledger shipped with `@objectstack/spec` still graded `page.print` `planned` with an author warning, so `os validate` / `os build` / `os lint` told every author of a print page that printing it prints the screen layout. The `print` describes said the same ("until it ships, nothing applies it").
10+
11+
**What changes.**
12+
13+
- `liveness/page.json`: `print`, its eight keys and the four `margins` sides read `live`, citing the console reader at the `.objectui-sha` pin. The container's `authorWarn` / `authorHint` are dropped, so `lintLivenessProperties` no longer emits `liveness-planned-property` for a page that declares `print`.
14+
- `liveness/translation.json`: `flows.successMessage` and `flows.errorMessage` read `live`. The console's flow runner shows the translated terminal toast through `translateFlow` and falls back to the flow's own text when the bundle has none.
15+
- `PagePrintSchema` and `PageSchema.print`: the describes name the console's browser print rendering as the consumer that applies each key. The server-side PDF renderer is still to come.
16+
17+
Unchanged: what a `print` declaration accepts and refuses, and every other ledger row.

‎content/docs/references/ui/page.mdx‎

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ View filter rule
213213
| **assignedProfiles** | `never` | optional | [REMOVED] `page.assignedProfiles` was removed in @objectstack/spec 17.5.0 (ADR-0090 D2, ADR-0049 enforce-or-remove) — it was named for the Profile concept ADR-0090 D2 deleted, and it gated nothing: no renderer, route or metadata read door ever read the key, so a page that "assigned profiles" stayed open to every caller who could reach it. Delete the key. Page audience is the permission set's: gate the DATA the page shows with the object's permission sets, and bind those sets to people through positions (named in a position's `permissionSets`) — those are the checks the runtime actually runs. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
214214
| **interfaceConfig** | `{ source?: string; columns?: string[] \| object[]; sort?: object[]; filterBy?: object[]; … }` | optional | Interface-level page configuration (for Airtable-style interface pages) |
215215
| **aria** | `{ ariaLabel?: string \| Record<string, string>; ariaDescribedBy?: string; role?: string }` | optional | ARIA accessibility attributes |
216-
| **print** | `{ paperSize?: Enum<'A4' \| 'A5' \| 'Letter' \| 'Legal'>; orientation?: Enum<'portrait' \| 'landscape'>; margins?: object; repeatHeader?: boolean; … }` | optional | Print declaration — makes this page a print page, a document authored in the page's own blocks: paper size and orientation, margins in millimetres, the running header and footer (the page's own `header` / `footer` regions), page numbers and page-break hints, each mapped to print CSS. A print page is a `kind: 'full'` page of `type: 'record'`, `'home'` or `'app'` with its blocks in `regions`, and every block in it must come from the printable block subset: containers that draw every child (`page:section`, `page:card`, `page:footer`), field blocks (`record:details`, `record:highlights`), the child-record table `record:line_items`, `element:text`, `element:image`, `element:divider`, `element:definition-list`, `element:repeater`, `element:number` and `object-metric`; `os validate` / `os build` / `os lint` and the metadata save door refuse any other block inside it. Consumer: the console's browser print rendering, and later the server-side PDF renderer; until it ships, the declaration is validated but nothing applies it. A list view's print button is `allowPrinting`, not this. |
216+
| **print** | `{ paperSize?: Enum<'A4' \| 'A5' \| 'Letter' \| 'Legal'>; orientation?: Enum<'portrait' \| 'landscape'>; margins?: object; repeatHeader?: boolean; … }` | optional | Print declaration — makes this page a print page, a document authored in the page's own blocks: paper size and orientation, margins in millimetres, the running header and footer (the page's own `header` / `footer` regions), page numbers and page-break hints, each mapped to print CSS. A print page is a `kind: 'full'` page of `type: 'record'`, `'home'` or `'app'` with its blocks in `regions`, and every block in it must come from the printable block subset: containers that draw every child (`page:section`, `page:card`, `page:footer`), field blocks (`record:details`, `record:highlights`), the child-record table `record:line_items`, `element:text`, `element:image`, `element:divider`, `element:definition-list`, `element:repeater`, `element:number` and `object-metric`; `os validate` / `os build` / `os lint` and the metadata save door refuse any other block inside it. Applied by the console's browser print rendering, which prints the page through the browser's own print dialog (where it can be saved as PDF); the server-side PDF renderer is to follow. A list view's print button is `allowPrinting`, not this. |
217217
| **kind** | `Enum<'full' \| 'slotted' \| 'html' \| 'react'>` | optional (default: `"full"`) | Page override mode. full \| slotted = structured authoring; html = author-written constrained JSX compiled (parsed, never executed) to the tree (ADR-0080; ADR-0081 named it html), styled by the registered components' structured props plus a JSON `style` object with hsl(var(--token)) theme colors; react = real-React source executed at render by the runtime (ADR-0081), styled by inline `style` with the same token colors; it runs author JS, so it is gated by a host capability that defaults ON and is disabled server-side via the OS_PAGE_REACT=off env toggle. Do not author Tailwind classes in page source in either tier: `source` is runtime metadata the build-time Tailwind never scans, so utility classNames silently produce no CSS (ADR-0065; ADR-0080). |
218218
| **slots** | `{ header?: object \| object[]; actions?: object \| object[]; alerts?: object \| object[]; highlights?: object \| object[]; … }` | optional | Slot override map for slotted pages |
219219
| **source** | `string` | optional | Page source text. For kind==='html' it is constrained JSX compiled to the tree by @objectstack/sdui-parser at save time (parse, never execute), styled by the registered components' structured props plus a JSON `style` object with hsl(var(--token)) theme colors. For kind==='react' it is real React/JSX executed at render by @object-ui/react-runtime (trusted tier), styled by inline `style` with the same token colors. Do not author Tailwind classes in page source in either tier: `source` is runtime metadata the build-time Tailwind never scans, so utility classNames silently produce no CSS (ADR-0065; ADR-0080). Authoritative over `regions` in both. |
@@ -275,14 +275,14 @@ View filter rule
275275

276276
| Property | Type | Required | Description |
277277
| :--- | :--- | :--- | :--- |
278-
| **paperSize** | `Enum<'A4' \| 'A5' \| 'Letter' \| 'Legal'>` | optional | Paper size — the size keyword of the print CSS `@page { size }` rule (A4 when omitted). Consumer: the console's browser print rendering, and later the server-side PDF renderer; until it ships, nothing applies it. |
279-
| **orientation** | `Enum<'portrait' \| 'landscape'>` | optional | Sheet orientation — the orientation keyword of the `@page { size }` rule (portrait when omitted). Consumer: the console's browser print rendering, and later the server-side PDF renderer; until it ships, nothing applies it. |
280-
| **margins** | `{ top?: number; right?: number; bottom?: number; left?: number }` | optional | Sheet margins in MILLIMETRES, one number per side — the `@page { margin }` rule; a side left out takes the renderer's default. Consumer: the console's browser print rendering, and later the server-side PDF renderer; until it ships, nothing applies it. |
281-
| **repeatHeader** | `boolean` | optional | Repeat the page's `header` region (its `regions` entry named `header`) at the top of every printed sheet — the running header. The parse refuses it on a page with no `header` region. Off when omitted: the region prints once, at the top of the first sheet. Consumer: the console's browser print rendering; until it ships, nothing applies it. |
282-
| **repeatFooter** | `boolean` | optional | Repeat the page's `footer` region (its `regions` entry named `footer`) at the bottom of every printed sheet — the running footer. The parse refuses it on a page with no `footer` region. Off when omitted: the region prints once, after the body. Consumer: the console's browser print rendering; until it ships, nothing applies it. |
283-
| **pageNumbers** | `boolean` | optional | Print the sheet number and the sheet count in the bottom margin of every sheet — the `@page` margin box with `counter(page)` and `counter(pages)`. Off when omitted. Consumer: the console's browser print rendering; until it ships, nothing applies it. |
284-
| **repeatTableHeaders** | `boolean` | optional | Page-break hint: repeat a table's column headings at the top of every sheet the table runs onto (`thead { display: table-header-group }`). On when omitted. Consumer: the console's browser print rendering; until it ships, nothing applies it. |
285-
| **avoidBreakInside** | `boolean` | optional | Page-break hint: keep each block whole on one sheet when it fits, moving it to the next sheet instead of splitting it (`break-inside: avoid` on every block). Off when omitted. Consumer: the console's browser print rendering; until it ships, nothing applies it. |
278+
| **paperSize** | `Enum<'A4' \| 'A5' \| 'Letter' \| 'Legal'>` | optional | Paper size — the size keyword of the print CSS `@page { size }` rule (A4 when omitted). Applied by the console's browser print rendering; the server-side PDF renderer is to follow. |
279+
| **orientation** | `Enum<'portrait' \| 'landscape'>` | optional | Sheet orientation — the orientation keyword of the `@page { size }` rule (portrait when omitted). Applied by the console's browser print rendering; the server-side PDF renderer is to follow. |
280+
| **margins** | `{ top?: number; right?: number; bottom?: number; left?: number }` | optional | Sheet margins in MILLIMETRES, one number per side — the `@page { margin }` rule; a side left out takes the renderer's default. Applied by the console's browser print rendering; the server-side PDF renderer is to follow. |
281+
| **repeatHeader** | `boolean` | optional | Repeat the page's `header` region (its `regions` entry named `header`) at the top of every printed sheet — the running header. The parse refuses it on a page with no `header` region. Off when omitted: the region prints once, at the top of the first sheet. Applied by the console's browser print rendering. |
282+
| **repeatFooter** | `boolean` | optional | Repeat the page's `footer` region (its `regions` entry named `footer`) at the bottom of every printed sheet — the running footer. The parse refuses it on a page with no `footer` region. Off when omitted: the region prints once, after the body. Applied by the console's browser print rendering. |
283+
| **pageNumbers** | `boolean` | optional | Print the sheet number and the sheet count in the bottom margin of every sheet — the `@page` margin box with `counter(page)` and `counter(pages)`. Off when omitted. Applied by the console's browser print rendering. |
284+
| **repeatTableHeaders** | `boolean` | optional | Page-break hint: repeat a table's column headings at the top of every sheet the table runs onto (`thead { display: table-header-group }`). On when omitted. Applied by the console's browser print rendering. |
285+
| **avoidBreakInside** | `boolean` | optional | Page-break hint: keep each block whole on one sheet when it fits, moving it to the next sheet instead of splitting it (`break-inside: avoid` on every block). Off when omitted. Applied by the console's browser print rendering. |
286286

287287

288288
---
@@ -397,14 +397,14 @@ Print declaration: paper size, orientation, margins, the running header and foot
397397

398398
| Property | Type | Required | Description |
399399
| :--- | :--- | :--- | :--- |
400-
| **paperSize** | `Enum<'A4' \| 'A5' \| 'Letter' \| 'Legal'>` | optional | Paper size — the size keyword of the print CSS `@page { size }` rule (A4 when omitted). Consumer: the console's browser print rendering, and later the server-side PDF renderer; until it ships, nothing applies it. |
401-
| **orientation** | `Enum<'portrait' \| 'landscape'>` | optional | Sheet orientation — the orientation keyword of the `@page { size }` rule (portrait when omitted). Consumer: the console's browser print rendering, and later the server-side PDF renderer; until it ships, nothing applies it. |
402-
| **margins** | `{ top?: number; right?: number; bottom?: number; left?: number }` | optional | Sheet margins in MILLIMETRES, one number per side — the `@page { margin }` rule; a side left out takes the renderer's default. Consumer: the console's browser print rendering, and later the server-side PDF renderer; until it ships, nothing applies it. |
403-
| **repeatHeader** | `boolean` | optional | Repeat the page's `header` region (its `regions` entry named `header`) at the top of every printed sheet — the running header. The parse refuses it on a page with no `header` region. Off when omitted: the region prints once, at the top of the first sheet. Consumer: the console's browser print rendering; until it ships, nothing applies it. |
404-
| **repeatFooter** | `boolean` | optional | Repeat the page's `footer` region (its `regions` entry named `footer`) at the bottom of every printed sheet — the running footer. The parse refuses it on a page with no `footer` region. Off when omitted: the region prints once, after the body. Consumer: the console's browser print rendering; until it ships, nothing applies it. |
405-
| **pageNumbers** | `boolean` | optional | Print the sheet number and the sheet count in the bottom margin of every sheet — the `@page` margin box with `counter(page)` and `counter(pages)`. Off when omitted. Consumer: the console's browser print rendering; until it ships, nothing applies it. |
406-
| **repeatTableHeaders** | `boolean` | optional | Page-break hint: repeat a table's column headings at the top of every sheet the table runs onto (`thead { display: table-header-group }`). On when omitted. Consumer: the console's browser print rendering; until it ships, nothing applies it. |
407-
| **avoidBreakInside** | `boolean` | optional | Page-break hint: keep each block whole on one sheet when it fits, moving it to the next sheet instead of splitting it (`break-inside: avoid` on every block). Off when omitted. Consumer: the console's browser print rendering; until it ships, nothing applies it. |
400+
| **paperSize** | `Enum<'A4' \| 'A5' \| 'Letter' \| 'Legal'>` | optional | Paper size — the size keyword of the print CSS `@page { size }` rule (A4 when omitted). Applied by the console's browser print rendering; the server-side PDF renderer is to follow. |
401+
| **orientation** | `Enum<'portrait' \| 'landscape'>` | optional | Sheet orientation — the orientation keyword of the `@page { size }` rule (portrait when omitted). Applied by the console's browser print rendering; the server-side PDF renderer is to follow. |
402+
| **margins** | `{ top?: number; right?: number; bottom?: number; left?: number }` | optional | Sheet margins in MILLIMETRES, one number per side — the `@page { margin }` rule; a side left out takes the renderer's default. Applied by the console's browser print rendering; the server-side PDF renderer is to follow. |
403+
| **repeatHeader** | `boolean` | optional | Repeat the page's `header` region (its `regions` entry named `header`) at the top of every printed sheet — the running header. The parse refuses it on a page with no `header` region. Off when omitted: the region prints once, at the top of the first sheet. Applied by the console's browser print rendering. |
404+
| **repeatFooter** | `boolean` | optional | Repeat the page's `footer` region (its `regions` entry named `footer`) at the bottom of every printed sheet — the running footer. The parse refuses it on a page with no `footer` region. Off when omitted: the region prints once, after the body. Applied by the console's browser print rendering. |
405+
| **pageNumbers** | `boolean` | optional | Print the sheet number and the sheet count in the bottom margin of every sheet — the `@page` margin box with `counter(page)` and `counter(pages)`. Off when omitted. Applied by the console's browser print rendering. |
406+
| **repeatTableHeaders** | `boolean` | optional | Page-break hint: repeat a table's column headings at the top of every sheet the table runs onto (`thead { display: table-header-group }`). On when omitted. Applied by the console's browser print rendering. |
407+
| **avoidBreakInside** | `boolean` | optional | Page-break hint: keep each block whole on one sheet when it fits, moving it to the next sheet instead of splitting it (`break-inside: avoid` on every block). Off when omitted. Applied by the console's browser print rendering. |
408408

409409
### Nested Shape: `PagePrint.margins`
410410

0 commit comments

Comments
 (0)