Skip to content

fix(app-shell): datetime action 参数提交带显式时区的 ISO instant - #3930

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-os5061-datetime-param-zone
Aug 9, 2026
Merged

fix(app-shell): datetime action 参数提交带显式时区的 ISO instant#3930
yinlianghui merged 2 commits into
mainfrom
claude/issue-os5061-datetime-param-zone

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixesobjectstack-ai/objectstack#5061

问题

声明 type: 'datetime' 参数的 action 在 Console 上完全不可用 —— 没有任何用户能输入的值可以通过校验。

对话框把参数渲染成 datetime-local(天然无时区),serializeParamValues 在提交前又把值转回该控件自己的无时区墙上时钟(如 2026-08-10T15:00)—— 而这恰好是平台 datetime 值契约唯一拒绝的形状。自 17.0 起 dispatcher 会在 handler 之前按 action 声明校验 params bag(ADR-0104 D2,validateActionParamsInstantValueSchema),于是每一次 UI 提交都拿到:

HTTP 400 VALIDATION_ERROR
Action param "start" (datetime): expected an ISO-8601 instant with explicit zone (e.g. 2026-03-15T14:30:00.000Z)

渲染器与校验器要的形状不相交,app 作者在两者之间没有任何可作用的缝隙。

修法:是删除,不是新增转换

DateTimeField 自 objectui#3127 / objectui#3565 起就是两向 ISO 规范的 —— 读入记录的 ISO instant,交回 ISO instant(秒、毫秒、时区都在)。也就是说 widget 交给对话框的值本来就满足契约,提交边界不需要任何转换。

objectui#3565 当时为了在修显示 bug 时保持线上形状逐字节不变而加了这段回转,并在自己的 commit message 里点名了后续:「moving action params onto ISO is a contract change of its own」。本 PR 就是那个 contract change,它只是把回转删掉。

顺带得到的性质:

  • 幂等 —— 已带时区的值原样通过,+08:00 偏移逐字节存活,不会被重新推导后再截到分钟;
  • 空值安全 —— 空串/未填原样通过,不会产生 Invalid Date(校验器把空串视为「缺失」而非「非法」);
  • 秒与毫秒 —— 由 toISOString() 天然补全;
  • date 参数不受影响 —— 它的契约是日历日,加时区反而会让格林尼治以西整体错一天。

刻意兜的一种形状

