feat(app-shell): the flow designer marks the node config keys the spec now requires, and pins that each unconfigured seed is a located save error (objectui#10948) - #11247
Conversation
…stalled spec refuses the node without (objectui#10948) Spec 17.5.0 refuses at authoring a flow node whose executor could not run it: a required config key left out, a decision branch with no label, a screen field with no name, a connector_action naming no connector/action. The designer's live FlowSchema pass already locates each at the node's config path; this marks the same keys in the inspector before the author reaches that error, with SchemaForm's required marker idiom. The source is the installed spec, asked of the node as it stands (flow-required-keys.ts): the key is removed from a copy of the node and handed to the judges the flow parse runs (flowNodeConfigRefusals, the predicate-slot walk, FlowNodeSchema). No required-key list lives here. Pins: every seeded kind's located save error and a clean control flow (the measurement), a blank branch label / screen field name authored through the real row editor, and the markers per seeded kind as an equality over the drawn labels. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
…read through, and read the end message label off the field table (objectui#10948) Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ 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
|
Contract reviewServed-tier: Read at 2026-09-30T17:41Z: card objectui#10948 (body and all five comments, the dev report ① Derived judgments
② Semver level
③ Boundary flagsDev report
The four Acceptance-notes observations, each judged:
Not flagged by the dev, noted here: the app-shell README's Implemented-by: VERDICT: PASS Generated by Claude Code |
…ckage README and the flow-designer guide (objectui#10948) The README's FlowNodeInspector paragraph names the marker, its source (the spec's own judges, asked through flow-required-keys.ts, no second list) and the aria-required it sets on the controls it renders itself. The guide's node-inspector paragraph gains the same fact in author terms, and drops two sentences the bump and an earlier removal had made false: that a freshly dropped node is never in a broken state, and that the inspector shows a Description field. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Contract reviewServed-tier: Read at 2026-09-30T18:06Z: card objectui#10948 (body and every comment, the dev reports ① Derived judgments
② Semver level
③ Boundary flagsReports
The four Acceptance-notes observations, each judged:
The README note of record Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #10948
Clause-②: no
What this does
The flow node inspector now marks the config keys the installed
@objectstack/spec(17.5.0) refuses a node without, so an author sees the requirement before the save-time error names it. The marker is the metadata form's own required marker (the*withdata-required-markerthatSchemaForm'sFieldRowdraws), reused through a newRequiredMarkeratom ininspectors/_shared.tsx. No new visual idiom.Source of requiredness: the installed spec, asked at render time. There is no second list.
inspectors/flow-required-keys.tsprobes the node as it stands: it removes the key from a copy and hands the copy to the judges the flow parse runs. The key is marked exactly when one of them then names it.flowNodeConfigRefusalsis the ONE config judge thatFlowSchema.parse,registerFlowandobjectstack validateshare ([finding] a decision branch with no label registers and validates clean, then at run time the decision takes EVERY out-edge; a non-object conditions element also registers objectstack#20316). It covers a key the executor contract requires, and a decision branchlabel.resolveFlowNodeExpressions+predicateSlotRefusalis the flow parse's predicate-slot walk. It covers a decision branchexpression, which the ledger marksrequired.FlowNodeSchemais the node contract. It covers theconnectorConfig/waitEventConfig/boundaryConfigblocks and theendnode's config.Because the answer comes from the node's own configuration, a rule-dependent requirement is marked only while it applies:
notifyrequirestitleonly while it has notemplate;looprequirescollectiononce it has a body;endrequiresmessage.The marker is presence only. The spec also refuses a BLANK branch label or connector id, but that stays the save-time error's job.
What gets marked, measured (pinned as an equality over the drawn labels, so a pin fails both for a missing marker and for an extra one):
aria-requiredgoes on each control the inspector owns: text / textarea / expression inputs, selects, numbers, and a row's scalar cells. The marker itself isaria-hidden, as inSchemaForm.Measure first (restart steps 1 and 2 of the triage)
Measured on the installed 17.5.0, and kept as a regression pin:
previews/flow-canvas-seeds.saveErrors-10948.test.tsx.17.5.0 carries fix(spec)!: refuse a connector_action node its executor cannot dispatch — no connectorConfig block, or a blank connectorId / actionId — at all three doors (#20418) objectstack#20453. Its CHANGELOG
## 17.5.0section lists2304b16(the [finding] aconnector_actionflow node with noconnectorConfigpasses all three build doors and fails every run objectstack#20418 connector refusal) beside7dc45eb([finding] a decision branch with no label registers and validates clean, then at run time the decision takes EVERY out-edge; a non-object conditions element also registers objectstack#20316). The live pass refuses the seeded blankconnectorConfigatnodes.0.connectorConfig.connectorIdand.actionId.Each freshly added node of each seeded kind is a located error. The live
FlowSchemapass (validateMetadataDraft('flow', …)) reports it at the node's config path, andbuildFlowProblemsputs it on that node's canvas badge, never on the flow as a whole:nodes.0.config.objectNamefor the four CRUD kinds;…config.urlforhttp;…config.titlefornotify;…config.functionforscript;…config.flowNameforsubflow;…config.collectionand…config.flowNameformap;…config.branchesforparallel;…config.tryfortry_catch;connectorConfigids forconnector_action.decision,loop,assignment,approval,screen,waitandendseeds save clean.Control: the same kinds, configured, make a flow that saves clean (
ok: true, no issues).A blank branch label and a blank screen field name, typed through the inspector's real row editor, are committed without the key.
FlowObjectListField'srowsToListdrops a blank cell;FlowStringListField'srowsToListis the primitive-list one and is not involved. They are refused atnodes.0.config.conditions.0.labelandnodes.0.config.fields.0.name.Step 2, an unsaved draft kept at all: not a regression. The live pass is advisory.
ResourceEditPagegates no save door (button, ⌘S, autosave) on it, as pinned byResourceEditPage.schemaAdvisory.test.tsx(re-run green on this head), so the draft stays in the editor with located errors.Mechanism assumption 3 holds. The per-seed ratchet
flow-canvas-seeds.spec-parse.test.tsx(FlowNodeSchema.safeParseper seed) stays green: 25/25. The new rules live in theFlowSchemawalk, not in the node schema.Pins
previews/flow-canvas-seeds.saveErrors-10948.test.tsxcovers the measurement above: every palette kind plusmap, the clean control, and the two blank row keys through the real inspector.inspectors/FlowNodeInspector.requiredMarkers-10948.test.tsxcovers:waiton a signal;aria-requiredon a text input, a select trigger and a row cell;Verification
All runs below are at head
e962853fb. Each test run went through the shared verify lock, and its pass count is quoted from the run's own output.pnpm exec vitest runcovered every test file underpackages/app-shell/src/views/metadata-admin/inspectors/and…/previews/, in three chunks. The population is 205 files, enumerated withgit ls-treeat head.it.skipinflow-node-config.spec-reconciliation.test.ts.pnpm --filter @object-ui/app-shell type-checkexited 0. It ran after building the closure withpnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build(29 of 47 projects, exit 0).tsc --listFilesOnlyconfirms the new module is in thetsconfig.jsonpass and both new test files are in thetsconfig.test.jsonpass.ablation-replace.mjsin wrap mode. It deletes the marker inFlowReferenceField: the anchor count went 1 to 0 and the blob went frome815546c2bd1toc2a3f7207f94.subflow,map,connector_actionandboundary_event.e815546c2bd1) andgit diff HEADis empty. The same pin re-run on the restored tree passed 29/29.node scripts/check-changeset-presence.mjsnode scripts/check-control-bytes.mjspnpm check:new-line-citations, with 0 new citationspnpm check:i18n-keys; no string is added, the marker is*node scripts/markdown-test-inputs.mjs --auditcheck-changeset-no-majorcheck-changeset-overwritecheck-changeset-fixedcheck-pending-changeset-literalscheck-changeset-claims, which is report-onlyResourceEditPage.schemaAdvisory.test.tsx(the step-2 pin) andstudio-design/ObjectGroupInspector.test.tsx(the one_shared.tsxconsumer outside them): 2 files, 6 passed.scripts/__tests__/suite, run once because the diff adds a changeset (markdown): 177 files passed and 2 skipped; 5371 tests passed and 2 skipped.@object-ui/app-shellsuite and the full lint farm are CI's.Acceptance notes
These are notes, not filed.
FlowReferenceField,FlowKeyValueField,FlowStringListField, the list row labels, and the inline text labels. This is pre-existing and not widened here. Those fields get the visual marker. Where the inspector renders the input itself, the input also getsaria-required. The reference combobox and the list editors carry noaria-required.booleanand condition-builder kinds are not threaded. No field of either kind is spec-required today.specRequiredColumnsanswers per column from an empty-row probe. A column required only by a rule on a sibling cell (a screenlookupfield'sreference) would not show in the row label; the save-time error still names it. The static screen table has no such column today.saveMetaItemruns the same spec schema on a?mode=draftsave too. So against a 17.5.0 server, an autosave of a flow holding an unconfigured node is refused (422) with located issues, and the designer holds autosave for that slice until it changes. That is the ruled loud-at-save direction, and the draft stays in the editor.Generated by Claude Code