Skip to content

fix(plugin-detail): a record on a print page prints its own title field (objectui#12787) - #12814

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12787-print-title-field
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12787-print-title-field

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12787
Clause-②: no

What

On a page that declares print, record:details now prints the record's own title field. A printed invoice carries its invoice number again. Off a print page nothing changes.

Off a print page, record:details hides the row that the page header's H1 already shows: the declared name field, else the field a titleFormat resolves to, else the first title candidate that has a value. A print page refuses page:header, so on paper nothing drew that value.

packages/plugin-detail/src/renderers/record-details.tsx now reads usePrintPage() !== null with its other hooks, above every early return, the same way DetailView (PR #12785) and DetailSection (PR #12729) read it. On a print page the whole title ladder is skipped. An author's hideFields and the record:highlights dedupe still apply: their premise does not depend on a page header.

No prop, type member, export or i18n key changed.

Measured

  • Before the fix, a test with each rung on a print page showed all three rungs drop the row: the declared name field, the titleFormat match and the candidate fallback.
  • showcase_invoice (next.2) declares no nameField and no titleFormat. On the live PDF, the invoice number was hidden by the candidate fallback (the derived name), not by the declared-name branch.

Tests

New packages/plugin-detail/src/__tests__/record-details.printTitleField-12787.test.tsx, through the real renderer:

  • Inside a PrintPageProvider, the title row prints for a declared name field (INV-1010), for a titleFormat that resolves to contract_no, and for the candidate fallback.
  • CONTROL: off a print page, the same three rows stay hidden.
  • The objectui#10360 and objectui#9436 dedupe pins are unchanged and green (14 passed with the new file).

Ablations, with the fix committed:

  • The print guard forced off turns the three print rows red and leaves the controls green.
  • The guard forced on turns the three controls red.
  • Both were restored to HEAD (blob equal, git diff HEAD empty).

Results at 05aab42:

  • pnpm --filter @object-ui/plugin-detail test (run at 1eb4c26, before a type-only follow-up to the new test): 282 files passed, 1 skipped; 2692 tests passed, 18 skipped.
  • The new file at 05aab42: 6 passed.
  • Exit 0: the plugin-detail closure build, type-check, build, tsc over the package's test config, eslint on the touched files (no new warnings), check:i18n-keys, check:i18n-drift, the changeset presence check, check:new-line-citations and check:test-path-roots.

Printed page

Setup:

  • Published @objectstack/*@18.0.0-next.2 showcase backend, from the showcase source at the next.2 release commit.
  • A throwaway A4 print page on showcase_invoice whose record:details declares name, added in a scratch copy only.
  • INV-1010 printed to PDF with Chromium, on the record route and on the named page route.

PDF text read at capture:

  • Before (main f114263), record route: no INV-1010.
  • Before, named page route: no INV-1010.
  • After, record route: INVOICE NUMBER INV-1010.
  • After, named page route: INVOICE NUMBER INV-1010.

Screenshots:

The images live on branch claude/issue-12787-evidence only. This branch carries no PNG or PDF.

Acceptance notes

  • Out of scope, unchanged: page:header's refusal on a print page (the spec's contract), DetailView's own header and the auto-tabs strip on a print page, and the related list (objectui#12741).
  • The test file sits in src/__tests__/, where the claim's file surface names it. The sibling title-dedupe pins live in src/renderers/__tests__/.

Built in cloud session https://claude.ai/code/session_016Vv5qFjotEeCQN4PUiWwiT (claim 6225037918).

🤖 Generated with Claude Code

https://claude.ai/code/session_016Vv5qFjotEeCQN4PUiWwiT

…ld (objectui#12787)

record:details hid the row the page header's H1 shows (declared name
field, titleFormat match, candidate fallback). A print page refuses
page:header, so a printed invoice carried no invoice number. On a print
page, read through usePrintPage() as DetailView and DetailSection do,
the dedupe no longer applies; off a print page nothing changes.

Claude-Session: https://claude.ai/code/session_016Vv5qFjotEeCQN4PUiWwiT
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · objectui#12787 · head 05aab42 · 2026-10-16T22:21Z

Seat domain:ui#4, session session_01SJn1GEkrbbBQkhGC26wEDw. Checked against GitHub and the diff, not against the report (relayed on the card, 6225799780). The seat opened this PR from the dev's drafts (degraded channel), and the body is the dev's verbatim.

Checklist

  • Form:

    • Draft → main, assignee os-sales.
    • The first line is Fixes #12787, and the second is Clause-②: no, as the claim declares (6225037918).
    • Two commits, 1eb4c26 (the fix) and 05aab42 (type annotations in the new test's helper). Each message carries only the model-free trailer pair.
    • git merge-tree against current main is clean; the only plugin-detail change on main since the base is objectui#12741's related list, in other files. 3 files, no PNG and no PDF.
  • Rulings, as bound:

    • On a print page, record:details prints every field it declares, the title field included. isPrintPage guards the whole title ladder: the declared name field, the titleFormat match and the candidate fallback. All three are pinned.
    • The print context is read the way the siblings read it: usePrintPage() !== null, with the component's other hooks and above its only top-level return, so hook order is unconditional. No prop, type member, export or key.
    • Off a print page nothing changes: the CONTROL rows hide the title row as before, and the objectui#10360 and objectui#9436 dedupe pins are unchanged and green.
    • Out of scope, untouched: page:header's refusal, DetailView's own header and auto-tabs, the related list.
  • Mechanism assumptions: 1 is partly falsified, which is a good result. showcase_invoice at next.2 declares no nameField and no titleFormat, so on the live PDF the invoice number was hidden by the candidate fallback (the derived name), not by the declared-name branch. 2 and 3 held. 4 was measured before the fix: all three print rows were red, so every rung of the ladder drops the row on paper.

  • Evidence, read by the seat (never-merged claude/issue-12787-evidence at f9af05b):

    • before, record route and named page route: the printed card starts at Account, with no invoice number;
    • after, both routes: the first row reads INVOICE NUMBER INV-1010;
    • CONTROL, the ordinary record page after the change: the H1 reads INV-1010 and no Invoice Number row appears below it.

    Sent to the maintainer at 2026-10-16T22:21Z.

  • Tests:

    • New record-details.printTitleField-12787.test.tsx, 6 pins through the real renderer, with and without PrintPageProvider.
    • Two ablations: the guard forced off turns the three print rows red, and forced on turns the three CONTROL rows red. Each was restored to an empty git diff HEAD.
    • pnpm --filter @object-ui/plugin-detail test in full at 1eb4c26: 282 files, 2,692 tests passed, 0 failed. 05aab42 adds only type annotations to the new test; that file re-ran 6 passed.
    • The closure build, type-check (plus the package's test tsconfig), build, eslint on touched files (no new warnings), the i18n checks, changeset presence, line citations and test-path roots: all exit 0.
  • Changeset: @object-ui/plugin-detail, patch, user-facing prose.

  • Contract faces: none. RecordDetailsRenderer keeps its props; Clause-②: no holds, and no contract review is owed.

  • CI: running at this record. Landing waits for green.

Rulings

  1. The contract conflict: as dispatched. The relay is refused from the dev's container, so the seat opened the PR and relayed the report.
  2. The test's location: A, src/__tests__/, as the claim's file surface names it. Moving it beside the sibling pins in src/renderers/__tests__/ would be a pure rename and is not worth a round.
  3. The scope of "prints every field it declares": A, only the H1 title ladder. The ladder's premise is the page header's H1, which a print page refuses. An author's hideFields is an explicit contract, and record:highlights still prints on paper, so their dedupe keeps its counterpart.
  4. Out of scope, not filed: objectstack main's showcase source does not compile against the published next.2 packages (kind on a select option). That is the expected skew between main and a release, so the evidence backend used the next.2 release commit.

Implemented-by: cloud session session_016Vv5qFjotEeCQN4PUiWwiT, branch claude/issue-12787-print-title-field
Reviewed-by: seat domain:ui#4, session_01SJn1GEkrbbBQkhGC26wEDw


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 75 chunks) 3130.0 KB 3744.1 KB
Main entry chunk (gzip) 75.7 KB 350 KB
Entry file index-CnR0QnT6.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) 22.59KB 7.89KB
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) 41.15KB 10.86KB
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) 41.19KB 11.12KB
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) 603.95KB 146.15KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.96KB 69.45KB
fields (index.js) 291.36KB 74.46KB
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) 40.79KB 11.90KB
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) 201.89KB 48.13KB
plugin-dashboard (index.js) 146.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 263.70KB 69.73KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.28KB 46.60KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 270.84KB 75.07KB
plugin-kanban (index.js) 59.42KB 18.48KB
plugin-list (index.js) 135.74KB 34.83KB
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.07KB 5.33KB
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

Landing · head 05aab42 · 2026-10-16T22:33Z

  • Head unchanged since the ACCEPT: 05aab42.
  • Checks on 05aab42: 36 runs, 33 green and 3 expected skips, none red. That includes Live E2E (informational), Build & E2E, all 16 test shards, Type Check, Lint, Changeset Bump Policy and Bundle Analysis at 3,130.0 KB against the 3,744.1 KB ceiling.
  • Merge state: clean against main 655e897. The one plugin-detail change since the base is objectui#12741's related list (PR feat(plugin-detail): record:related_list reads showViewAll, and "View all" opens the parent-scoped list (objectui#12741) #12796, 8df2608), in RelatedList.tsx and record-related-list.tsx, not in record-details.tsx.
  • Commit messages: both commits carry only the model-free trailer pair, so the squash adds no model name to main.
  • Contract review: none owed. Clause-②: no; RecordDetailsRenderer keeps its props.
  • Action: mark it ready for review and enable auto-merge through the merge queue. Fixes #12787 closes the card on merge, and with it a printed invoice carries its number.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 22:34
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 16, 2026 22:34
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit 6e4f144 Oct 16, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12787-print-title-field branch October 16, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants