From ab36336e7626ad56da4435d7aa84b6448506b3c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8C=85=E5=91=A8=E6=B6=9B?= Date: Fri, 7 Aug 2026 23:20:34 -0700 Subject: [PATCH 1/2] test(showcase): give the inline expense grid a datetime fixture (objectui#3569) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No inline-grid child object in showcase carried a `datetime` field, so the renderer defect objectui#3569 describes could not be reproduced here at all: `showcase_expense_line` and `showcase_invoice_line` between them had exactly one temporal column, a `date`. `showcase_expense_line.incurred_at` adds the missing shape — the exact instant on a receipt — sitting deliberately NEXT TO the existing `incurred_on` date. The pair is the fixture: a renderer that folds `datetime` onto the `date` control renders the two identically, and because `` emits a bare `YYYY-MM-DD`, a user correcting the DAY writes the time out of the record. Measured on this fixture before the objectui fix: `2026-08-05T14:30:00.000Z` became `2026-08-06T00:00:00.000Z` after a day-only edit. Every seeded `incurred_at` carries a NON-ZERO time on purpose — at `00:00` a dropped time is indistinguishable from a kept one. `daysAgo(n)` is a calendar-day function resolving to UTC midnight, so the clock is added with the sub-day idiom the formula stdlib documents for this (` + duration("Nh…")`), keeping the seed relative like its neighbours. With seven editable fields the grid's six-column default budget parks `incurred_on` in the column chooser; that is the intended "personalize columns" behaviour and both columns stay reachable. Note: the backend returns full ISO for `datetime` but flattens `date` to `YYYY-MM-DD` on read and write — the separate gap tracked in objectstack-ai/objectstack#6328, not addressed here. Co-Authored-By: Claude Opus 5 --- .../src/data/objects/expense-report.object.ts | 16 +++++++++ examples/app-showcase/src/data/seed/index.ts | 35 ++++++++++++------- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/examples/app-showcase/src/data/objects/expense-report.object.ts b/examples/app-showcase/src/data/objects/expense-report.object.ts index e958c93273..10eb212be5 100644 --- a/examples/app-showcase/src/data/objects/expense-report.object.ts +++ b/examples/app-showcase/src/data/objects/expense-report.object.ts @@ -164,6 +164,22 @@ export const ExpenseLine = ObjectSchema.create({ { label: 'Rejected', value: 'rejected', color: '#EF4444' }, ], }), + /** + * The exact instant on the receipt — a meal at 19:45, a cab at 07:12. + * + * Deliberately a `datetime` sitting NEXT TO the `date` below: the two are + * the inline grid's temporal-type fixture (objectui#3569). A renderer that + * folds `datetime` onto the `date` control does not merely hide the clock — + * `` emits a bare `YYYY-MM-DD`, so a user correcting the + * DAY writes the time out of the record. Nothing in showcase could catch + * that before, because no inline-grid child carried a datetime at all. + * + * Keep both columns: the pair is what makes the distinction observable. + * With seven editable fields the grid's six-column default budget parks one + * of them in the column chooser (`incurred_on`, by declaration order) — that + * is the intended "personalize columns" behaviour, not a dropped field. + */ + incurred_at: Field.datetime({ label: 'Incurred At' }), incurred_on: Field.date({ label: 'Incurred On' }), }, }); diff --git a/examples/app-showcase/src/data/seed/index.ts b/examples/app-showcase/src/data/seed/index.ts index 91fdca7a5c..0b395113b0 100644 --- a/examples/app-showcase/src/data/seed/index.ts +++ b/examples/app-showcase/src/data/seed/index.ts @@ -388,27 +388,36 @@ const expenseReports = defineSeed(ExpenseReport, { ], }); +/** + * Every line carries BOTH temporal shapes — `incurred_on` (calendar day) and + * `incurred_at` (instant) — and every `incurred_at` has a NON-ZERO time + * component on purpose (objectui#3569). A wall clock of `00:00` would make the + * defect invisible: a renderer that drops the time would look identical to one + * that keeps it. `daysAgo(n)` is a calendar-day function resolving to UTC + * midnight, so the clock is added with the sub-day idiom the formula stdlib + * documents for exactly this — ` + duration("Nh…")`. + */ const expenseLines = defineSeed(ExpenseLine, { mode: 'upsert', externalId: 'merchant', records: [ // EXP-2001 → total 1500.50 · approved 960 · reimbursable 512 · rejected 0 · over$500 2 - { merchant: 'United Airlines', expense_report: 'EXP-2001', category: 'travel', amount: 620, billable: false, status: 'approved', incurred_on: cel`daysAgo(9)` }, - { merchant: 'Marriott Downtown', expense_report: 'EXP-2001', category: 'lodging', amount: 340, billable: false, status: 'approved', incurred_on: cel`daysAgo(8)` }, - { merchant: 'Chipotle', expense_report: 'EXP-2001', category: 'meals', amount: 28.5, billable: false, status: 'submitted', incurred_on: cel`daysAgo(8)` }, - { merchant: 'AWS', expense_report: 'EXP-2001', category: 'software', amount: 512, billable: true, status: 'submitted', incurred_on: cel`daysAgo(7)` }, + { merchant: 'United Airlines', expense_report: 'EXP-2001', category: 'travel', amount: 620, billable: false, status: 'approved', incurred_on: cel`daysAgo(9)`, incurred_at: cel`daysAgo(9) + duration("6h20m")` }, + { merchant: 'Marriott Downtown', expense_report: 'EXP-2001', category: 'lodging', amount: 340, billable: false, status: 'approved', incurred_on: cel`daysAgo(8)`, incurred_at: cel`daysAgo(8) + duration("15h05m")` }, + { merchant: 'Chipotle', expense_report: 'EXP-2001', category: 'meals', amount: 28.5, billable: false, status: 'submitted', incurred_on: cel`daysAgo(8)`, incurred_at: cel`daysAgo(8) + duration("19h45m")` }, + { merchant: 'AWS', expense_report: 'EXP-2001', category: 'software', amount: 512, billable: true, status: 'submitted', incurred_on: cel`daysAgo(7)`, incurred_at: cel`daysAgo(7) + duration("2h10m")` }, // EXP-2002 → total 917 · approved 825 · reimbursable 825 · rejected 1 · over$500 1 - { merchant: 'Delta Air Lines', expense_report: 'EXP-2002', category: 'travel', amount: 780, billable: true, status: 'approved', incurred_on: cel`daysAgo(15)` }, - { merchant: 'Uber', expense_report: 'EXP-2002', category: 'travel', amount: 45, billable: true, status: 'approved', incurred_on: cel`daysAgo(14)` }, - { merchant: 'Staples', expense_report: 'EXP-2002', category: 'supplies', amount: 92, billable: false, status: 'rejected', incurred_on: cel`daysAgo(14)` }, + { merchant: 'Delta Air Lines', expense_report: 'EXP-2002', category: 'travel', amount: 780, billable: true, status: 'approved', incurred_on: cel`daysAgo(15)`, incurred_at: cel`daysAgo(15) + duration("7h35m")` }, + { merchant: 'Uber', expense_report: 'EXP-2002', category: 'travel', amount: 45, billable: true, status: 'approved', incurred_on: cel`daysAgo(14)`, incurred_at: cel`daysAgo(14) + duration("21h50m")` }, + { merchant: 'Staples', expense_report: 'EXP-2002', category: 'supplies', amount: 92, billable: false, status: 'rejected', incurred_on: cel`daysAgo(14)`, incurred_at: cel`daysAgo(14) + duration("11h15m")` }, // EXP-2003 (draft) → total 225.75 · approved 0 · reimbursable 0 · rejected 0 · over$500 0 - { merchant: 'Hilton Garden Inn', expense_report: 'EXP-2003', category: 'lodging', amount: 210, billable: false, status: 'submitted', incurred_on: cel`daysAgo(3)` }, - { merchant: 'Starbucks', expense_report: 'EXP-2003', category: 'meals', amount: 15.75, billable: false, status: 'submitted', incurred_on: cel`daysAgo(2)` }, + { merchant: 'Hilton Garden Inn', expense_report: 'EXP-2003', category: 'lodging', amount: 210, billable: false, status: 'submitted', incurred_on: cel`daysAgo(3)`, incurred_at: cel`daysAgo(3) + duration("14h30m")` }, + { merchant: 'Starbucks', expense_report: 'EXP-2003', category: 'meals', amount: 15.75, billable: false, status: 'submitted', incurred_on: cel`daysAgo(2)`, incurred_at: cel`daysAgo(2) + duration("8h05m")` }, // EXP-DEMO → total 8900 (≥ $5000, trips the committee-quorum threshold) - { merchant: 'Dreamforce Conference', expense_report: 'EXP-DEMO', category: 'other', amount: 3200, billable: true, status: 'submitted', incurred_on: cel`daysAgo(4)` }, - { merchant: 'Lufthansa', expense_report: 'EXP-DEMO', category: 'travel', amount: 2400, billable: true, status: 'submitted', incurred_on: cel`daysAgo(4)` }, - { merchant: 'Grand Hyatt', expense_report: 'EXP-DEMO', category: 'lodging', amount: 1800, billable: true, status: 'submitted', incurred_on: cel`daysAgo(3)` }, - { merchant: 'Apple Store', expense_report: 'EXP-DEMO', category: 'software', amount: 1500, billable: true, status: 'submitted', incurred_on: cel`daysAgo(3)` }, + { merchant: 'Dreamforce Conference', expense_report: 'EXP-DEMO', category: 'other', amount: 3200, billable: true, status: 'submitted', incurred_on: cel`daysAgo(4)`, incurred_at: cel`daysAgo(4) + duration("9h40m")` }, + { merchant: 'Lufthansa', expense_report: 'EXP-DEMO', category: 'travel', amount: 2400, billable: true, status: 'submitted', incurred_on: cel`daysAgo(4)`, incurred_at: cel`daysAgo(4) + duration("5h25m")` }, + { merchant: 'Grand Hyatt', expense_report: 'EXP-DEMO', category: 'lodging', amount: 1800, billable: true, status: 'submitted', incurred_on: cel`daysAgo(3)`, incurred_at: cel`daysAgo(3) + duration("17h55m")` }, + { merchant: 'Apple Store', expense_report: 'EXP-DEMO', category: 'software', amount: 1500, billable: true, status: 'submitted', incurred_on: cel`daysAgo(3)`, incurred_at: cel`daysAgo(3) + duration("13h40m")` }, ], }); From 94161354bc75a53f86a408892057b28858b2617b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 13:36:57 +0000 Subject: [PATCH 2/2] test(showcase): translate incurred_at label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The i18n entry prepared and reverse-verified on PR #7328 (comment 5237329627), applied verbatim per the #7358 handoff: exactly one field, en + zh-CN, because check-i18n-coverage is a two-sided ratchet (451 frozen; a new untranslated label grows it, paying down old debt shrinks it — both fail). Refs #7358, objectui#3569. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MwoubC3jL271FYt9rGXwxb --- .../src/system/translations/index.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/examples/app-showcase/src/system/translations/index.ts b/examples/app-showcase/src/system/translations/index.ts index fac1cd9cc8..284ea770c2 100644 --- a/examples/app-showcase/src/system/translations/index.ts +++ b/examples/app-showcase/src/system/translations/index.ts @@ -105,6 +105,22 @@ export const ShowcaseTranslationBundle = { total: { label: 'Total' }, }, }, + // Translated at birth, like `globalActions` below and for the same + // reason: `incurred_at` is a NEW declared label (objectui#3569's inline- + // grid datetime fixture), and check-i18n-coverage freezes this example at + // its current untranslated count — a new label that skips zh-CN widens + // the debt and fails the ratchet. + // + // DELIBERATELY only this one field. The rest of the expense family + // (object labels, `incurred_on`, `merchant`, …) predates the ratchet and + // is part of the frozen baseline; translating it here too would push the + // count BELOW the baseline, which the same gate rejects as an + // un-ratcheted improvement. Paying that debt down is its own change. + showcase_expense_line: { + fields: { + incurred_at: { label: 'Incurred At' }, + }, + }, showcase_preference: { label: 'Setting', pluralLabel: 'Settings', @@ -372,6 +388,13 @@ export const ShowcaseTranslationBundle = { total: { label: '合计' }, }, }, + // See the `en` side for why this entry translates exactly ONE field and + // no more (check-i18n-coverage is a two-sided ratchet). + showcase_expense_line: { + fields: { + incurred_at: { label: '发生时间' }, + }, + }, showcase_preference: { label: '设置', pluralLabel: '设置',