作者手写成无时区墙上时钟的 defaultValue。这是歧义元数据(按谁的时区?),在渲染器里归一化会让同一份元数据对不同用户产生不同 instant;更糟的是造成分裂 —— UI 里「能用了」,而 REST / MCP 提交同一个字面量依旧 400,这是最难排查的一种不一致。按 contract-first(AGENTS.md #0.1)正确的位置是生产端在授权期被拒绝,已另开 objectstack-ai/objectstack#6970(该洞不限于 datetime:同样允许 number 参数默认值写成 'abc')。本 PR 用一条测试把「渲染器不掩盖它」钉住。

测试

原来那条 render proof 是整条替换而不是改写字面量的:它断言产出等于 2026-07-20T14:30,一直是绿的,而功能 100% 坏着 —— 因为它钉的形状正是无人接受的那一个。现在它驱动真实 widget,然后把对话框 resolve 出来的 bag 交给真的validateActionParams(即产生 issue 里那条 400 的同一个函数)判定。同时在 packages/fields 补了生产端的钉子:fromDateTimeInputValue 的产出必须被 InstantValueSchema 接受。

paramValueShape.test.ts 的样例值改了,但注释里说明它是文档而非守卫 —— 新旧字符串都归类成同一个 'string' tag,那一行本来就抓不到这个缺陷。

反向验证(方向为事先预测的「红」):把删掉的回转分支放回去,三条新断言转红,报错文案与 issue 里的 400 逐字一致:

× datetime param emits a zoned ISO instant the platform validator accepts
× passes the widget ISO instant straight through, so the platform accepts it
× is idempotent for a value that already carries a zone — no second conversion
AssertionError: expected [ { param: 'when', …(2) } ] to deeply equal []
+ "message": "Action param \"when\" (datetime): expected an ISO-8601 instant with explicit zone (e.g. 2026-03-15T14:30:00.000Z)"
Tests 3 failed | 33 passed (36)

时区形状的断言全部写成任意时区成立(仓库既有约定,见 nativeDateValue.test.ts 的说明:只在 UTC 下成立的测试会在 CI 绿着,而缺陷对每一个东西向用户都活着)。三个时区各跑一遍:

命令结果
TZ=Asia/Shanghai vitest run …/ActionParamDialog.test.tsx …/nativeDateValue.test.ts46 passed
TZ=UTC 同上46 passed
TZ=America/Los_Angeles 同上46 passed
TZ=Asia/Shanghai vitest run packages/app-shell/src/utils/19 files / 311 passed
TZ=UTC vitest run packages/app-shell/src/utils/ packages/fields/src/widgets/63 files / 739 passed
参数提交路径(ActionParamDialog + uploading + useConsoleActionRuntime + datetime-widgets + plugin-grid bulk param)6 files / 109 passed
turbo run type-check --concurrency=278 successful, 78 total
pnpm run check:control-bytesOK(3808 文件)
pnpm run changeset:checkOK(fixed 组齐全,无 major)
eslint(改动文件)0 errors(仅既有 react-refresh warning)

ADR-0059 两向约定核对

按任务要求核对了 objectui#3127 的反方向(其修复已随 objectui#3565 落地),把它折叠进本单:

  • 记录表单:读 ISO -> 控件显示本地 -> 写回 ISO,单一基准;
  • action 参数:无「读」,写 = widget 的 ISO。

本 PR 之前,action 参数是唯一跑在另一个基准上的表面;之后两向都以 ISO instant 为规范形式,本地墙上时钟只存在于控件内部。这是让 ADR-0059 更一致,而不是引入新分歧。

变更文件

  • packages/app-shell/src/views/ActionParamDialog.tsx —— 删掉 datetime 回转分支与 toDateTimeInputValue 导入,注释改写为记录真实契约与「为什么不兜 defaultValue」
  • packages/app-shell/src/utils/paramValueShape.ts —— datetime 的契约说明改为带显式时区的 ISO instant
  • packages/fields/src/index.tsx —— 导出注释里已过时的理由(原文写着「app-shell 的契约是无时区墙上时钟」)
  • packages/app-shell/README.mddocs/adr/0059-action-params-shared-field-widgets.md —— 契约表
  • 测试:ActionParamDialog.test.tsx(替换 + 新增)、paramValueShape.test.ts(样例)、packages/fields/src/widgets/nativeDateValue.test.ts(生产端钉子)
  • .changeset/action-param-datetime-zoned-instant-os5061.md —— patch @object-ui/app-shell

🤖 Generated with Claude Code

https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt


Generated by Claude Code

An action declaring a `type: 'datetime'` param was unusable from the Console:
no value a user could pick could pass validation. The dialog renders the param
as `datetime-local` (zone-less by nature) and `serializeParamValues` then
converted the value back to that control's naive wall clock — the one shape the
platform's `datetime` value contract rejects. Since 17.0 the dispatcher
validates a params bag against the action's declaration before the handler runs
(ADR-0104 D2, `validateActionParams` -> `InstantValueSchema`), so every UI
submission earned a 400: "expected an ISO-8601 instant with explicit zone".
Renderer and validator wanted disjoint shapes and the app author had no seam
between them.
The fix is a removal, not a conversion. `DateTimeField` has been ISO-canonical
on both sides since objectui#3127/#3565 — it takes the record's ISO instant in
and hands an ISO instant back out, seconds, milliseconds and zone included — so
the widget's value already satisfies the contract. #3565 added the
back-conversion to keep the wire shape byte-identical while fixing a display
bug and named the follow-up in its own commit message: moving action params onto
ISO is a contract change of its own. This is that change. Dropping the branch
also makes the boundary idempotent for an already-zoned value (a `+08:00`
offset survives byte-for-byte instead of being re-derived and re-cut to the
minute) and leaves an empty or unfilled param alone.
Deliberately NOT normalized: an authored `defaultValue` written as a zone-less
wall clock. That is ambiguous metadata (whose zone?), and coercing it in the
renderer would make it "work" in the UI while the identical literal kept 400ing
from REST/MCP. It stays loud until the spec validates a param default against
the param's own value contract — filed as objectstack#6970, which is not
datetime-specific (the same hole lets a `number` param default to 'abc').
The render proof that pinned the old shape was replaced rather than re-spelled:
it asserted `2026-07-20T14:30` and was green while the feature was 100% broken,
because the shape it pinned is the one shape nothing accepts. It now drives the
real widget and asks the real `validateActionParams` — the exact function that
produced the 400 — whether the resolved bag is acceptable. Reverse-verified:
restoring the deleted branch turns three of the new assertions red with that
same 400 message. The datetime assertions hold in every timezone (run under
Asia/Shanghai, UTC and America/Los_Angeles), since a zone-shaped test that only
holds in UTC goes green on CI while the defect is live for every user east or
west of it.
The `paramValueShape` sample row is re-spelled but noted as documentation, not a
guard: both the old and new strings classify to the same `'string'` tag, so that
row could not have caught this.
Fixesobjectstack-ai/objectstack#5061
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercelBot commented Aug 9, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 9, 2026 6:52am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests labels Aug 9, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)28.1 KB350 KB
Entry fileindex-5gt3BdY-.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.66KB3.13KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)7.57KB2.97KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)22.10KB4.37KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)482.53KB106.37KB
core (index.js)2.96KB1.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)139.61KB35.99KB
fields (index.js)231.00KB56.76KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.65KB1.06KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)9.48KB3.27KB
i18n (useObjectLabel.js)27.59KB6.63KB
i18n (useSafeTranslation.js)4.52KB1.96KB
layout (index.js)38.53KB10.71KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)44.98KB12.37KB
plugin-charts (index.js)61.04KB17.31KB
plugin-chatbot (index.js)180.33KB42.79KB
plugin-dashboard (index.js)117.21KB30.27KB
plugin-designer (index.js)210.51KB42.51KB
plugin-detail (index.js)236.63KB59.02KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)112.10KB27.10KB
plugin-gantt (index.js)162.55KB39.57KB
plugin-grid (index.js)187.63KB49.66KB
plugin-kanban (index.js)48.30KB13.28KB
plugin-list (index.js)108.07KB26.13KB
plugin-map (index.js)16.81KB5.24KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.58KB10.58KB
plugin-timeline (index.js)25.76KB7.33KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.03KB20.55KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)19.28KB6.38KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.02KB0.55KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)2.71KB1.34KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…by spec's sentence
The "zone-less authored default is still rejected" case pinned
`validateActionParams`' full message string, which is owned by
`@objectstack/spec` and reachable here only through a versioned dependency. A
reword upstream would turn this repo's CI red for a reason that has nothing to
do with the renderer. Assert the verdict structurally (`param` + `code`) plus
the one phrase that identifies the rule, so the test still says which contract
rejected the value without locking a cross-repo string.
Refs objectstack-ai/objectstack#5061
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)28.1 KB350 KB
Entry fileindex-DNe5UGio.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.66KB3.13KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)7.57KB2.97KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)22.10KB4.37KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)482.53KB106.37KB
core (index.js)3.00KB1.14KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)139.61KB35.99KB
fields (index.js)231.13KB56.80KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.65KB1.06KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)9.48KB3.27KB
i18n (useObjectLabel.js)27.59KB6.63KB
i18n (useSafeTranslation.js)4.52KB1.96KB
layout (index.js)38.53KB10.71KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)44.98KB12.37KB
plugin-charts (index.js)61.04KB17.31KB
plugin-chatbot (index.js)180.33KB42.79KB
plugin-dashboard (index.js)117.21KB30.27KB
plugin-designer (index.js)210.51KB42.51KB
plugin-detail (index.js)236.63KB59.02KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)112.10KB27.10KB
plugin-gantt (index.js)162.55KB39.57KB
plugin-grid (index.js)187.63KB49.66KB
plugin-kanban (index.js)48.30KB13.28KB
plugin-list (index.js)109.67KB26.55KB
plugin-map (index.js)16.81KB5.24KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.58KB10.58KB
plugin-timeline (index.js)25.76KB7.33KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.03KB20.55KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)20.15KB6.72KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.02KB0.55KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)2.71KB1.34KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

