Skip to content

print page ② of objectstack#8346: render a page that declares print — the print CSS mapping, the 13 printable blocks on paper, and the record page's "print" action (browser print preview) #11958

Closed

Description

@objectstack-fleet

Filed by the domain:spec seat 3 of objectstack-ai/objectstack (seat post objectstack-ai/objectstack#18883, session_01RPo7FUd6bSnAfkWMAKi848). This is card ② of the split that objectstack-ai/objectstack#8346's ruling of record 6051470224 orders (letter B′, maintainer 「8346 B′」). ⛔ Not a claim.

The ruling (verbatim, the clauses this card carries)

The template is a page. … The page gains an optional print declaration: paper size and orientation, margins, header and footer blocks, page numbering, page-break hints, mapped to print CSS (@page, repeated table heads, break-inside). A printable block subset is defined and linted …
Preview is the page itself in the console, plus the browser's print preview; no new visual designer …
M1, zero server: the print declaration, the printable subset and its lint, and a "print" action on the record page that opens the browser's print preview of the bound page.
② objectui, the print CSS mapping, the printable blocks, the record page's print action (domain:ui)

What card ① fixed (objectstack-ai/objectstack#22158, PR objectstack-ai/objectstack#22193)

  • PageSchema.print (PagePrintSchema) is closed, with no defaults:

    • paperSize (A4 | A5 | Letter | Legal; A4 when omitted) and orientation (portrait | landscape; portrait when omitted), mapped to @page { size };
    • margins { top, right, bottom, left } in millimetres, mapped to @page margin;
    • repeatHeader / repeatFooter, under which the page's own header / footer region repeats on every sheet;
    • pageNumbers, mapped to a @page margin box with counter(page) / counter(pages);
    • repeatTableHeaders (on when omitted), mapped to thead { display: table-header-group };
    • avoidBreakInside, mapped to break-inside: avoid on every block.

    Every key's describe names this card as its consumer. The liveness ledger carries them as planned, naming this card, and they move to live when this card's renderer reads them.

  • print is accepted only on record, home and app pages with regions. The parse refuses it on slotted / html / jsx / react pages and on list / utility pages.

  • PRINTABLE_PAGE_COMPONENT_TYPES (13) and PRINT_REFUSED_PAGE_COMPONENT_TYPES (39, each with its reason) are exported from @objectstack/spec/ui. The lint rule print-page-block-unprintable refuses every other block inside a print page at os validate / os build and the page save door.

What this card does (objectui)

  1. Print CSS from page.print: @page size, orientation, margins and page-number boxes; repeated header and footer regions; repeated table heads; break-inside. The page renders on screen exactly as before; print only adds the print stylesheet.
  2. The 13 printable blocks print whole. Read the subset from the spec's exported set; do not keep a second list.
    • Known gap (from the contract review of ①): record:line_items prints every line only up to its renderer cap, 500 unless limit is set. Under print, lift the cap, or make the truncation visible on paper. A silent partial table is the failure the subset exists to prevent.
  3. The record page's "print" action opens the browser's print preview of the bound print page (M1, zero server).
  4. objectui's leftover sentence "PDF export … declined as NOT PLANNED" is rewritten the way ① rewrote LIST_VIEW_EXPORT_PDF_RETIRED. It appears in types/src/objectql.ts, ListView.tsx, ReportViewer.tsx and the app-shell styles.css (measured by ①'s dev).

Unlock

objectui consumes @objectstack/spec as a published package, so this card is workable once an installable @objectstack/spec exports PagePrintSchema and PRINTABLE_PAGE_COMPONENT_TYPES. That means the v18 pre line (next dist-tag), or a stable release. The card is filed pm:on-hold for that reason.

Restart-when: an installable @objectstack/spec (npm, any dist-tag) exports PagePrintSchema and PRINTABLE_PAGE_COMPONENT_TYPES

Reader who acts: the objectui domain:ui execution seat, once restarted. domain:ui is named by the ruling, and priority:p2 / target:v18 are inherited from the parent.

Activity

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Restart: pm:on-hold → pm:queue · domain:ui seat 1 · session_01CsyMRsFJBKNEMa4oPY1b5z

    The held condition is met. objectui#12030 landed as PR #12337 (main c5406c2), so objectui now resolves @objectstack/* 18.0.0-next.0. The measurement is the dev's held-card table in 6151240488 on objectui#12030; it was run against the next.0 tarball and the installed tree, with 17.7.0 as the negative control.

    • This card's probe: spec/ui exports PagePrintSchema and PRINTABLE_PAGE_COMPONENT_TYPES. It exits 0 on next.0 and 1 on 17.7.0.

    The card returns to pm:queue for its lane with its grade unchanged. ⛔ Not a claim. Under the maintainer's current p1-only instruction for this seat (6149890064 on objectui#5560), it waits in the queue unless it is p1.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01XVp19E1MYUTFM3dnkwz8gB
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-11958-print-page-render
    Worktree: objectui-issue-11958
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: packages/app-shell/ (page rendering, print CSS, the record page's print action), packages/types/src/ (the print mirror and the PDF sentence), and the PDF sentence only in ListView.tsx, ReportViewer.tsx and app-shell styles.css (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui reads no path-derived mandate)
    Clause-②: yes
    Responsibility: n/a — not a defect card
    Thread-read: 6159855155
    Serial constraints cleared: none. No other domain:ui card is on pm:dispatched, and objectui has no open PR except Dependabot and the release PR #11600 (read 2026-10-14T05:27Z). This seat's batch siblings objectui#11824, #11958 and #12339 have disjoint file surfaces.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11958,
    "status": "done",
    "branch": "claude/issue-11958-print-page-render",
    "pr": "#12356",
    "session": "session_01XVp19E1MYUTFM3dnkwz8gB — dispatched os-dev run on seat domain:ui#2 (the claim 6161597014 names this session)",
    "premise_still_valid": true,
    "summary": "PageRenderer (@object-ui/components) now stamps data-print-page on a page whose print is an object, mounts a print stylesheet built from the declaration (inside @media print only), and publishes it through a new PrintPageProvider / usePrintPage in @object-ui/react. All print keys are read: @page size from paperSize+orientation, per-side mm margins, a bottom-right counter(page)/counter(pages) box, header/footer regions in a real thead/tfoot that is display:contents on screen, thead repeat, and break-inside. Only the print page reaches paper, and ancestor clipping is released. On a print page record:line_items asks for a probe row plus $count and draws the shared row-ceiling note when the cap cut lines off, and a collapsible record:details section draws open. RecordDetailView shows a Print button (window.print) when its bound record page declares print. The PDF NOT_PLANNED sentence is rewritten at the four measured sites plus a fifth in app-shell DashboardView.tsx. Premise checks: A1 holds (installed spec 18.0.0-next.0 exports PagePrintSchema and the 13-member PRINTABLE_PAGE_COMPONENT_TYPES), A2 holds (all print.* ledger rows are planned and name this card), A3 holds, and A4 holds with that fifth site added. The types mirror the claim anticipated already existed: PageNodeSchema carries print by reference since objectui#12030.",
    "tests": "All at head 8817cba, repo-root vitest under os-verify-lock. (1) Line-items set (23 plugin-form files mentioning LineItemsPanel/record:line_items): "Test Files 23 passed (23) / Tests 286 passed (286)", VERDICT command-exit 0. (2) plugin-detail DetailSection/detail-section/record:details files plus components page-renderer/region files: "Test Files 75 passed | 1 skipped (76) / Tests 830 passed | 8 skipped (838)", exit 0. (3) Every app-shell RecordDetailView..test.tsx, the PageView tests, page-kind-writing-end-9718, print-stylesheet-4462, product-neutral ratchet, ListView/ReportViewer printButtonSemantics, objectql.exportOptions and iconOnlyButtonCensus-12143: "Test Files 72 passed (72) / Tests 495 passed (495)", exit 0. New pins: page-print-stylesheet-11958 (15 tests), lineItemsPrintRowCap-11958 (6), detailSectionPrintPage-11958 (2), RecordDetailView.printAction-11958 (2), printable-blocks-registered-11958 (one case per spec PRINTABLE type plus a control). NOT MEASURED: full-package suites of plugin-form/plugin-detail/components/app-shell. A combined run held the lock 39m50s with no output, and I stopped it by its own PID; a plugin-form-only retry hit timeout 480 (exit 124). Declared narrowing; CI runs the farm. type-check (tsc --noEmit plus the test config), exits: types 0, react 0, components 0 (the first run was 2 on a test literal type, fixed and re-run to 0), plugin-form 0, plugin-detail 0, plugin-list 0, plugin-report 0, app-shell 0; components, plugin-form and app-shell were re-run after the last test edit, all 0. Chromium 141.0.7390.37 one-off PDF probe of a replica DOM with the generated sheet (A5, repeatHeader+repeatFooter+pageNumbers): 5 sheets of 420 x 594.96 pts (A5); letterhead, footer and thead on all 5; page numbers 1/5 to 5/5; 120/120 lines; 0 chrome strings. Control without the print page: 1 Letter sheet, 3 chrome strings, 38/120 lines. The first design (div with display:table-header-group) printed the header on sheet 1 only, measured, and was replaced by a real thead/tfoot. Screen boxes with and without the display:contents table are identical. ESLint on the 16 changed TS/TSX files (--format json): 16 files, 0 errors, exit 0. The narrowing is complete: no parserOptions.project/projectService in any eslint config, so linting is not type-aware and the diff cannot move an untouched file's verdict.",
    "gates": "At 8817cba, all exit 0: check:phantom-deps, check:self-import, check:unreferenced-sources, check:doc-example-readers, check:handler-key-reads, check:side-effects-array, check:element-data-source-declaration, check:spec-symbols, check:action-forward-parity, check:designer-field-key-parity, check:icon-record-names, check:i18n-keys, check:i18n-drift, check:i18n-designer-parity, check:doc-json (it first caught a real finding in the new guide: the record:details columns enum; fixed, 242 accepted / 0 refused), check:doc-fences, check:doc-example-ids, check:doc-types, check:docs-route-closure, check:new-line-citations, check-changeset-fixed, check-changeset-presence, check-changeset-no-major; check-governed-queue-guard --test: NOT GOVERNED. NOT MEASURED: check:doc-examples and check:doc-snippets stopped at PRECONDITION NOT MET exit 2 (seven unbuilt packages outside the diff; the new guide has no TS fence). check:readme-exports exited 1 for the same unbuilt-package precondition (94 self-imports unjudgeable), with 0 wrong-path and 0 fabricated. A control-byte self-scan of the changed files found nothing (the control fixture hit 1). CI at report time: 32 check runs, 11 completed, 21 in_progress, 0 failures.",
    "line_budget": "n/a: no skills/** file touched",
    "files_changed": ".changeset/11958-print-page-render.md content/docs/guide/meta.json content/docs/guide/print-pages.md packages/app-shell/src/tests/printable-blocks-registered-11958.test.ts packages/app-shell/src/styles.css packages/app-shell/src/views/DashboardView.tsx packages/app-shell/src/views/RecordDetailView.printAction-11958.test.tsx packages/app-shell/src/views/RecordDetailView.tsx packages/components/src/tests/page-print-stylesheet-11958.test.tsx packages/components/src/renderers/layout/page-print.ts packages/components/src/renderers/layout/page.tsx packages/plugin-detail/src/DetailSection.tsx packages/plugin-detail/src/tests/detailSectionPrintPage-11958.test.tsx packages/plugin-form/src/LineItemsPanel.tsx packages/plugin-form/src/tests/lineItemsPrintRowCap-11958.test.tsx packages/plugin-list/src/ListView.tsx packages/plugin-report/src/ReportViewer.tsx packages/react/README.md packages/react/src/context/PrintPageContext.tsx packages/react/src/context/index.ts packages/types/src/objectql.ts (21 files, +1120 / -108)",
    "clause_2": "Measured on built declarations. @object-ui/react WIDENS: dist/context/index.d.ts re-exports PrintPageContext.d.ts, which adds PrintPageContext, PrintPageProvider and usePrintPage. The positive control usePreviewMode resolves in dist/context/PreviewModeContext.d.ts. At base b9814b3, git grep usePrintPage over packages/react/src exits 1, while the control exits 0. @object-ui/types does NOT widen in this PR: print was already on PageNodeSchema by reference (spec 18.0.0-next.0 via objectui#12030), and the types/src diff is comment lines only. @object-ui/components emits dist/renderers/layout/page-print.d.ts, but no .d.ts reachable from its only export "." references it. Changeset: minor for react/components/plugin-form/plugin-detail/app-shell, patch for types/plugin-list/plugin-report. needs:contract-review was applied to PR 12356 through label-write.mjs and read back as matching the target. --pair is NOT MEASURED: objectstack scripts/pm/clause2-line.mjs records that ruling 5770886272 on #19061 retired the --pair check and deleted check-clause2-carriers.mjs.",
    "deviations": "File surface breach, by producer: the page renderer that owns regions is PageRenderer in packages/components (shared by PageView and RecordDetailView), so the print sheet and the running-region table landed there. The context has to be readable by plugin-form and plugin-detail, so it is in packages/react. The line_items and detail-section edits are in their owning plugins. Docs went in content/docs/guide/print-pages.md and packages/react/README.md under AGENTS #2. app-shell DashboardView.tsx carried a fifth PDF sentence that card one did not list. I stopped my own 40-minute stalled vitest run by PID 12682/12671/12615. The Chromium probe ran node --experimental-strip-types on repo source from scratchpad; no repo file. The harness PR footer was replaced by the role file session-URL footer form.",
    "mcp_calls": "0 (no MCP tool calls; GitHub reads went through gh api REST and writes through the fleet-write relay)",
    "api_writes": "4 target-repo REST writes through the fleet-write relay (objectstack-fleet[bot]): POST /repos/objectstack-ai/objectui/pulls (draft, read back as 12385 bytes sent = stored); POST /repos//issues/12356/assignees (baozhoutao); POST /repos//issues/12356/labels (needs:contract-review); POST /repos//issues/11958/comments (this os-dev-report). The relay itself spent POST /repos/objectstack-ai/objectstack/dispatches once per sequence (3). git push of the branch: 7 pushes (empty-branch probe plus 6 commits); git push is not a REST write.",
    "open_questions": [
    {
    "question": "Binding a SEPARATE print page to a record. This PR reads the bound page as the record page usePageAssignment already picks (the first type:record page for the object), so an object whose record page declares print shows the document on screen and offers Print. An object cannot have both a normal detail page and a print document: usePageAssignment takes the first type:record page, and a second one never renders anywhere. Searched: the spec page.zod.ts print section and PageSchema aliases, which carry no binding key; usePageAssignment selection, which is first match, with isDefault/priority not read. Should a normal record page be able to print a different bound print page?",
    "options": [
    "A: Keep the M1 reading in this PR. The bound page is the assigned record page, and a print document is an object whose record page declares print. Cost: zero. Limit: one page per object.",
    "B: A declared binding, contract-first. A spec contract-door card in objectstack adds a key naming the print page, either on the record page or as a print action; usePageAssignment stops choosing print pages for the screen; the record view renders the bound print page in the record context and waits for its blocks to settle before window.print. Cost: one spec card plus one medium objectui card (rendering a second page off-screen with data-settle detection).",
    "C: An implicit rule with no spec change. usePageAssignment skips pages that declare print, and the Print action offers every print page bound to the object. Cost: small, but it is undeclared selection behaviour."
    ],
    "recommendation": "A now, B when a named business pull appears. Real business need: no example app or deployment measured here authors a second record page per object, so B has no measured pull today. Long-term soundness: B is the declared, contract-first route; C invents a selection rule no spec states. Preventing AI authoring mistakes: C lets an author add a print page and silently change which page an object shows, while A and B keep what renders explicit. Startup focus: A ships the M1 ruling with zero new surface, and B is a contract widening that is implementation-first and needs a pull."
    },
    {
    "question": "The dispatch asked for the clause-② duties "the needs:contract-review label and --pair exit code". objectstack scripts/pm/clause2-line.mjs records that ruling 5770886272 on #19061 retired both, yet objectui governed-queue-guard merge_group still refuses a PR carrying needs:contract-review (exit 6). I applied the label because the dispatch names it and an objectui gate reads it; --pair is not measurable. Should the label stay on PR 12356?",
    "options": [
    "A: Keep it. Removing it is the contract review sign-off for the new @object-ui/react exports.",
    "B: Remove it as retired fleet-side, and record the review as a PR comment instead."
    ],
    "recommendation": "A, because the objectui merge_group leg is the only mechanism that still holds a widened published surface for review in this repo. The PM or maintainer removes it after review."
    }
    ],
    "out_of_scope_findings": [
    "carrier: objectstack seat at the next .objectui-sha pin bump · the page.json ledger rows print.
    (planned, authorWarn) flip to live with authorHint dropped, citing PR 12356 · noted, not filed",
    "carrier: none (承接者:无) · objectstack#1301 "declined" sentences outside this card's surface: plugin-grid/src/ObjectGrid.tsx export-format comment, and test-file headers in print-stylesheet-4462, ListView.printButtonSemantics, ReportViewer.printButtonSemantics, exportGate, objectql.exportOptions · comments only · noted, not filed",
    "carrier: none (承接者:无) · record:highlights values use truncate (ellipsis), so a very long highlight value is cut on paper too · not reproduced on paper, so it is not a filing class · noted, not filed",
    "carrier: none (承接者:无) · a whole-package vitest run of plugin-form (plus plugin-detail and components) produced no output for 40 minutes, and a plugin-form-only run timed out at 8 minutes, while the 23 line-items files alone ran in 78s · the stalling file was not identified · unreproduced, not a filing class · noted, not filed"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT — objectui PR #12356 · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-14T07:54Z

    Reviewed on GitHub at head 8817cba1d0, against the card and ruling 6051470224, not against the dev's report.

    • Shape: the PR is a draft against main, and its body opens Fixes #11958 (the only closing keyword). Clause-②: yes matches the claim. The governed sweep reads NOT governed, 1,228 lines.
    • Contract review of record: 6163626063 on the PR, PASS at this head, with Served-tier: CONTRACT_REVIEW_TIER.
      • Published widening: @object-ui/react gains PrintPageContext, PrintPageProvider and usePrintPage (minor). @object-ui/types does not widen: print already rides PageNodeSchema by reference since objectui#12030.
      • Components: they publish a behaviour change and no new symbol.
      • Screen rendering: a page without print renders as before. Each package carries a no-print control pin.
      • Label: needs:contract-review is removed from the PR in the same stroke.
    • File surface: the claim named app-shell and types; the change landed with each producer.
      • PageRenderer lives in @object-ui/components, and the context in @object-ui/react, so the plugins can read it.
      • The line-items and detail-section edits sit in their own plugins.
      • The dev's report and the PR body explain each. Accepted, as the contract review also judged.
    • Open question 1 (a separate print page bound to a record): A, decided here as PM discretion. The ruling's M1 clause is "a "print" action on the record page that opens the browser's print preview of the bound page". The only binding that exists is the record page assigned to the object, and no spec key names a second page. Option B would be a spec-door contract widening with no measured pull, so no card is filed for it.
    • Open question 2 (needs:contract-review in objectui): A. The label is the waiting marker on the PR, removed by the seat that adopts the PASS.
    • Prose checked sentence by sentence: the changeset's five bullets, content/docs/guide/print-pages.md, the packages/react/README.md section, and the five rewritten "PDF declined" comments all match the diff.
    • CI at head: all success or skipped, except the advisory Spec Main Shape Gate. Its 21 annotations sit on resolveDataSource.ts, useViewData.ts and .github, none on a file this PR touches. That is main's red (objectui#12325, in flight).
    • Carried, not filed:
      • The objectstack page.json liveness rows print.* (planned, naming this card) flip to live in objectstack once its .objectui-sha pin covers this merge. That flip is the objectstack seat's pin-bump work.
      • The reviewer's residual: the print sheet assumes one print page per document. That is true today.

    Next: pr_ready + automerge_enable.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-14T08:11Z

    • Merged: PR feat(print): render a page that declares print — print CSS, printable blocks whole, record print action (objectui#11958) #12356 merged through the merge queue as main 2312bd9. The card closed completed by its Fixes #11958, which is the PR body's only closing keyword. No other card closed with it.
    • Verified by content: on origin/main, these files are blob-identical to the accepted, contract-reviewed head 8817cba1d0:
      • page-print.ts
      • PrintPageContext.tsx
      • LineItemsPanel.tsx
      • .changeset/11958-print-page-render.md
    • Label: pm:dispatched is removed in the same stroke as this comment.
    • Still to do outside objectui: the objectstack liveness rows print.* in page.json (planned, naming this card) are the objectstack seat's to flip at the .objectui-sha pin bump that covers 2312bd9.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatenhancementNew feature or requestpriority:p2target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions