Skip to content

[fields] the readonly datetime WIDGET faces are the same omission one type over — a bare toLocaleDateString + toLocaleTimeString pair, not formatDateTime #8209

Closed

Description

@os-justin

Found while implementing objectui#8194 (converging the four readonly date widget faces onto formatDate). Render-measured on origin/main 158d75bb3. Filing unassigned — recording, not claiming.

⛔ Deliberately NOT folded into #8194: that card's landing point is formatDate's year-dropping decision, and the maintainer ruling it follows (objectui#7620, option A) is about formatDate and nothing else. These two sites are datetime faces whose one home is formatDateTime, and picking one of ITS named faces is a second display-convention decision.

The claim

#8194's population is defined by OMISSION — a date rendered with no options bag — so it was re-enumerated by mechanism rather than by spelling. Across the 78 non-test source files of @object-ui/fields the complete set of "a Date becomes user-visible text" mechanisms is toLocaleDateString (7 occurrences), toLocaleTimeString (2), and the shared formatDate / formatDateTime family. There is no Intl.DateTimeFormat, no toDateString / toUTCString, and no date-fns / dayjs / luxon / moment anywhere in the package. (toLocaleString, 4 occurrences, is number formatting.)

That enumeration returns six bare no-bag sites, not the four #8194 names. The two extra ones are these:

site spelling
packages/fields/src/widgets/DateTimeField.tsx (readonly branch) date.toLocaleDateString(locale) and date.toLocaleTimeString(locale), joined by a space
packages/fields/src/widgets/GridField.tsx (temporalText, the datetime branch) the same pair, same join

Neither ever calls formatDateTime, which since objectui#7443 is the declared ONE home for the datetime display convention.

Measured, five locales, origin/main, TZ=UTC

Value 2026-07-04T07:00:00.000Z, rendered in the readonly DateTimeField and in the sub-grid datetime cell (both sites produce the identical string):

locale these two sites render formatDateTime 'compact' (what the datetime CELL renders)
en 7/4/2026 7:00:00 AM 7/4/2026 7:00 am
de 4.7.2026 07:00:00 4.7.2026 7:00 am
zh 2026/7/4 07:00:00 2026/7/4 上午7:00
ja 2026/7/4 7:00:00 2026/7/4 午前7:00
ar 4/7/2026 7:00:00 ص 4/7/2026 7:00 ص

(The ar forms carry U+200F marks around the date separators; they are omitted here so the table stays readable.)

The date half agrees with the compact face by coincidence — formatDateTimeCompactParts asks for { month: 'numeric', day: 'numeric', year: 'numeric' }, which is what Intl's no-options default happens to produce for these locales. The time half does not: these sites carry seconds and, where the locale writes one, a differently-cased meridiem.

⭐ One thing a triager should notice in that right-hand column rather than take from me: the compact face declares hour12: true, so the German session renders 7:00 am. date-display.ts records that as design ("the 12-hour face is its design, not a locale artefact"), which is exactly why "obviously converge on compact" is not a free move for a readonly FORM field.

Why it is worth a card

Same mechanism as objectui#7443, objectui#7620, objectui#8194 and objectui#4576 — more than one option bag for one concept (here: one bag and one absence of a bag), kept in step by nothing. #7443 gave datetime one home and converged the CELL onto it under a constraint that no pixel move elsewhere; these two widget faces are what that constraint left behind, and nothing says out loud that they opted out.

What the answer is NOT, yet

Converging them requires choosing WHICH formatDateTime face, and that is the decision:

Either way pixels move on the readonly form / detail surfaces and in the sub-grid, so it wants the treatment #7620 and #8194 got — a ruling, then a pin against a verbatim copy of the former face — not a drive-by edit. ⛔ In particular the answer is NOT "obviously compact, to match the cell": DateTimeCellRenderer reads field.format and defaults to 'compact', while these widgets read no style at all, so "match the cell" is itself a proposal to give the widgets a style vocabulary.

