Skip to content

fix(app-shell): flow 设计器不再给 wait 种子写入 spec 17 已退役的 waitEventConfig.onTimeout (#3316) - #3371

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3316-flow-wait-ontimeout-seed
Aug 5, 2026
Merged

fix(app-shell): flow 设计器不再给 wait 种子写入 spec 17 已退役的 waitEventConfig.onTimeout (#3316)#3371
yinlianghui merged 1 commit into
mainfrom
claude/issue-3316-flow-wait-ontimeout-seed

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#3316

前提核验(origin/main @ 68b6a28)

议题正文的两处快照已随 #3294 漂移,故一律以 origin/main 为准复核,前提成立:

  • previews/flow-canvas-parts.tsx:385defaultNodeExtras('wait') 仍返回 { eventType: 'timer', onTimeout: 'fail' };
  • previews/FlowCanvas.tsx:367(一键退回修订环)现为 { eventType: 'signal', signalName: 'revision', onTimeout: 'fail' } —— 与正文快照不同,但同样带着 onTimeout

对本仓 pin 的 @objectstack/spec@17.0.0-rc.2 实测,waitEventConfig.onTimeout 确为 retiredKey() 墓碑(即 z.never().optional()),不是被静默剥离而是 FlowNodeSchema.parse() 的硬错误:

current seed { eventType: 'timer', onTimeout: 'fail' } -> REJECTED
waitEventConfig.onTimeout: `waitEventConfig.onTimeout` was removed in
@objectstack/spec 17 (#4158). It had no readers at all … Delete the key.
proposed seed { eventType: 'timer' } -> ACCEPTED
revise seed { eventType: 'signal', signalName: 'revision', onTimeout: 'fail' } -> REJECTED
revise fixed { eventType: 'signal', signalName: 'revision' } -> ACCEPTED

即:设计器自己产出的形状过不了 spec 的加载校验 —— 作者只是从面板拖出一个 wait 节点、或点一次「添加退回修订环」,尚未编辑任何字段,保存/发布即 422。

改动

  1. defaultNodeExtras('wait') 改为 { waitEventConfig: { eventType: 'timer' } }(timerDuration 由作者填,表单里已是 text 字段)。
  2. FlowCanvas.addReviseLoop 的 wait 节点改为 { eventType: 'signal', signalName: 'revision' }
  3. 新增防回归测试 previews/flow-canvas-seeds.spec-parse.test.tsx

同一次退役的另外两个消费点在本仓早已跟上(i18n.ts:3519 删了标签覆盖、inspectors/flow-node-config.ts:766 删了表单字段),漏的正是产出这个键的那一块 —— 本 PR 补齐的就是这一半。

防回归测试的两条判据(刻意不同)

  • 严格兄弟块(waitEventConfig / connectorConfig / boundaryConfig):种子形状完整,判据是整节点 FlowNodeSchema.safeParse 全绿(值判定);另加一条按名点名的 [REMOVED] 墓碑断言,让下一次退役直接报出是哪个种子写了哪个键,而不是抛一个 invalid_type: never
  • config 根的种子(approval / notify / http):FlowNodeSchema.config 是宽松的 z.record(),根本看不见它们;且这些种子故意是残缺的(notify 的 title、http 的 url 留给作者填),要求整解析全绿等于断言一件种子从未承诺的事。故判据下沉到键级 —— 每个种子键必须是 spec 已发布的 ApprovalNodeConfigSchema / NotifyConfigSchema / HttpConfigSchema在生键(非墓碑)。

覆盖面按生产者而非文件划:NODE_PALETTE 全部类型 + start / end / http_request / boundary_event,加上从真实按钮点击捕获的退回修订环节点(该节点写在 React 回调里,没有可直接调用的导出;两个生产者放同一文件正是因为 #3316 的失效形状就是「只改了看得见的那个」)。

议题建议第 3 点的核验结论:无第二例

defaultNodeExtras 其余分支逐一对 spec 跑过,均通过,没有第二个退役键,因此无 out-of-scope finding:

start OK · connector_action OK · boundary_event OK · approval OK
notify OK · http OK · http_request OK · script OK · wait FAIL(本 PR 修)

顺带核实:议题建议第 1 点括注「timerDuration 接受裸数字作为毫秒」在本仓 pin 的 spec 下,该字段类型是 z.string(),裸数字 60000 被拒、字符串 '60000' 通过 —— 与 flow-node-config.ts 注释里「timeoutMs: 60000timerDuration: '60000' 是同一个 wait」一致。本 PR 不种 timerDuration,不受影响,记录于此以免下一位读者踩空。

验证

反向验证(先定方向再跑)。 预判:把 onTimeout: 'fail' 原样塞回两个种子点,应是标准红向 —— 墓碑键按「是否存在」直接判定,不经 ?? 链,故既非计数翻转也非反转。实跑与预判一致,4 条红,每条都点名了 offender:

× `wait`: a freshly added node parses as a spec FlowNode
waitEventConfig.onTimeout: `waitEventConfig.onTimeout` was removed in
@objectstack/spec 17 (#4158) … Delete the key.
× seeds no key the spec has retired in a structured sibling block
+ "wait.waitEventConfig.onTimeout (retired in spec)"
× `wait` seeds only the timer flavor — no retired timeout keys
+ "onTimeout": "fail",
× the wait node the revision-loop button creates parses as a spec FlowNode
Tests 4 failed | 21 passed (25)

恢复修复后复绿(输出里可见文件名,确认新测试真的跑了,规避 #3288 的静默路径过滤):

✓ |dom| …/previews/flow-canvas-seeds.spec-parse.test.tsx (25 tests)
Test Files 1 passed (1) Tests 25 passed (25)
vitest run packages/app-shell
Test Files 275 passed (275) Tests 2406 passed | 1 skipped (2407)
pnpm --filter @object-ui/app-shell type-check -> exit 0
pnpm --filter @object-ui/app-shell lint -> 0 errors(2160 warnings,仓库既有基线,与 #3360 记录的数字一致)
node scripts/check-changeset-no-major.mjs -> No changeset declares a `major` bump.

消费半径扫描:全仓(含 e2e / examples / apps)grep onTimeout,除注释与 CHANGELOG 外无任何 fixture 仍拼写该键 —— apps/console/src/preview-samples.ts 早已删除它,并有 preview-samples-spec-valid.test.ts 按任意深度封禁退役键。无陈旧 fixture 需要改口。

已含 changeset(@object-ui/app-shell patch)。未触碰 content/docs/releases/。文件面严格限于议题划定范围。

🤖 Generated with Claude Code

https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt


Generated by Claude Code

…node seeds (#3316)
`waitEventConfig.onTimeout` was retired in @objectstack/spec 17
(framework#4158) as a `retiredKey()` tombstone — `z.never().optional()`, a hard
`FlowNodeSchema.parse()` error rather than a silently-stripped extra. Two
designer seed sites kept writing it, so every wait node dragged from the
palette, inserted on an edge, or created by the one-click revision loop carried
a key the loader rejects and published as a 422.
- `defaultNodeExtras('wait')` seeds `{ eventType: 'timer' }`.
- `FlowCanvas.addReviseLoop` seeds `{ eventType: 'signal', signalName:
'revision' }`.
- New `flow-canvas-seeds.spec-parse.test.tsx` runs every seeded node shape
through the spec's own `FlowNodeSchema` (full-parse verdict + a
`[REMOVED]`-tombstone check for the strict sibling blocks; key-level checks
against the published node-config Zods for the deliberately-partial
`config`-rooted seeds), so the next retirement lands red at the seed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercelBot commented Aug 5, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 5, 2026 10:11am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.47KB3.09KB
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)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.25KB0.53KB
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)477.09KB104.71KB
core (index.js)2.25KB0.80KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)136.23KB34.75KB
fields (index.js)227.16KB55.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.46KB0.96KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)5.37KB1.72KB
i18n (useObjectLabel.js)26.14KB6.07KB
i18n (useSafeTranslation.js)3.26KB1.44KB
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.05KB1.53KB
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)60.83KB17.25KB
plugin-chatbot (index.js)180.09KB42.72KB
plugin-dashboard (index.js)112.01KB28.86KB
plugin-designer (index.js)210.51KB42.51KB
plugin-detail (index.js)231.11KB56.98KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)111.54KB26.97KB
plugin-gantt (index.js)162.25KB39.55KB
plugin-grid (index.js)185.08KB49.04KB
plugin-kanban (index.js)47.89KB13.18KB
plugin-list (index.js)105.02KB25.36KB
plugin-map (index.js)16.81KB5.24KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.55KB10.59KB
plugin-timeline (index.js)25.76KB7.33KB
plugin-tree (index.js)8.34KB2.82KB
plugin-view (index.js)83.67KB20.43KB
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.46KB1.21KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)0.20KB0.18KB
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

@yinlianghui
yinlianghui marked this pull request as ready for review August 5, 2026 10:16
@yinlianghui
yinlianghui added this pull request to the merge queueAug 5, 2026
Merged via the queue into main with commit 35da149Aug 5, 2026
16 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3316-flow-wait-ontimeout-seed branch August 5, 2026 10:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flow 设计器给每个新建 wait 节点种下 spec 17 已退役的 waitEventConfig.onTimeout,发布即 422

2 participants

@yinlianghui@claude