Skip to content

fix(showcase): the invoice print page heads its line-items table with labels, not field names - #24139

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-23985-invoice-line-item-labels
Oct 17, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-23985-invoice-line-item-labels

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23985
Clause-②: no. An example app's column headings change. No export, type or accept set moves.

What

The invoice print page (showcase_invoice_document) heads its line-items table with labels now, not raw field names. This is the card's acceptance option 1, Showcase. No contract changed.

  • examples/app-showcase/src/ui/pages/invoice-document.page.ts: the four record:line_items columns each declare a label, taken from the invoice line field's own label in invoice.object.ts:

    column was headed now headed
    description description Description
    quantity quantity Qty
    unit_price unit_price Unit Price
    amount amount Amount

    Qty is the field's own label, and it is also the header the master-detail grid hydrates for the same field. A short comment above the array says why the labels are declared here.

  • examples/app-showcase/test/invoice-document-line-items.test.ts (new): the pin.

Why the page, not the renderer

RecordLineItemsProps.columns (packages/spec/src/ui/component.zod.ts) reads: "Unlike the master-detail carriers, this block does NOT hydrate a column from the child object's field: declare label, type and options yourself — an identity-only { name } column is a text cell headed by its name." The renderer does what that says. At the pin f8267df93a, objectui's LineItemsPanel passes schema.columns through applyColumnPermissions only, and GridField heads each column with c.label || c.name. The page was the off-contract part, so the page is what changed.

Can the zh-CN print translate these headings? No.

On a zh-CN print the four headings read in English: Description, Qty, Unit Price, Amount. Before this PR they read as raw field names, which are also English. Measured on origin/main c1c0a90942:

  • No inline locale map. A column's label is InlineGridColumnSchema.label, which is z.string().optional() (packages/spec/src/data/field.zod.ts). It does not take the { en, 'zh-CN' } map that I18nLabelSchema takes.
  • No bundle key. The page's other headings are translated by name. The two record:details sections go through objects.showcase_invoice._sections.invoice / .amounts, and the page label through pages.showcase_invoice_document.label. A page component's copy goes through pages.NAME.components.ID.KEY. translatePage (packages/spec/src/system/i18n-resolver.ts) overlays only PAGE_COMPONENT_COPY_KEYS (title, description, label, placeholder, emptyText) onto properties.KEY. No key reaches properties.columns[i].label, and the extractor (packages/cli/src/utils/i18n-extract.ts) does not walk columns either.

So the labels are literal English, as the dispatch directs when neither route exists. No translation mechanism was added. The translation bundle is untouched.

The zh-CN sheet is therefore mixed: translated section headings (发票, 金额), bilingual header and footer text, and English column headings. Closing that gap takes a contract change, which is the card's option 2. That is spec-lane work and is not built here; see Acceptance notes.

Tests

All runs are on head 0de6f72ee4. origin/main is still c1c0a90942, so the pre-PR merge was a no-op.

  • New pin, alone: pnpm --filter @objectstack/example-showcase exec vitest run --maxWorkers=2 test/invoice-document-line-items.test.ts gave Test Files 1 passed (1), Tests 3 passed (3).
  • Full showcase suite: pnpm --filter @objectstack/example-showcase test gave Test Files 35 passed (35), Tests 427 passed (427).
  • Showcase typecheck: pnpm --filter @objectstack/example-showcase typecheck (tsc --noEmit) exited 0. tsc --listFiles includes the new test file (count 1).
  • Dependency closure built first: turbo run build --filter='@objectstack/example-showcase^...' --concurrency=2, 62 of 62 tasks successful.

The pin's three cases:

  1. The page carries exactly one record:line_items block. Its child object is showcase_invoice_line, and its columns are description, quantity, unit_price, amount.
  2. Each column's label equals its child field's own label, and never equals the field name. The expected value comes from InvoiceLine.fields rather than a hard-coded string, so the print stays in step with the field definitions.
  3. The block passes a full RecordLineItemsProps.safeParse, and the parsed output equals the authored properties, so label is an accepted key and is not stripped.

Ablation, one leg, through scripts/ablation-replace.mjs in wrap mode on the committed fix. The four-line columns array was put back to today's single line of identity-only columns:

  • Landed: anchor hits 1 → 0, and the blob went 1e5578e84b55 → c9acb7dcda24.
  • Result: Tests 1 failed | 2 passed (3). Case 2 failed with AssertionError: column 'description' must declare its heading: expected undefined to be 'Description'.
  • Restored: the blob after restore equals the blob at HEAD (1e5578e84b55), and git diff HEAD is empty. This was re-checked independently with git hash-object against HEAD: and a clean git status --porcelain.

