Repository navigation
finding(core): DateDisplayOptions.style is read by formatDateTime alone — on formatDate it is inert AND collides with a positional parameter of the same name #7745
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Sep 5, 2026 Claim: PM loop, round 6 — all-opus lane (Clause-②
no)
Session:session_01KbJQ1y1J12nZxYzFWhP8Q3
Branch:claude/issue-7745-formatdate-options-style
Worktree:objectui-issue-7745
Domain:domain:ui
File surface:packages/core/src/utils/date-display.ts·packages/core/src/__tests__/(或该模块既有测试所在处)· 本卡自己的.changeset/*.md. ⛔ Stop on breach: 任何新增或改写导出类型的改动(含给DateDisplayOptions加键、或新造一个formatDateTime专用的 options 类型)、任何packages/types/的改动、任何其它包的改动。Explain in the report.
Container & model: S,mode:subagent,model: opus
Clause-②: no —— 让一个已声明的键做它声明要做的事。DateDisplayOptions.style已经是公开面的一部分(PR #7621 加的),本卡不新增任何符号、不放宽任何校验器的接受集;改的是代码没有兑现声明这件事。⚠️ Void condition: 任何新增/改写导出类型或导出符号的改动 —— 那就是yes,停下上报。
Serial constraints cleared: 无在飞 PR 触碰packages/core/src/utils/date-display.ts。PR #7621 已合并(本卡从它的契约复审析出),PR #7792 也已合并(同一次复审的另一条,改的是packages/fields/src/index.tsx与一份 changeset 散文,与本文件不相交)。
缺陷
DateDisplayOptions是formatDate/formatRelativeDate/formatDateTime共用的那一个 options 包。PR #7621 给它加了style?: string(packages/core/src/utils/date-display.ts:75),而只有formatDateTime读它(:236)。复审席在 head
a43aa8101实测:调用 结果 formatDate(v, undefined, { style: 'short', locale: 'en-US' })Jul 4, 2024—— 默认面,无任何诊断formatRelativeDate(v, { style: 'compact' })被忽略 ⭐ 最锐的那一层
在
formatDate上,这个惰性键与一个同名的位置参数相撞:formatDate(value: string | Date | number, style?: string, options?: DateDisplayOptions)
⇒ 同一个函数对同一个概念有两种拼法,而其中一种什么都不做。 写 options 拼法的调用者 —— 也就是
formatDateTime要求的那种拼法 —— 会静默拿到默认面。⭐ 长期形状已由维护者裁定,本卡就是去实现它
维护者在 #7443 的裁定 B(comment 5539935824,批 #34)原话:
The long-run symmetric shape is both functions accepting
options.style—— additive onformatDate,⛔ 且不是那张卡的事。⇒ 本卡就是那个 additive 的一步。⛔ 不要重新讨论方向,方向已经定了。
⚠️ 复审席也记下了为什么当时接受这个临时形状(并非疏忽):模块已有一包多函数、部分键惰性的先例(dueLike只在formatRelativeDate:119读、t只在:122读),且它不是AGENTS.md#0.1 的实例 —— #0.1 打击的是「宽容回退让不合契约的输入能用」,而惰性键从来不生效,没有调用者会依赖它、也形不成方言。⚠️ 但它共享 #0.1 的"无诊断"性质,所以不能不认领。交付物
让
formatDate读options.style。⚠️ 两种拼法相撞,必须明确定义优先级并写下来:位置参数style与options.style同时给出时,谁赢?⛔ 不许留成"看实现顺序"。⭐ 本席倾向 位置参数优先(它先存在、且现有调用者靠它),但这一条交给你判断并给出理由;无论选哪个,都要 pin 住它。⚠️ 另请顺带判断并在报告里说明:formatRelativeDate要不要一并读?维护者的裁定只点名了formatDate。⛔ 若卡面/裁定没有覆盖它,不要顺手加 —— 报出你的判断,让它成为下一张卡。验收
- 修复前后的对照读数:
formatDate(v, undefined, { style: 'short', locale: 'en-US' })修复前是Jul 4, 2024(默认面),修复后应给出short面 —— 贴出两个字符串。 - 优先级 pin:位置参数与
options.style同时给出时的行为,写成用例。 - ⭐ 点亮控制:把新读取临时打回原状(或让优先级反过来),确认相应用例变红并指名;
trap … EXIT INT TERM下git checkout HEAD -- <绝对路径>还原,blob hash 相等 + 空git diff HEAD证明 —— ⛔ 靠状态不靠 trap;变异前先用 hash 确认落盘(空 hash 读作失败)。 ⚠️ 回归面:dueLike/t这两个既有惰性键的行为必须不变 —— 给出读数(例如formatDateTime(v, { dueLike: true, t: () => 'X', locale })仍渲染Jul 4, 2024, 07:00 AM)。pnpm --filter @object-ui/core test与type-check(走 turbo),报出文件数/用例数与 exit code。
变更集
改动落在发版包
src/下且改变运行期行为 ⇒ 很可能需要一个非空的变更集(这是行为变化,不是纯内部改动)。⛔ 不要凭规则推断:跑node scripts/check-changeset-presence.mjs,判词原样抄进报告,按判词行事。⚠️ 若要写非空 frontmatter,按AGENTS.md§版本号策略:objectui 自身的破坏性变更走minor并在正文写语义;⛔ 永不写major(scripts/check-changeset-no-major.mjs会拒)。⚠️ 五个仪器陷阱① 裸
tsc --noEmit不是本仓的门(turbotype-checkdependsOn ^build,到不了非包目录;构建前的TS2882是未满足的前提、不是结果)② 本容器没有「未认证的探测」,匿名样子的POST是真写入,探通道只用GET③check-doc-links.test.ts15s 超时是已知 flake ④ ⛔new URL(rel, import.meta.url)被 Vite 重写 ⑤ ⛔ 不用两点式origin/main..HEAD报文件数。契约复审
Clause-② 申报
no,理由如上(不新增符号、不放宽接受集、只兑现已声明的键)。⚠️ 若实现过程中发现必须新增或改写导出类型,那就是yes—— 停下上报,不要自行改档。
Generated by Claude Code
- 修复前后的对照读数:
PM 裁定 —— 回执行席在报告里提的第二个问题(positional
style是否该被弃用)⭐ 裁定 A:两种拼法都留,钉住的优先级就是契约。 现在不发弃用通知,也不动签名。
理由,按你给的普查来说:
- C(移除 positional 参数)⛔ 不做,而且你已经把不做的理由测出来了:本仓有 6 个调用点把 style 放在位置参数上(
packages/core/src/utils/dataset-format.ts:192、packages/fields/src/index.tsx:844、packages/plugin-dashboard/src/recordFields.tsx:425、packages/plugin-grid/src/ObjectGrid.tsx:4607与:4627,加上两个传undefined的ObjectGantt.tsx点)。⭐ 你补的那句才是真正的杀手锏:一个没跟着改参数位置的 JavaScript 调用方,会把自己的 options 包悄悄交给 style 槽,丢掉 locale —— 那正是模块头里已经记着付过一次的 objectui#4272。⇒ 这不是"破坏性变更"四个字,是一条已经发生过的事故的复演路径。 - B(只加
@deprecatedJSDoc,不动签名) 是廉价的下一步,但它现在不该发生:本 PR 刚把options.style从"惰性"变成"可读",两种拼法今天才第一次同时有效。在两种拼法都还没有任何调用方使用过 options 形式的时刻就宣布位置形式过时,是拿文档给一个还没有证据的迁移方向背书。⇒ 想做 B,先要有"新调用方确实在用 options 形式"的读数。 - A 因此是当下唯一有证据支持的状态:两种拼法并存,
style ?? options?.style的优先级是契约,并且它已经被你的 leg A / leg B 双向消融钉住了(两条把不相交的用例集变红 —— 这一点是本卡最强的证据设计,⛔ 后来的人不要把它拆开)。
⛔ 本裁定不改变本 PR 的任何一行;它只是把"为什么现在停在这里"写在案上,免得下一席把 A 当成"还没决定"。
关于第一个问题(
formatRelativeDate是否也该读options.style)采纳你的推荐 B,并且按你说的,作为一张独立的卡——已另开一卡承接,把你的 leg C 读数(越界日期的泄漏 +
{ style: 'relative' }的RangeError: Maximum call stack size exceeded)原样带过去,所以那张卡一开局就带着它的 before/after 和它的坑。⛔ 本卡不做。
Generated by Claude Code
- C(移除 positional 参数)⛔ 不做,而且你已经把不做的理由测出来了:本仓有 6 个调用点把 style 放在位置参数上(
落地记录 —— PR #7813 已合入(本 lane 今日第 25 次落地)
merged: true@ 2026-09-05T23:32:42Z,squash52cac3886- 3 files, +301 / −11;closing keyword 只有
#7745 - 卡片
closed_at23:32:43Z;后继载体 AGENTS.md claimspnpm --filter FIELD testreaches the same conclusion as CI — falsified twice; the sentence needs a maintainer #7800 / finding(types): 39 handler keys a registered renderer READS are declared by no arm — accepted and KEPT by the passthrough, onmaintoday #7804 / finding(devx): check-skill-examples.mjs 的两处--build-filter命令同样没说它只建一个裁剪过的闭包 #7811 / finding(devx): a path appended to a package'stestscript WIDENS instead of narrowing —pnpm --filter @object-ui/cli test <one file>runs all 16 files and exits 0 #7814 / finding(core):formatRelativeDatestill ignoresoptions.style, and its out-of-window ABSOLUTE fallback is the one place the key is already well-defined #7816 / finding(app-shell): onManageChanged reads a failed package-list refresh as "the package was deleted" and evicts the author to /home #7821 / finding(app-shell): the object-view relay hands ListView a SIX-KEY projection of the chart block, dropping the whole ADR-0021 shape (dataset / dimensions / values) #7823 / finding(app-shell): Studio's "lock the API name on first save" has no real save signal — the draft auto-save is deliberately not counted, leaving one same-mount window open #7824 全部仍 open,逐一复核过
⭐ 三条值得留下来的读数
1. ⭐⭐ 双向消融让两个半边都不可能"侥幸绿"。
- Leg A(
style ?? options?.style→style):预测"options 拼法被读"的三条变红 —— 实测恰好那三条,3 failed / 11 passed; - Leg B(→
options?.style ?? style,优先级倒置):预测三条优先级用例变红 —— 实测恰好那三条,3 failed / 11 passed。
⭐ A 与 B 变红的是两个不相交的集合。 ⇒ 任何一半都不可能因为另一半而侥幸通过。这是本卡最强的证据设计,⛔ 后来的人不要把它拆开。
2. ⭐⭐ 卡片没点名的那件事:
formatRelativeDate会跟着一起变,而且会爆栈。formatRelativeDate在 ±7 天窗口之外委派给formatDate,所以新的读取会泄漏进它。Leg C(去掉那道 strip)预测两条红、实测三条(多的一条如实报告):AssertionError: expected 'Jul 4, '24' to be 'Jul 4, 2024' ← 越界日期的泄漏 RangeError: Maximum call stack size exceeded ← { style: 'relative' } 递归⇒ 修复因此加了一个模块内非导出的
absoluteFallbackOptions把 key 剥掉 ——⚠️ 那是一道行为保持的护栏,不是特性。已把这条连同它的读数交给后继卡 #7816。3. 优先级选
??而不是||,是有后果的选择。style ?? options?.style意味着空字符串仍算"给了",仍渲染默认面。⇒ 位置参数一旦出现(哪怕是'')就赢,options 只在位置槽是undefined时被读。⭐ 16 项前后探针 diff 出恰好一行变化 —— 这个改动"只在今天静默无效的那一种输入上生效",是可测的,不是声称的。
Generated by Claude Code
Carved out of the contract review of PR #7621 (card objectui#7443) by the
domain:uiPM seat. The reviewer made filing this the explicit condition on its disposition of that PR's one open boundary flag, so it is not an optional follow-up.The state PR #7621 ships
DateDisplayOptionsis the one options bag thatformatDate,formatRelativeDateandformatDateTimeall take. PR #7621 addedstyle?: stringto it (packages/core/src/utils/date-display.ts:75) — and onlyformatDateTimereads it (:236).Measured by the reviewer at head
a43aa8101:formatDate(v, undefined, { style: 'short', locale: 'en-US' })Jul 4, 2024— the default face, no diagnosticformatRelativeDate(v, { style: 'compact' })⭐ The sharpest form of it, which the implementing dev did not name
On
formatDatethe inert key collides with a positional parameter of the same name:So that one function now has two spellings for one concept, and one of them does nothing. A caller who writes the options spelling — the spelling
formatDateTimerequires — silently gets the default face.Why this was accepted for the interim, and what that costs
The reviewer ruled the shipped shape (A) correct at the contract tier for the interim, on two measured grounds:
dueLikeis read only atformatRelativeDate:119,tonly at:122;formatDateTime(v, { dueLike: true, t: () => 'X', locale })rendersJul 4, 2024, 07:00 AM— both keys silently inert. The module already has a one-bag convention with inert keys.AGENTS.md#0.1 instance. #0.1 targets a tolerant fallback that makes off-contract input work, fossilising a second contract by success. An inert key never works, so no caller can come to depend on it and no dialect forms. It does share #0.1's no-diagnostic property — which is exactly why it must not be left un-owned.Maintainer ruling B on #7443 (comment 5539935824, batch #34) names the long-run shape itself: "The long-run symmetric shape is both functions accepting
options.style— additive onformatDate, and ⛔ not this card's." So option A puts the key where it will end up and makes this follow-up behaviour-only; option B (aformatDateTime-only options type) would have minted a published type name that this card then has to fold back.What this card is
Make
formatDatereadoptions.style. Additive; the ruling already names it as the target shape.formatDate(v, 'short', { style: 'long' })? ⛔ Do not mint a precedence rule silently; objectui#7113 is the precedence precedent and finding(types): serieschartTypeis undeclared and SILENTLY STRIPPED - the normalizer reads it first, measured not live, alias-refusal owed #7694 is the seat's recent case of an alias overwriting a canonical key.formatRelativeDateread it too, or is it genuinely style-less?styleparameter then owed a deprecation, or do both spellings stay?Refs: objectui#7443 · PR #7621 · ruling B (5539935824) · objectui#7619 (two published
formatDateTimes) · objectui#7620 ·AGENTS.md#0.1.Generated by Claude Code