Grading

Observation-class. Every face is locale-correct, no data is lost or misread. objectui#8194's PR leaves both sites untouched on purpose and pins that boundary in packages/fields/src/__tests__/fields-date-widget-convention-8194.test.tsx (SCOPE FENCE block), so this card has a measured starting point rather than a prose one.

Related: objectui#8194, objectui#7443, objectui#7620, objectui#4576.

Activity

  1. os-justin commented on Sep 16, 2026

    @os-justin
    CollaboratorAuthor

    转入决策箱 —— 卡片自己写了「答案还不是什么」,余下的是一次显示约定裁决

    domain:ui 执行席 session_017bzjQQsBrHdwBy17iHVVe9,2026-09-16T10:0xZ。本卡带 pm:queue 坐在可派发队列里,但它的 ## What the answer is NOT, yet 一节明写:收敛这两个面需要先选定 formatDateTime 的哪一张脸,而那是一次显示约定决策 ⇒ 状态修为 needs-user-decision。⛔ 无代码改动,⛔ 无重新测量——卡片的五语种渲染表是 render-measured 的,且 objectui#8194 的 PR 已经用 SCOPE FENCE 把这两个站点的当前面逐字钉住,所以本卡有一个测量过的起点。

    Governing text: 四棱决策框架第三轴与第四轴(创业阶段聚焦,维护者 2026-08-04)· 本项目基本裁决原则(维护者 2026-09-10:spec 声明 > 实现 > 文档面)· objectui#7443 已裁「datetime 只有一个家 = formatDateTime」。


    维护者速读

    事情: 只读的日期时间字段,在表单/详情页上和在表格单元格里,显示得不一样 —— 而它们本该是同一个约定。

    两个站点(只读的 DateTimeField、子表格的 datetime 格子)自己拼 toLocaleDateString + toLocaleTimeString,从不走项目声明的唯一入口 formatDateTime。后果是它们带秒,而单元格不带;并且在写「上午/下午」的语种里大小写也不一致。

    语种 这两个站点 单元格
    en 7/4/2026 7:00:00 AM 7/4/2026 7:00 am
    de 4.7.2026 07:00:00 4.7.2026 7:00 am
    zh 2026/7/4 07:00:00 2026/7/4 上午7:00

    ⚠️ 没有数据错误,没有语种错误 —— 每一种写法本身都是对的。问题是一个概念两套写法,靠什么都不维系。

    两个选项 —— 收敛到 formatDateTime 的哪张脸:

    • A —— 'compact'(单元格用的那张)。 和旁边的格子长得一样。代价:丢掉现在显示的秒,并且把强制 12 小时制带进来 —— 德语会话会渲染成 7:00 am。卡片查到 date-display.ts 把这条记为有意设计,不是语种瑕疵。
    • B —— 冗长默认面(席位推荐),en 下是 Jul 4, 2026, 07:00 AM。这是所有非单元格调用方(数据集度量、甘特悬浮、数据表)今天已经拿到的那张脸。

    ⚠️ 答案不是「显然用 compact,好跟单元格一致」 —— 单元格是读 field.format 之后默认到 compact 的,而这两个 widget 根本不读任何 style。所以「跟单元格一致」本身就是一个提案:给 widget 新增一套 style 词汇。

    四棱分析:

    轴 读数
    实际业务需求 ⚠️ 未实测哪一张脸有用户拉动。但只读表单字段与密集表格是不同的显示register,这是产品判断不是技术判断 —— 这一轴是你的,不是席位的。
    项目长远合理性 objectui#7443 已裁「一个概念一个家」。两条路都收敛到那个家 ⇒ 这一轴不区分 A 和 B,只否决「维持现状」。
    防 AI 写错 ⭐ 指向 B。A 需要先给 widget 一套 style 词汇才能自圆其说,那是新增声明面;B 用的是已经存在、已经是默认的那张脸,零新增。
    创业阶段不扩散 ⭐ 同样指向 B。A 的隐含成本是一套新的 widget style 词汇 + 把一个「有意的」12 小时制约束扩散到只读表单面。零拉动 ⇒ 默认从紧。

    ⇒ 席位推荐 B(冗长默认面),三、四两轴同向,第二轴中立。⚠️ 两条路都会动像素(只读表单/详情面与子表格),所以无论选哪条,都应照 objectui#7620 / objectui#8194 的待遇:先裁决,再对旧面留一份逐字拷贝的 pin。⚠️ 另有一个 B 会顺带撞上的问题:date 的省略年份规则(objectui#7620 已答)没有人替 datetime 问过。选 B 请顺便说一句 datetime 要不要省略年份。

    你要做的: 回 A(compact,跟单元格一致)或 B(冗长默认面,推荐);选 B 请附一句 datetime 是否省略年份。

    domain:ui execution seat · session_017bzjQQsBrHdwBy17iHVVe9 · 2026-09-16T10:0xZ


    Generated by Claude Code

  2. hotlong commented on Sep 16, 2026

    @hotlong
    Contributor

    Ruling: batch #142 item 2 · letter = per register (readonly form → B default face; sub-grid cell → A compact) · maintainer 「同意」 2026-09-16T11:41Z

    Director seat, summon #24, session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented as batch #142 item 2 with the recommendation each site takes the face of its register, both through the one home formatDateTime; the maintainer's reply, verbatim: 「同意」.

    Ruling

    site register face call shape
    packages/fields/src/widgets/DateTimeField.tsx (readonly branch) form / detail default (verbose) — Jul 4, 2026, 07:00 AM in en, what every non-cell caller already gets formatDateTime(value, { locale })
    packages/fields/src/widgets/GridField.tsx (temporalText, datetime branch) grid cell 'compact' — the face the sibling datetime cell renders (DateTimeCellRenderer reads field.format, default 'compact') formatDateTime(value, { style: field.format ?? 'compact', locale })
    • Both sites stop composing toLocaleDateString + toLocaleTimeString bags; objectui#7443's 「datetime has one home」 is now true for all six bare sites the card enumerated.
    • ⛔ No new widget style vocabulary: the sub-grid cell reads field.format the way DateTimeCellRenderer does — reuse, not a new declaration.
    • datetime does not drop the current year. The year-drop is formatDate's (date-only) cell rule per objectui#7620; the verbose default face carries the year, and nothing here extends the drop to datetime.
    • Precedent basis: objectui#7443 ruling B (signature unchanged, 'compact' inside options), objectui#7620 ruling A (one face per register within the grid family), date-display.ts:264 (hour12 is the compact cell's design).

    Execution (domain:ui seat)

    • Card: needs-user-decision → pm:queue.
    • Pixels move on the readonly form/detail surface and in the sub-grid ⇒ objectui#7620 / [fields] the readonly date WIDGET faces are a third convention — a bare toLocaleDateString(locale), not formatDate #8194 treatment: first a verbatim pin of the former face (objectui#8194's SCOPE FENCE block already pins today's strings — convert it to a flip pin), then the change, then the five-locale render table re-taken.
    • Fallback recorded for the record: both sites on the default face, if the per-register split proves unimplementable at the sub-grid (it has the field, so this is not expected).

    Four-facet reading: as presented in batch #142.


    Generated by Claude Code

  3. self-assigned this
    on Sep 16, 2026
  4. os-justin commented on Sep 16, 2026

    @os-justin
    CollaboratorAuthor

    Claim: PM loop round 1
    Session: session_012EpHzwH4wTy5sd7ibkD2yq
    Branch: claude/issue-8209-datetime-widget-faces-formatdatetime
    Worktree: objectui-issue-8209
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/fields/src/widgets/DateTimeField.tsx · packages/fields/src/widgets/GridField.tsx · their pin siblings under packages/fields/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgement tier — ⛔ no path-derived mandate: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectui --tier packages/fields/src/widgets/DateTimeField.tsx packages/fields/src/widgets/GridField.tsx answers verbatim 「dispatch-gates: REFUSING — asked for 'objectstack-ai/objectui', but this checkout is 'objectstack-ai/objectstack'. … a sister repo's list is hand-derived from its own package manifest and its own workflow files.」 ⇒ per-card seat judgement, recorded as such.
    Clause-②: no
    Thread-read: 5696850133
    Serial constraints cleared: none — no open PR and no in-flight claim touches packages/fields/**. Enumerated against all 12 open objectui PRs read this fire (returned == 12); the two in-flight domain:ui cards (objectui#7945 → PR #9592, objectui#9370 → PR #9378) are governed-surface skills/** + AGENTS.md, disjoint. ⚠️ Adjacency ages inside a round — re-derive before opening the PR, ⛔ do not cite this line as fact.

    Why Clause-②: no

    Both sites stop composing a toLocaleDateString + toLocaleTimeString bag and route through formatDateTime, which objectui#7443 already declares the single home for the datetime convention. The ruling is explicit that no declaration grows: 「⛔ No new widget style vocabulary: the sub-grid cell reads field.format the way DateTimeCellRenderer does — reuse, not a new declaration.」 The accepted authoring set is unchanged; what moves is rendered text on two faces, pulling them onto a contract that already exists. That is the 拉回已声明契约 direction, which the charter says does not trip clause ②.

    ⚠️ Two fences carried from the ruling, both falsifiable and both worth checking before writing code:

    • datetime does NOT drop the current year. The year-drop belongs to formatDate (date-only) under objectui#7620. The verbose default face carries the year. ⛔ Do not extend the drop.
    • The signature stays as objectui#7443 ruling B set it ('compact' inside options), ⛔ not a new parameter.

    domain:ui execution seat · session_012EpHzwH4wTy5sd7ibkD2yq · R1 claim


    Generated by Claude Code

  5. os-justin commented on Sep 16, 2026

    @os-justin
    CollaboratorAuthor

    os-dev-report
    {
    "issue": 8209,
    "status": "done",
    "branch": "claude/issue-8209-datetime-widget-faces-formatdatetime",
    "pr": "#9638",
    "session": "session_012EpHzwH4wTy5sd7ibkD2yq",
    "premise_still_valid": true,
    "premise_detail": {
    "card_premise": "HOLDS. Re-derived on today's main ff29450, not copied: the two named sites were the only live toLocale* date mechanisms left in @object-ui/fields (the other 5 toLocaleDateString hits are objectui#8194's own comments); Intl.DateTimeFormat / toDateString / toUTCString / date-fns / dayjs / luxon all zero, with toLocaleDateString as the lit control in the same query shape; every moment hit is the English word in prose. The card's five-locale BEFORE table reproduces byte for byte on today's main.",
    "card_premise_drift": "Two measured drifts, neither load-bearing: non-test source files 81 (card said 78); toLocaleString (number formatting) 3 occurrences (card said 4).",
    "pm_assumption_a": "VERIFIED then re-taken. Table re-measured by rendering both components, TZ=UTC, on this branch. Not copied from the card.",
    "pm_assumption_b": "VERIFIED, with the two drifts above.",
    "pm_assumption_c": "FALSIFIED. temporalText is handed (type, value, locale) and has no field. Its caller holds a GridColumn, and neither GridColumn nor the published GridColumnDefinition it mirrors declares a format key. There is nothing at this surface to reuse the way DateTimeCellRenderer reuses DateTimeFieldMetadata.format.",
    "pm_assumption_d": "VERIFIED. objectui#8194's SCOPE FENCE block existed exactly as described and was converted in place."
    },
    "summary": "Both readonly datetime faces in @object-ui/fields stop composing a bare toLocaleDateString + toLocaleTimeString pair and call formatDateTime, each on the face its register was ruled: the readonly DateTimeField (form/detail) takes the verbose default, the GridField sub-grid datetime cell takes compact. The year is not dropped at either site. objectui#8194's SCOPE FENCE block is converted to the negative half of a flip pin (removed spelling kept verbatim, both sites asserted to have left it); a new datetime-widget-faces-8209.test.tsx pins which face each site arrived at, five locales plus the en literals the ruling quotes.",
    "deviations": [
    {
    "what": "The sub-grid selects the compact style as a LITERAL, not as field.format ?? compact the way the ruling's call-shape column wrote it.",
    "why": "PM assumption (c) is false: temporalText has no field, and the GridColumn / published GridColumnDefinition shape declares no format key. Spelling the authored read would require DECLARING that key, which the same ruling forbids in its next bullet (no new widget style vocabulary, reuse not a new declaration) and which is exactly the Clause-2 fork the dispatch says to stop on.",
    "effect": "The ruled FACE is delivered exactly at both registers. Only the read that would have selected it is absent, because the vocabulary it would read does not exist on this surface. No authorable key declared, no published type widened, so Clause-2: no stands as declared.",
    "fallback_not_taken": "The ruling's recorded fallback (both sites on the default face) was NOT taken. It is reserved for the per-register split proving unimplementable at the sub-grid; the split is implementable, it is the field.format READ that is not. Taking the fallback would have discarded the ruled grid-cell face over a spelling detail."
    },
    {
    "what": "Four sibling pins that asserted the former faces were updated rather than left red.",
    "why": "They pin the exact strings the ruling moved; objectui#8194 updated the DateField case in the same file the same way.",
    "effect": "datetime-widgets.test.tsx (1 case), tests/date-locale-channel.test.tsx (1 case, via a new defaultDateTimeFace helper), widgets/GridField.test.tsx (2 cases, both expectations now asked of the shared formatters)."
    }
    ],
    "render_table_five_locales": {
    "method": "rendered through I18nProvider + LocalizationProvider, TZ=UTC, value 2026-07-04T07:00:00.000Z, on this branch; the ar rows carry U+200F marks around the date separators, omitted here",
    "rows": [
    {
    "locale": "en",
    "before_both_sites": "7/4/2026 7:00:00 AM",
    "after_readonly_widget": "Jul 4, 2026, 07:00 AM",
    "after_subgrid_cell": "7/4/2026 7:00 am"
    },
    {
    "locale": "de",
    "before_both_sites": "4.7.2026 07:00:00",
    "after_readonly_widget": "4. Juli 2026, 07:00",
    "after_subgrid_cell": "4.7.2026 7:00 am"
    },
    {
    "locale": "zh",
    "before_both_sites": "2026/7/4 07:00:00",
    "after_readonly_widget": "2026年7月4日 07:00",
    "after_subgrid_cell": "2026/7/4 上午7:00"
    },
    {
    "locale": "ja",
    "before_both_sites": "2026/7/4 7:00:00",
    "after_readonly_widget": "2026年7月4日 07:00",
    "after_subgrid_cell": "2026/7/4 午前7:00"
    },
    {
    "locale": "ar",
    "before_both_sites": "4/7/2026 7:00:00 ص",
    "after_readonly_widget": "4 يوليو 2026، 07:00 ص",
    "after_subgrid_cell": "4/7/2026 7:00 ص"
    }
    ],
    "year_not_dropped": "every after cell carries 2026, both registers, all five locales",
    "unparseable_value": {
    "readonly_widget": "was the literal Invalid Date twice, now the shared em dash from formatDateTime",
    "subgrid_cell": "unchanged, still shows the raw stored string (objectui#3569)"
    }
    },
    "gates": {
    "pnpm exec vitest run packages/fields/ (full package suite, 164 files / 2845 tests)": 0,
    "pnpm exec turbo run lint --filter=@object-ui/fields --concurrency=2 (2 tasks)": 0,
    "pnpm exec turbo run type-check --filter=@object-ui/fields --concurrency=2 (11 tasks, includes tsconfig.test.json)": 0,
    "pnpm exec eslint on the 7 changed files (0 errors, 62 pre-existing warnings)": 0,
    "node scripts/check-changeset-presence.mjs": 0,
    "node scripts/check-changeset-no-major.mjs": 0,
    "node scripts/check-changeset-claims.mjs (report-only; its one finding read and answered)": 0,
    "node scripts/check-control-bytes.mjs": 0,
    "node scripts/check-new-cross-file-line-citations.mjs (0 new)": 0,
    "node scripts/check-test-path-roots.mjs": 0,
    "node scripts/check-unreferenced-sources.mjs": 0,
    "node scripts/check-comment-mask-corpus.mjs (report-only, within the held-open residue)": 0,
    "node scripts/check-governed-queue-guard.mjs --test PATHS (NOT GOVERNED)": 0
    },
    "gates_not_run": "The repo-wide pnpm test / pnpm lint farm belongs to CI. Dependent packages' suites were not run locally; the narrowing is declared below. Remote CI on PR 9638 had not converged when this report was written.",
    "narrowing_declared": "No file outside packages/fields/ names DateTimeField, GridField, LineItemsField or line-items-readonly; toLocaleTimeString occurs nowhere outside that package; a search of every dependent package's tests for a seconds-bearing clock literal returns only a debug drawer's own fixture strings and two timestamps inside a prose comment, with a positive control in the same query shape firing inside packages/fields.",
    "tests": "Ablation (reverse verification), run from the committed fix: both widget files reverted to BASE ff29450 via git checkout BASE -- PATHS; mutation proven on disk (formatDateTime occurrences 4 and 6 -> 0 and 0; DateTimeField blob hash 0adfdaf9 -> 4d78ed98, differing from the HEAD blob) rather than by the editor's exit code; pins re-run -> 25 failed / 142 passed across both files (15 in datetime-widget-faces-8209, 10 in the FENCE LIFTED block = 5 locales x 2 sites). Restored with git checkout HEAD -- PATHS under a trap on EXIT/INT/TERM using absolute paths; restore proven by git diff HEAD being empty and git status --porcelain empty, not by an exit code. With the fix in place the same two files are 167 passed / 0 failed. Heavy runs serialized through ../objectstack/scripts/pm/os-verify-lock.sh, slot objectui-8209-dev; verdicts read from the VERDICT command-exit line, package exits echoed explicitly (LINT_TASK_EXIT=0, FIELDS_SUITE_EXIT=0) rather than read after a pipe.",
    "line_budget": "n/a - no skills/** file touched, so the published-skill line ratchet does not apply. Diff size: 8 files, 469 insertions / 36 deletions.",
    "files_changed": [
    "packages/fields/src/widgets/DateTimeField.tsx",
    "packages/fields/src/widgets/GridField.tsx",
    "packages/fields/src/tests/datetime-widget-faces-8209.test.tsx (new)",
    "packages/fields/src/tests/fields-date-widget-convention-8194.test.tsx (SCOPE FENCE -> FENCE LIFTED flip)",
    "packages/fields/src/tests/date-locale-channel.test.tsx",
    "packages/fields/src/datetime-widgets.test.tsx",
    "packages/fields/src/widgets/GridField.test.tsx",
    ".changeset/8209-datetime-widget-faces.md (new, @object-ui/fields: minor, following the objectui#8194 precedent)"
    ],
    "mcp_calls": "0 - no MCP GitHub tool called, read or write. All GitHub traffic went through the REST proxy with curl.",
    "api_writes": "2 - POST /repos/objectstack-ai/objectui/pulls (draft), POST /repos//issues/8209/comments (this report). No label writes: objectui declares skip-changeset through empty-frontmatter changesets, and the dispatch budget named these two endpoints only. Reads, not writes: GET issues/8209, GET issues/8209/comments, GET pulls/9638 for the body read-back.",
    "open_questions": [],
    "out_of_scope_findings": [
    "noted, not filed: the em dash a readonly datetime widget now shows for an unparseable value is formatDateTime's own hand-rolled one, so it carries no empty-value slot and no accessible name, unlike the absent-value branch beside it. The same gap exists at every site objectui#8194 converged, so it predates this card and is the objectui#8490 / objectui#8581 class. Asserted in the new pin so the difference is visible rather than assumed away. Carrier: whatever revisits the shared empty affordance across the date family.",
    "noted, not filed (repaired in place, inside the very block this change had to edit): expectedDay in GridField.test.tsx was Intl's bare numeric default 6/17/2026, a string the datetime cell beside it CONTAINS, so since objectui#8194 moved the date column to Jun 17 that toContain assertion had been satisfied by the wrong column and would have stayed green with the date column rendering anything at all. Both faces are now named and a FIXTURE VALIDITY case pins that the three temporal faces are three distinct strings.",
    "noted, not filed: check:changeset-claims reports, report-only, that pending .changeset/6625-retire-fieldmeta-decimals.md names GridField.tsx. Read and answered: its paragraph is about .decimals / .scale member reads, this diff adds and removes none, and the scale occurrence count in GridField.tsx is unchanged at 3. The claim stands. Carrier: none needed."
    ]
    }


    Generated by Claude Code

  6. os-justin commented on Sep 16, 2026

    @os-justin
    CollaboratorAuthor

    ✅ ACCEPT — objectui#9638 · the ruled FACE delivered at both registers · ⛔ landing deferred, CI not converged

    Reviewed by the dispatching domain:ui seat, session_012EpHzwH4wTy5sd7ibkD2yq, R1. Verified against GitHub and against the tree, ⛔ not against the report's narrative.

    Checklist

    leg reading
    PR shape objectui#9638 — draft, base main, body first line Fixes #8209. ✓
    changed files 8, +469/−36, matching the report
    carrier ⛔ no needs:contract-review — correct, Clause-②: no, and it still holds (below)
    CI on head 87533098eabe 36 checks, returned == total_count (36): 29 success, 3 skipped, 4 in_progress, 0 red. ⇒ no red, not converged
    mergeable_state behind — base merge owed before landing
    mcp_calls 0; 2 REST writes, exactly the two endpoints the dispatch budgeted

    ⭐⭐ The deviation is CORRECT, and it is the ruling that was wrong

    The ruling's call-shape column reads formatDateTime(value, { style: field.format ?? 'compact', locale }), resting on the stated basis 「it has the field, so this is not expected」. That basis is false, and the dev found it. Re-derived by this seat at source, ⛔ not taken from the report:

    • packages/fields/src/widgets/GridField.tsx:356 — function temporalText(type: string | undefined, value: any, locale: string): string. No field parameter.
    • :387 — the sole call site passes three arguments: temporalText(c.type, value, locale).
    • Its caller holds a GridColumn, and the published GridColumnDefinition (exported at packages/types/src/index.ts:524, declared at packages/types/src/field-types.ts:933) declares no format key — probed and empty.

    ⇒ spelling the authored read would mean declaring a new key on a published type. That is forbidden twice over: by the ruling's own very next bullet (「⛔ No new widget style vocabulary … reuse, not a new declaration」), and by the Clause-②: no declared on this card — it is precisely the fork the dispatch says to stop on.

    ⭐ The dev took the literal 'compact' and said so. The ruled face is delivered exactly at both registers; only the read that would have selected it is absent, because the vocabulary it would read does not exist on that surface. No authorable key declared, no published type widened ⇒ Clause-②: no stands as declared, verified here rather than assumed.

    ⭐ And the recorded fallback was correctly NOT taken. The ruling reserves 「both sites on the default face」 for the per-register split proving unimplementable at the sub-grid. The split is implementable — it is the field.format read that is not. Taking the fallback would have discarded the ruled grid-cell face over a spelling detail. That is exactly the right distinction to draw, and drawing it rather than reaching for the escape hatch is the judgement this card was dispatched at default tier to get.

    The five-locale table, re-taken

    Re-measured on the branch by rendering both components (TZ=UTC, 2026-07-04T07:00:00.000Z), ⛔ not copied from the card. The year is carried in every after-cell, at both registers, in all five locales — the objectui#7620 year-drop is formatDate's and is correctly not extended to datetime. en lands on Jul 4, 2026, 07:00 AM (readonly) and 7/4/2026 7:00 am (sub-grid cell), which is the ruling's own quoted pair.

    ⭐ Premise falsifications credited — assumption (c) was mine and it was load-bearing

    • (c) FALSIFIED — the one that decided an arm, as flagged possible in the brief. I wrote 「the ruling says it does … but neither the director nor I read that function. Check it first — it decides whether the per-register split is implementable at all.」 It was checked, it was false, and the consequence was reasoned through rather than worked around.
    • (a), (b), (d) verified, with two non-load-bearing drifts honestly reported: non-test source files 81 (card said 78) and toLocaleString 3 (card said 4). ⭐ Reporting drift that changes nothing is how a census stays trustworthy.
    • The card's BEFORE table reproduces byte-for-byte on today's main, with toLocaleDateString as the lit control in the same query shape.

    ⭐ A real defect repaired in place, inside the block this change had to touch

    expectedDay in GridField.test.tsx was Intl's bare numeric default 6/17/2026 — a string the datetime cell beside it contains. Since objectui#8194 moved the date column to Jun 17, that toContain assertion had been satisfied by the wrong column, and would have stayed green with the date column rendering anything at all. Both faces are now named, and a FIXTURE VALIDITY case pins that the three temporal faces are three distinct strings. ⭐ This is 「AN ASSERTION THAT PASSES IS NOT ONE THAT WITNESSES」 caught in the wild.

    Deviations — two declared, both accepted

    1. The literal 'compact' in place of the ruled call shape — accepted, reasoned above; the ruling's premise is the thing at fault.
    2. Four sibling pins updated rather than left red — accepted: they pin the exact strings the ruling moved, and objectui#8194 set the precedent in the same file. Ablation confirms they witness: reverting both widget files to base fails 25 of 167, including all 10 of the FENCE LIFTED block (5 locales × 2 sites); with the fix, 167 passed / 0 failed. Restore proven by an empty git diff HEAD, ⛔ not by an exit code.

    Out-of-scope findings — re-judged, none owed a card

    Three, all 「noted, not filed」, and I agree with each: the em-dash empty affordance predates this card and is the objectui#8490 / #8581 class (and is now asserted in the pin rather than assumed away); the GridField.test.tsx fixture defect was repaired in place inside the block being edited, which is the correct call; and the check:changeset-claims report-only finding was read and answered on its merits (.decimals/.scale member reads, unchanged at 3).

    ⛔ Landing deferred

    Work ACCEPTED. Not landing yet: 4 checks still in_progress (no red, but not converged) and mergeable_state: behind. A base merge is owed, and this seat re-takes check-harness-current immediately before any ready-flip or enqueue — a currency reading ages inside a round.

    domain:ui execution seat · session_012EpHzwH4wTy5sd7ibkD2yq · R1 ACCEPT · readings taken 2026-09-16T16:45Z on head 87533098eabe


    Generated by Claude Code

  7. removed their assignment
    on Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seati18npackage: fieldspriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions