Skip to content

feat(example-showcase): plant the inline-grid time real-machine fixture on showcase_invoice_line - #8655

Merged
qq9340100 merged 3 commits into
mainfrom
claude/issue-6533-showcase-time-grid-fixture
Aug 14, 2026
Merged

feat(example-showcase): plant the inline-grid time real-machine fixture on showcase_invoice_line#8655
qq9340100 merged 3 commits into
mainfrom
claude/issue-6533-showcase-time-grid-fixture

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes#6533

objectui#3569 split date / datetime / time into three distinct inline-grid controls. The datetime half got a long-lived showcase fixture (showcase_expense_line.incurred_at) and a real-machine BEFORE/AFTER. The time half got unit tests only — GridField.test.tsx covers the control, but no running app has ever rendered a time column inside an inline-edit grid. showcase_field_zoo.f_time seeds a time value, but field_zoo is not a master-detail child, so it never reaches this rendering path.

This plants that fixture.

What changed

  • showcase_invoice_line.service_start — a time field, seeded '09:15' on the T&M service lines and left empty on the goods lines (the empty-cell case).
  • showcase_invoice_line.invoice.inlineColumns — the grid's column set, declared explicitly.
  • service_start translated at birth, en + zh-CN, and only that field.

Why the explicit inlineColumns is load-bearing, not decoration

This is the constraint the card exists because of, and it was measured rather than assumed.

showcase_invoice_line had exactly six editable fields — exactly DEFAULT_MAX_INLINE_COLUMNS. A seventh makes auto-derivation curate: it keeps the primary plus every required column, fills the rest by type usefulness, and marks the overflow defaultHidden. receipt is a file column, which the fill-priority table does not rank, so it sorts last and loses the tie-break. That column is objectui#2360's upload-in-grid fixture — demoting it is precisely the cost that kept time out of objectui#3569.

Declaring the set routes through hydrateColumns instead of deriveColumns (deriveMasterDetail.tsoverride.columns?.length ? hydrateColumns(...) : deriveColumns(...)), and hydrateColumns never sets defaultHidden. So all seven columns stay default-visible, and receipt's visibility stops depending on a tie-break it happens to be losing.

Entries are bare { field } on purpose: hydrateColumns fills label, type, options, lookup target, readonlyWhen / requiredWhen and the computed expression from the schema, so labels stay translatable and the columns cannot drift from the field definitions.

Measured in the running app, three states

Showcase booted on a private port with its own DB; console built at the pinned .objectui-sha (665661ab). Default-visible Line Items columns, read off the live grid:

statedefault-visible columnsReceipt
before (main)Product · Description · Qty · Unit Price · Receipt · Amountvisible
service_startwithoutinlineColumns (ablation)Product · Description · Service Start · Qty · Unit Price · Amountdemoted
service_startwithinlineColumns (this PR)Product · Description · Service Start · Qty · Unit Price · Receipt · Amountvisible

The middle row is the point: the harm is demonstrated, not asserted, and the ablation was predicted in writing before it ran. No existing fixture column loses its default visibility.

The time column, driven for real

  • Service Start renders as a native time control (clock affordance), not a date control.
  • The seeded fixture clock reads back as 09:15 AM.
  • Typing into an empty cell, saving, and reopening the form from scratch returned the typed value — a real write and re-read, not local component state.
  • Server-side read of the stored rows: service lines carry 09:15:00 / 13:40:00, goods lines null.

One real-machine fact a unit test could not have shown: the platform stores a time as HH:mm:ss, so a seeded '09:15' is at rest as '09:15:00'. That is spec-valid — field-value.zod.ts validates HH:mm[:ss] — no clock information is lost, the grid round-trips whatever is stored, and the user still sees 09:15 AM. Worth recording because the widget-level phrase "round-trips HH:mm verbatim" describes the control, not what a real app has on disk.

i18n

check-i18n-coverage freezes this example's untranslated count and fails in both directions. A new declared label must therefore be translated at birth, and only that label — translating the neighbouring line-item fields would push the count below the baseline and fail as an un-ratcheted improvement. Same rule, same shape as the incurred_at fixture, and the reasoning is left in both locale blocks.

Verification

checkresult
pnpm --filter @objectstack/example-showcase test20 files, 218 tests passed
pnpm --filter @objectstack/example-showcase typecheckexit 0
node scripts/check-type-check-coverage.mjs --re-measureexit 0, ledger unchanged
node scripts/check-i18n-coverage.mjsOK, "none new"
pnpm exec eslint (changed files)exit 0
node scripts/check-nul-bytes.mjsOK

Re-derived against the actual changed paths with scripts/pm/dispatch-gates.mjs: no check family names these paths in its own source. The i18n ratchet is not auto-surfaced by that script for an example config, so it was identified from scripts/i18n-coverage-baseline.json and run explicitly.

No changeset: @objectstack/example-showcase is private: true, so this PR releases nothing — skip-changeset.

Draft only, and staying draft: a merge freeze is in force while a release runs. Separately, check-changeset-no-major --self-test is red repo-wide because the release consumed the changeset stock; it is not this PR's doing and was deliberately not touched.


Generated by Claude Code

…ture on showcase_invoice_line (#6533)
objectui#3569 split `date` / `datetime` / `time` into three distinct inline-grid
controls. The `datetime` half got a long-lived showcase fixture
(`showcase_expense_line.incurred_at`); the `time` half got only unit tests, so
no running app has ever exercised the grid's time control.
Adds `showcase_invoice_line.service_start` (type `time`, seeded `09:15` on the
T&M service lines) and — crucially — declares the grid's `inlineColumns`
explicitly.
The explicit declaration is the point, not decoration. This line had exactly six
editable fields, i.e. exactly `DEFAULT_MAX_INLINE_COLUMNS`. A seventh column
makes auto-derivation curate, and the column that loses the tie-break is
`receipt` (a `file` column, unranked in the fill-priority table, so it sorts
last) — objectui#2360's upload-in-grid fixture. Demoting it into the column
chooser is exactly the cost that kept `time` out of objectui#3569. Declaring the
set routes through `hydrateColumns`, which never sets `defaultHidden`, so all
seven columns stay default-visible and `receipt` keeps its visibility.
Measured in the running app (showcase on a private port + DB, console built at
the pinned .objectui-sha):
before: Product | Description | Qty | Unit Price | Receipt | Amount
after: Product | Description | Service Start | Qty | Unit Price | Receipt | Amount
`Service Start` renders as a native time control; the seeded 09:15 reads back as
09:15 AM, and a value typed into an empty cell survived save + reopen.
`service_start` is translated at birth (en + zh-CN) and only that field, because
check-i18n-coverage freezes the example's untranslated count in both directions
— the same rule the `incurred_at` fixture documented.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jqe56GnYFddggeAyfkZFVz
@vercel

vercelBot commented Aug 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 14, 2026 11:54am

Request Review

@qq9340100Claude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPTed on substance — held as draft. ⛔ Not flipped, not enqueued, not armed: blocked repo-wide by the condition filed as #8654 (check-changeset-no-major --self-test requires major-declaring changesets and a .changeset/pre.json that no longer exists post-release). ⚠️ That red is not a verdict on this diff — verified individually on this PR's own fresh merge ref rather than pattern-matched from the other two, precisely because a known repo-wide red is the best available cover for a real PR-specific failure.

⭐ The budget constraint was proven by ablation, not asserted

This is the part that makes the PR trustworthy. Three real-machine states, columns read off the live grid:

statedefault-visible Line Items columnsReceipt
before (main)# · Product * · Description · Qty * · Unit Price · Receipt · Amount (6)visible
ablationservice_start added, inlineColumnsremoved# · Product * · Description · Service Start · Qty * · Unit Price · Amount⚠️demoted into the chooser
shippedservice_start + inlineColumns… Service Start · Qty * · Unit Price · Receipt · Amount (7)kept

The card's whole reason for existing is that adding a column costs another fixture's default visibility. The ablation reproduces that harm on purpose and then shows the declaration prevents it — so inlineColumns is demonstrably load-bearing rather than decorative. A run that only showed the final green state would have proven nothing about whether the protection was doing any work, and the failure direction was predicted in writing before the run.

The route choice was costed, not preferred

Route 1 (new shift-line object) was rejected on measurement I did not have when I dispatched: ~8 files (object, index export, seed, permission-set entries or an ADR-0090 grandfather stamp, nav entry, e2e nav list) plus 12–15 new label pairs that must all be translated at birth, because check-i18n-coverage freezes the example's untranslated count in both directions. Route 2 touches 3 and reuses the shape the sibling datetime fixture already established.

⚠️ And the card's own reservation — that service_start is only 勉强自然 on a T&M line — is answered by scoping rather than waved away: a services line bills hours (quantity) at a rate (unit_price), so a start clock plus that duration describes the whole billed window; ⛔ no service_end was added that would merely restate it; goods lines stay empty, which doubles as the empty-cell case.

The real-machine fixture is genuinely real

Showcase booted on private ports with its own DBs, console built at the pinned .objectui-sha. Service Start renders as a native time control (clock affordance, not a date control). Seeded 09:15 reads back as 09:15 AM; 14:05 typed into an empty cell → Update → form reopened from scratch02:05 PM. That last step is what separates a real write-and-re-read from surviving local state. Confirmed against the authoritative server read: service lines '09:15:00' / '13:40:00', goods lines null.

⇒ This is exactly the thing a unit test cannot supply, and the reason the card refused to accept unit coverage as done.

⭐ A written prediction was falsified, and reported although nothing was wrong

The dev predicted the expense-line control grid would park billable; the running app parks incurred_on. Cause: it assumed boolean fell to the unknown-fill bucket, when fieldTypeToColumnType maps it to 'select' at priority 0. Re-derived with the real mapping, the model matches the app exactly, and the invoice-line conclusion is untouched because filedoes fall to the unknown bucket.

⛔ No finding was filed, correctly — nothing is broken. Reporting a falsified prediction that changed no conclusion is the behaviour that makes the predictions which do change conclusions believable.

Also noted: the pre-existing @objectstack/lint TEST_DEBT surplus was deliberately not lowered — a package this PR never touched, per the earlier ruling that the tightening rides with #8231's remainder.

#8656 filed: 13 of 49 declared FieldType members have no Field.* builder, which is why service_start is authored in the literal { type: … } form following the showcase_field_zoo.f_time precedent.


Generated by Claude Code

@qq9340100
qq9340100 added this pull request to the merge queueAug 14, 2026
Merged via the queue into main with commit 2b50bbdAug 14, 2026
26 checks passed
@qq9340100
qq9340100 deleted the claude/issue-6533-showcase-time-grid-fixture branch August 14, 2026 12:19
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[showcase] 内嵌子表 time 列没有真机夹具:两个 inlineEdit 网格都无自然落脚点,且都已顶到 6 列预算

2 participants

@qq9340100@claude