验收通过(objectui 分片 PM,session session_01GTRjn8xBqp75dk7kFupVRt)objectstack-ai/objectstack#5061(v17)

实物核验:head 54fab2df2,两提交 9 文件。CI 亲读终态:20 检查全 completed、0 失败。

验收要点:

  • 修法是删除,归因比 issue 更准:400 的直接成因是 objectui#3565 在 serializeParamValues 里把 DateTimeField 已经正确的带时区 ISO instant 主动回转成无时区墙钟(校验器唯一拒绝的形状),且 fix(fields): 编辑弹窗 datetime/date 字段回显存量值 #3565 提交信息自己预告了本次契约变更。删除回转分支后,幂等/空值/秒毫秒补全/date 型四个边界由「不做转换」天然满足,零新逻辑。
  • oracle 选择正确:测试直接以装好的 spec 的 validateActionParams/InstantValueSchema 为判据(错误文案与 issue 的 400 逐字锚定),而非重写正则;三时区(Asia/Shanghai / UTC / America/Los_Angeles)矩阵按仓库既有约定断言「任意时区成立」。
  • 刻意不兜作者手写无时区 defaultValue 的裁量采纳:渲染器归一化会让同一份元数据对不同用户产生不同 instant,并造成「UI 能用、REST/MCP 仍 400」的分裂;拒绝理由已写进代码注释并立为 objectstack#6970(spec 的 defaultValue 是 z.unknown(),授权期静默、提交期 400 且报错不指真因 —— 不限 datetime)。有一条测试钉住渲染器不掩盖它。
  • ADR-0059 两向核对完成:本 PR 后 action 参数与记录编辑同以 ISO instant 为规范形式;[console] 记录编辑弹窗 datetime 字段不回显存量值:input[type=datetime-local] 恒为空(ISO 值未转 local 格式),用户误以为日期丢了 #3127 未被折叠。

范围外 finding objectstack#6970 立单规范(domain:spec 路由),冻结期不派,归 spec 车道分诊。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 9, 2026 07:01
@yinlianghui
yinlianghui added this pull request to the merge queueAug 9, 2026
Merged via the queue into main with commit d518a90Aug 9, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-os5061-datetime-param-zone branch August 9, 2026 07:01
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationtests

Projects

None yet

2 participants

@yinlianghui@claude