Skip to content

fix(app-shell): a print record page draws the record's header actions beside Print - #12921

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12906-print-page-header-actions
Oct 17, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12906-print-page-header-actions

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12906
Clause-②: yes (measured correction of the claim's no: @object-ui/components gains a new export, PageHeaderActions and its props type PageHeaderActionsProps. No spec key, registry key or printable component type changes.)

What changed

  • @object-ui/components, containers.tsx. page:header's action row (declared-id resolution, the location / capability / visible / hidden gates, the merge with RecordContext.headerSystemActions, ordering, the inline / ⋯ split, localization and dispatchHeaderAction) is lifted, unchanged, out of PageHeaderRenderer into an exported PageHeaderActions component. PageHeaderRenderer now draws its tail through it, passing its own actions / maxVisible / mobileMaxVisible and the empty slot. One resolver, two callers; ⛔ no second resolver.
  • @object-ui/app-shell, RecordDetailView.tsx. On a page that declares print, the view draws a screen-only chrome row (data-record-print-chrome) at the top of the content column, inside the record's RecordContextProvider / ActionProvider: the shared chrome (presence, managed-by badge), PageHeaderActions fed the object's declared record_header / record_more actions (synthParts.headerActions; the system Edit / Share / Delete come from the context as on every record page), and the Print button. The row is outside the print page root, so the print stylesheet's existing hide rule (everything that neither is the print page, holds it, nor sits in it) hides it on paper exactly as it hid Print. The Print button moved from the absolute top-right overlay into this row so the new buttons are not drawn over the page's own top-right content.
  • A record page without print is unchanged: the absolute overlay keeps presence and the badge, and page:header draws the actions.

Mechanism readings (dispatch A1 to A3)

  • A1. The resolver is the action pipeline inside PageHeaderRenderer (containers.tsx), now the exported PageHeaderActions; the system set reaches it through RecordContext.headerSystemActions (synthSystemActions in RecordDetailView). The print chrome reaches it by rendering PageHeaderActions inside the same providers.
  • A2. Pins in RecordDetailView.printPageHeaderActions-12906.test.tsx: (a) Submit for Sign-off, Edit and the ⋯ (favorites / Share / Delete) in the chrome beside Print, Edit calling the host's onEdit; (b) Print, the toolbar and the chrome are neither inside nor around [data-print-page], the page's stylesheet carries the hide rule verbatim, and toolbar and Print share one chrome container; (c) clicking the declared type: 'api' action sends POST /api/v1/data/OBJECT/INV-1/submit through the record page's api handler, and the request equals the one the same click sends on the default (non-print) record page; (d) CONTROL: without print, no chrome, no Print, one toolbar inside the header, each action drawn once. page-header-actions-shared-12906.test.tsx pins that PageHeaderActions draws byte-identical toolbar markup (React ids blanked) to page:header for the same context and keys, the system set alone, and the empty slot.
  • Ablation (ablation-replace.mjs, anchor the PageHeaderActions line in the print chrome, deleted): anchor 1 to 0, blob c6680e738e59 to a51ab974dbe1; (a), (b), (c) red, (d) green; restore verified blob equals HEAD c6680e738e59 and git diff HEAD empty. No dist in the path: the test resolves both packages from source through the vitest alias.
  • A3. NOT MEASURED: no browser print preview was taken (no backend booted for this round).
  • "Generate PDF" (print page ④ of #8346: a record-page "generate PDF" action that calls the render service and attaches the archived PDF to the record objectstack#22270, PR feat(service-print): the print door — POST /api/v1/print/records prints a record as the caller; the showcase invoice declares Generate PDF objectstack#24194): pending, not exercised. It is a declared record_header action, so it rides the same route as Submit for Sign-off once it lands in the example.

Verification at 5741ebd

  • Closure build pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' run build: exit 0.
  • pnpm --filter @object-ui/components run type-check and pnpm --filter @object-ui/app-shell run type-check: exit 0, both echoed tsc --noEmit && tsc -p tsconfig.test.json.
  • vitest packages/components/: 404 files passed, 1 skipped; 4047 tests passed. vitest packages/app-shell/: 1555 files passed, 1 skipped; 14635 tests passed.
  • eslint narrowed to the 5 changed source and test files (--no-inline-config --format json): 5 files, 0 errors. Population: the per-package eslint . the lint workflow runs; type-aware linting is not enabled in eslint.config.js (no parserOptions.project / projectService), so this diff cannot move a verdict on an untouched file.
  • Pre-tsc battery, each 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:control-bytes, check-hand-rolled-comment-mask.mjs, check-changeset-fixed.mjs, check-changeset-presence.mjs, check:new-line-citations (0 new).

Acceptance notes

  • File surface: packages/components/src/index.ts gains the one export line pair. It is outside the dispatched surface, and it is the only way app-shell can reach the shared resolver (the package exports only its root barrel). Declared here and in the report.
  • Docs (content/docs/guide, package README) are not updated in this round; the surface did not include them.
  • objectui#11958 is the print page this builds on; it is referenced, not addressed.

The session that wrote this: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB.


Generated by Claude Code

… beside Print

A page that declares `print` holds only the printable component types and
`page:header` is not one of them, so such a record page drew none of the
record's header actions. Lift `page:header`'s action row out of
`PageHeaderRenderer` into an exported `PageHeaderActions` (the one resolver,
used by `page:header` itself), and draw it in the print page's screen-only
chrome row beside Print, outside the print page root, where the print
stylesheet hides it on paper as it hides Print.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
…geHeaderActions export

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 75 chunks) 3154.0 KB 3744.1 KB
Main entry chunk (gzip) 80.3 KB 350 KB
Entry file index-BqI_b__s.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.42KB 7.85KB
app-shell (runtime-config.js) 23.60KB 8.19KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 610.97KB 148.46KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.83KB 69.44KB
fields (index.js) 291.60KB 74.45KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.33KB 11.96KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 210.60KB 49.94KB
plugin-dashboard (index.js) 146.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 265.18KB 70.24KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.24KB 46.60KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 272.78KB 75.66KB
plugin-kanban (index.js) 63.18KB 19.45KB
plugin-list (index.js) 139.66KB 35.98KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.27KB 5.40KB
plugin-view (index.js) 94.68KB 24.36KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.23KB 39.46KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 50b55a371a34ca1dfb6307d197967f32a4077357
Local-runs: none

Inputs read: card #12906 (body, triage 6237072589, the claim 6238554187 as amended in place, the os-dev-report 6241785371); PR #12921 (body, 6-file list, net diff against main 5ec2175fdf); the head's 35 check-runs; and, to judge ①, the head's containers.tsx, RecordDetailView.tsx and page.tsx as served by GitHub at this sha (read-only, nothing built or run).

① Derived judgments

  • Public surface: the export pair is the only widening — right. packages/components/src/index.ts gains exactly export { PageHeaderActions } and export type { PageHeaderActionsProps }, both from ./renderers/layout/containers. PageHeaderRenderer stays a module-private const; no new registry key (ComponentRegistry.register calls are untouched), no spec key, no change to PRINTABLE_PAGE_COMPONENT_TYPES or any @objectstack/spec source. The diff touches no package.json, so none of the eight publish-contract fields moved. @object-ui/app-shell's own exports are unchanged (RecordDetailView keeps its props signature; the new import is internal).
  • page:header's rendering is unchanged by the lift — right, with one residual. At the head, the body of PageHeaderActions (the lifted renderHeaderActions with its declared-id resolution, location / capability / visible / hidden gates, the headerSystemActions merge and dedupe, ordering, maxVisible / mobileMaxVisible inline-vs-⋯ split, localization and dispatchHeaderAction) carries no remaining read of schema, props, designer, language, fieldOptionLabel, tObjectLabel, pageDescription or hostRefresh: the only reads it lost are the three schema keys now passed as props, and PageHeaderRenderer.renderHeaderTail passes them with the same schema?.x ?? schema?.properties?.x dual-read and the same fallbacks (3 / 1 via readMax). The lifted component returns renderHeaderActions() ?? emptySlot inside a fragment, so the DOM renderHeaderTail places is byte-identical to the old renderHeaderActions() ?? emptySlot; the refresh button (⟳, objectui#3460) stays in PageHeaderRenderer, outside the action budget, as before. The one thing that moves is the React component boundary: the action row's hooks (useId, useAction, useInlineEdit, useIsMobile, the pending-run state) now live one level down, so aria-describedby / id values from useId differ, which page-header-actions-shared-12906.test.tsx blanks before its byte comparison. That is the correct reading of "byte-for-byte": no observable change to the action set, gating, ordering or overflow.
  • One resolver, not two — right. The print chrome renders PageHeaderActions itself (RecordDetailView.tsx, the data-record-print-chrome row), inside the same RecordContextProvider (headerSystemActions={synthSystemActions}) and the same ActionProvider (same handlers={{ api, flow, script, modal }}) that wrap the page tree. The system set is not re-declared: Edit / Share / Delete arrive through the context as on every record page. The declared set is synthParts.headerActions, the same value buildDefaultPageSchema folds into the default page's page:header.actions, so a print record page and a default record page feed the one resolver from the one source (the card's test (c) pins the two surfaces send the same POST .../INV-1/submit).
  • A non-print record page is unchanged — right. pageDeclaresPrint gates both the overlay (!pageDeclaresPrint: absolute top-right with presence + badge, as before) and the new row. An assigned non-print page keeps its own page:header untouched; a default page keeps buildDefaultPageSchema's header. Test (d) (CONTROL) pins no chrome, no Print, one toolbar inside header, each action once.
  • Off paper by Print's own mechanism — right. PageRenderer writes [data-print-page] on the page root and injects page-print.ts's stylesheet, whose hide rule (asserted verbatim by test (b)) hides every element that neither is the print page, holds it, nor sits in it. The new row is a sibling preceding the page tree inside the content column (div.flex-1.overflow-auto), so it neither holds nor sits in the root: hidden, by the identical selector that hid the old Print overlay. No second @media print rule, no print:hidden class.
  • Printable set / spec / registry keys — none changed; right. Confirmed by the file list: no page.zod.ts, no page-print.ts, no registration.
  • The Print button's move (dev deviation) — right, within the card. The card asks for the actions "beside Print, hidden on paper as the Print button is"; an in-flow row that holds presence, badge, the toolbar and Print delivers exactly that and avoids drawing a toolbar over the page's own top-right content. It is gated to pageDeclaresPrint, so the overlay of every non-print page is untouched. The Print button's classes change from h-8 gap-1.5 bg-background/90 to gap-1.5 (the translucent overlay backing is no longer needed in flow); data-testid, aria-label, title and onClick are unchanged, so existing pins (objectui#11958) keep their hooks. Cosmetic, accepted.
  • Props type. PageHeaderActionsProps.actions / maxVisible / mobileMaxVisible are typed unknown, matching page:header's untyped schema reads rather than inventing a stricter contract the renderer does not enforce. Acceptable for a transition-era surface; a later tightening is a minor of its own.
  • Residual, not a blocker (no code change asked): the row renders outside PageRenderer, so a predicate scope the page itself provides (e.g. PageVariablesProvider on a print page that declares variables) is not in scope for an action's visible expression there, whereas page:header inside the page would see it. The showcase invoice and the card's three bullets do not depend on page variables; noted for the record.
  • Check-runs on the head (the gate verdicts): completed success — Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Overwrite Report, Line Citation Gate, Governed Surface Queue Guard, label; skipped — Test (coverage), Test (coverage shard), dependabot. Still in progress at review time: Type Check, Lint, Repo Gates, Build & E2E, Build Docs, Test (dist pins), Test shards 1–16 (shard 7 queued), Bundle Analysis, Live E2E (informational). Their conclusions are not waited for; the adopting seat reads them before merge. The dev report records the same families green locally at 5741ebd0c1 (type-check both packages, full vitest of both package dirs, narrowed eslint, the pre-tsc battery).