The direction was red, as expected. The page is imported from source by a relative path, so no dist/ sat between the mutation and the test.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 42 commands from this change set, on the tree objectstack-ai/objectstack at 0de6f72ee4. All 42 were run, and each exit was recorded before any pipe. --ran reconciled them: "42 derived famil(ies) accounted for — 42 run, 0 NOT-MEASURED".

  • 42 of 42 exit 0 in their final reading.
  • pnpm check:dual-build-cjs-loads exited 3 on its first run, PREREQUISITE NOT MET. It reads every package's dist/, and nine packages outside the showcase closure were not built yet. That is not a measurement. check:type-check-debt, later in the same sequence, builds every package itself. The real command was then rerun and exited 0: "111 published require entry point(s) across 69 package(s) load".
  • pnpm check:i18n-coverage reports OK (14 config(s), 621 baselined untranslated string(s), none new). That confirms the literal column labels add no keys to the ratchet.
  • check:dts-closure and check:sourcemap-no-sources-content first swept only the 62 built packages. After the full build they were rerun and exited 0 again, sweeping 75 and 72 packages.

CI families are left to this PR's check-runs.

No changeset

@objectstack/example-showcase is "private": true and publishes nothing, so this PR carries the skip-changeset label. Check Changeset reads that label live; it does not read this body.

Acceptance notes

  • Contract option, not built. Option 2 of the card is the only route to translated zh-CN line-items headings. Under it, record:line_items would hydrate an identity-only column from the child field the way the master-detail carriers do, and an objectui renderer change would follow. It is spec-lane work and out of this card's scope (triage 6221505080). Nothing was filed. The report puts it to the seat as an open question.
  • Static reading, not measured on a render. In the read-only branch of GridField at the pin, a cell that is not lookup, file or temporal prints String(value). So unit_price and amount print as bare numbers. Declaring type: 'currency' would right-align them but would not format them. These columns declare no type, so they are also left-aligned. This is noted only. It was not reproduced, and no card was filed. Carrier: none.
  • No browser render on this head. The original failure was measured on the real PDF, in Chromium, by the card ③ dev of print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269 (comment 6208035645). This PR changes only declared metadata that the renderer draws verbatim, and the pin holds that metadata.

Generated by Claude Code

…labels

The invoice print page's `record:line_items` columns were identity-only
`{ name }` entries. That block draws a column exactly as declared and does
not fill one from the child field, so the printed table was headed
`description` / `quantity` / `unit_price` / `amount`. Each column now
declares the invoice line field's own label.

A column `label` is a plain string and no translation bundle key reaches a
line-items column, so the headings read in English in every locale.

The new pin holds each column to its child field's label, never the name.

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

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:services seat 2 · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-17T03:33Z

Reviewed against GitHub at head 0de6f72ee4, from the diff and the dev's report on #23985. Clause-②: no: an example app's column headings change, and no export, type or accept set moves. So this is the seat's own review of record.

Form

  • Draft, base main. The first line is Fixes #23985, then Clause-②: no.
  • 2 files, +75 / −1. skip-changeset is set as a label, which is what Check Changeset reads, because @objectstack/example-showcase is private.
  • check-governed-merges.mjs --pr 24139: NOT governed, 76 lines.

The change. The invoice print page's four record:line_items columns declare Description, Qty, Unit Price and Amount: the showcase_invoice_line fields' own labels. A comment says why this block, unlike the master-detail grid, needs them.

The zh-CN question, read by the seat.

The pin. test/invoice-document-line-items.test.ts derives each expected heading from the child field's own label, asserts it is never the field name, and parses the block against RecordLineItemsProps.

  • Ablation: the identity-only columns put back reds it ("expected undefined to be 'Description'").
  • Restore: proven by blob.

Gates and CI

  • Gates: 42 derived, 42 exit 0, reconciled with --ran.
  • The showcase passes 35 files and 427 tests. Typecheck exits 0.
  • CI at 0de6f72ee4: 0 failures. Every skipped run is on the check-expected-skips.mjs roster. Test Core (1/6) was still running at this review.

Out-of-scope findings: none.

ACCEPT. The seat marks the PR ready and enables auto-merge through the fleet relay once Test Core (1/6) is green on this head. It lands through the merge queue.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 17, 2026 03:40
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 17, 2026 03:41
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 17, 2026
Merged via the queue into main with commit 5c92b01 Oct 17, 2026
44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23985-invoice-line-item-labels branch October 17, 2026 03:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants