docs(app-shell): the flow canvas draft node becomes a real notify node (objectui#8483) - #8855
Conversation
The "Visual flow canvas" jsonc draft taught `{ "id": "email", "type":
"action", ... }`. `action` is not a flow node kind: it is absent from
`FlowNodeAction` (objectstack `packages/spec/src/automation/flow.zod.ts`,
20 members) and from the canvas's own 18-entry `NODE_PALETTE`, so a reader
copying the draft has the node refused when the type is checked against the
live action registry at `registerFlow()`.
`notify` is the replacement the spec itself names for the retired
`email`/`slack` dispatch branches (flow.zod.ts:38-45). `script`, the other
candidate, disqualifies itself: its `config.function` is REQUIRED, and this
draft node carries no `config` at all.
`notify` turns out to have required config too, so the one-word change alone
would have swapped one refusal for another: `NotifyConfigSchema` declares
`recipients` non-optional (io-node-config.zod.ts:186) and its superRefine
(:261, message at :299) refuses a node carrying neither `title` nor
`template`. The executor parses `node.config ?? {}` through that contract at
execute time (notify-node.ts:250, parse-config.ts:99), so a config-less
`notify` node is refused. The draft therefore gains exactly those two keys
and nothing else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
PM 复核 — 通过。已翻 ready,auto-merge 已武装。⛔ PR 状态由本席翻转,你不要再写 ⛔ 先记本席的错:裁决 ① 给了两条理由,只有一条成立本席排除
(a) 是死的。 你测了,本席也去 ⇒ ⇒ 裁决 ① 的结论是对的,但它只站在 (b) 上。 ⛔ 本席不让"两条排除"的说法留在记录里。 ⭐ 为什么这条证伪不显然 —— 你给的机制,值得单独记
⇒ 一个无 config 的 notify 节点能过 ⇒ ⭐ 而这也让本席理由 (a) 的措辞双重不准:本席写的是 本席自测的读数(git 层,带亮对照)⇒ 裁决 ① 走 STEP ONE 干净:
|
Fixes #8483
What changed
One node in
packages/app-shell/README.md's Visual flow canvas jsonc draft (was line 395):actionis not a flow node kind. It is absent fromFlowNodeAction— the canonical built-in list in theobjectstacksibling repo,packages/spec/src/automation/flow.zod.ts— and absent from the canvas's own 18-entryNODE_PALETTE.FlowNodeSchema.typeis checked against the live action registry atregisterFlow()time (flow.zod.ts:14-16), so a reader who copied this draft got the node refused, not silently ignored.notifyis not an "intent match" judgement call — it is what the published contract leaves standing:script, the other candidate, disqualifies itself twice for this draft. Itsconfig.functionis REQUIRED ("a node naming no callable is flagged at build and refused at execute", flow.zod.ts:39-45) and the draft node carried noconfigat all — swapping one refusal for another is not a fix.email/slackdispatch branches say "usenotify".The premise that turned one word into three lines
The dispatch carried a premise to be measured rather than taken:
notifyhas no requiredconfigkey, so the draft node is complete once the word changes. Measured, and falsified.notifyhas required config too, in two places, both inpackages/spec/src/automation/io-node-config.zod.ts(objectstack, read at881ce2b):recipientsio-node-config.zod.ts:186—z.union([z.string(), z.array(z.string())]), no.optional(), unlike every other key onNotifyConfigSchematitleortemplateio-node-config.zod.ts:261superRefine, message at:299— "A notify node needs one content source ... Neither was given, so there is nothing to deliver."And it bites a config-less node specifically:
parseNodeConfigparsesconfig ?? {}(service-automation/src/builtin/parse-config.ts:99), thenotifyexecutor runs it at execute time (builtin/notify-node.ts:250), and{}fails both checks. The descriptor's JSON-SchemaconfigSchemadeliberately carries norequiredarray (notify-node.ts:168-175 says the execute-time guard owns enforcement), so registration passes and the refusal lands at execute — precisely the failure shapescriptwas rejected for.⇒ the draft gains exactly two keys, the minimal set that satisfies those two lines, and the jsonc block is not otherwise expanded.
The three readings (⛔ no gate is claimed to prove this)
check:doc-types' package-README leg is not switched on yet (objectui#7896, blocked behind objectui#8115), so CI is green either way. The deliverable is the change plus a measurement:The lit control is the point: the still-valid kinds survive the same probe, so the zero is a reading rather than a deleted block.
Why this site now needs NO
DOC_TYPE_EXEMPTIONSentryobjectui#8115's acceptance gives each of the 25 census sites either a
DOC_TYPE_EXEMPTIONSentry naming its vocabulary and declaration site, or a re-classification as a defect with a card. This card is that re-classification. Fixed,README.md:395names a real flow node kind, so the site disappears from the 25 instead of becoming the 25th exemption — the table ends up one row shorter, not one row longer. The card's own reason for why that matters:⛔ No
DOC_TYPE_EXEMPTIONSentry is written here — that surface is objectui#8115's, and this PR touches neither it norscripts/check-doc-component-types.mjs.Step-one reading (the card's written-in re-grading trigger), reported either way: the false reason had not landed in
DOC_TYPE_EXEMPTIONS— it was still only a candidate row in PR #8111. Read fresh fromscripts/check-doc-component-types.mjson this branch: 32 file keys in the table,0of them underpackages/or namingapp-shell,0occurrences of the stringREADMEanywhere in the table body; lit controlcontent/docs/core/app-schema.mdxpresent in the same parse. ⇒ no p2 re-grade.Clause-② declaration
Clause-②: no— docs only. The diff is one Markdown file in this repo; it touches nopackages/spec/src/**contract schema, no error-code ledger, and widens no accepted set. It moves a teaching draft toward the published contract, so nothing new is accepted anywhere.Contract-text:objectstackpackages/spec/src/automation/flow.zod.ts:38—'notify', // Outbound notification (ADR-0012) - dispatched via the messaging service; andpackages/spec/src/automation/io-node-config.zod.ts:186—recipients: z.union([z.string(), z.array(z.string())]), plus:299—'A notify node needs one content source: inline title (+ optional message), or a template ...'. Both were already published and unchanged; the README now conforms to them.Gates
node scripts/check-governed-queue-guard.mjs --test packages/app-shell/README.mdNOT GOVERNED — 1 path(s) checked against 5 governed surface(s); none matched.AGENTS.mdas lit controlGOVERNED — 1 of 2 path(s), listing onlyAGENTS.md⇒ the probe is live and the diff path is genuinely ungovernednode scripts/check-changeset-presence.mjsNo source or published contract of a released package changed in this range, so no changeset is owed.(measured:1 file(s) changed, 0 of them published source) ⇒ no changesetnode scripts/check-control-bytes.mjsOK (scanned 7045 tracked text file(s); skipped 85 binary).node scripts/check-doc-component-types.mjsEvery documented component type is registered.(188 doc files, 893 type literals — this README is not in its scan set yet, see objectui#7896)node scripts/check-doc-fence-languages.mjsevery TypeScript block in 227 document(s) is fenced ts/tsx/typescriptnode scripts/check-readme-exports.mjstype entry ./dist/index.d.ts is not on disk -- run pnpm build first, across 43 package READMEs. Control leg: the identical run on a detachedorigin/mainworktree (my change absent) gives the same exit 1 and the same 543 lines ⇒ diff-independent. CI builds first and judges it there.No package source changed, so no build closure and no package test/typecheck is owed by this diff (the changeset gate's own count —
0published source files — is that measurement).验收备注 (out of scope, not repaired here)
Two further defects in the same jsonc block, both found while measuring the node above, both left untouched per this card's boundary (the card names one site). Filed together as objectui#8854 (unlabelled, unassigned — grading is the triage seat's), not ridden along:
decidenode carries"ui": { "x": 220, "y": 180 }labelled "optional persisted canvas position", butflowNodeObject(flow.zod.ts:283) is astrictObjectthat declaresposition, notui— and the README's own prose 12 lines below says so: "a draft that still carriesuifails client-side validation and is rejected on save with a 422". A reader who copies the block still gets a 422, now for a different key.edgesomitid, whichFlowEdgeSchemadeclares required (flow.zod.ts:562,id: z.string()with no.optional()).Generated by Claude Code