② Semver level

  • Changeset .changeset/12906-print-page-header-actions.md: @object-ui/components: minor, @object-ui/app-shell: patch. Right.
    • @object-ui/components publishes a new value export and a new type export on its root barrel: an additive public-surface widening, which is minor under this repo's policy (AGENTS.md: changes to published src/ of a fixed-group package need a changeset; major is reserved for following an @objectstack major, so even a breaking objectui change is minor — check-changeset-no-major.mjs). Nothing here is removed or renamed, so minor, not a breaking-minor note.
    • @object-ui/app-shell changes published src/ (RecordDetailView.tsx) to fix a user-visible defect with no new export: patch. Both packages are in the fixed group, so the group bumps minor together; Changeset Bump Policy and Changeset Declaration are green on the head.
    • The changeset body cites by content (test names, symbols, issue ids), no path:line; Line Citation Gate green.
  • Clause-②: line — right. The claim comment was amended in place from no to yes with the reason (the new export pair), the PR body's line 2 reads Clause-②: yes (...), and the changeset's last line restates it. All three agree with what the diff publishes, and Changeset Claim Re-read is green.

③ Boundary flags

  • open_questions: [] — nothing to answer.
  • Dev deviation 1, index.ts outside the dispatched surface: resolved — the claim was amended in place to name packages/components/src/index.ts and the one export pair, so the file is inside the surface as it now stands; declared in the PR body.
  • Deviation 2, Clause-② yes vs the claim's original no: resolved — amended in place; see ②.
  • Deviation 3, the 2h16m lock hold: a process matter for the seat, not a contract finding; no effect on the diff.
  • Deviation 4, the Print button moved into the in-flow row on print pages: accepted — judged in ①.
  • Deviation 5, docs not updated: accepted for this round, escalated as a follow-up — @object-ui/components now publishes PageHeaderActions, and the package README / content/docs/guide do not mention it. The claim excluded docs from the surface, so it is not a FAIL; the seat may file a docs card.
  • Deviation 6, vi.mock in .test.tsx under the dom project, as the copied harnesses do: accepted.
  • Card "Done when", bullet 3 (none of the actions appears in the browser's print preview) — A3 NOT MEASURED by the dev (no backend booted). The mechanism is pinned structurally (test (b): the chrome, the toolbar and Print are all outside [data-print-page], and the hide rule is present verbatim), and the selector is the one objectui#11958 measured in a browser. Escalated to the adopting seat as a residual: a one-time browser print-preview check on the showcase invoice at merge or in the next dogfood round closes the bullet as measured rather than inferred.
  • "Generate PDF" (objectstack#24194) pending upstream — noted in the PR body as the triage ruling allows; it is a declared record_header action and rides the same route; nothing to do here.

Implemented-by: claude/issue-12906-print-page-header-actions
Reviewed-by: session_01XVp19E1MYUTFM3dnkwz8gB

VERDICT: PASS


Generated by Claude Code

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