Skip to content

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

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10948-flow-designer-required-keys
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10948-flow-designer-required-keys

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 * with data-required-marker that SchemaForm's FieldRow draws), reused through a new RequiredMarker atom in inspectors/_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.ts probes 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.

Because the answer comes from the node's own configuration, a rule-dependent requirement is marked only while it applies:

  • notify requires title only while it has no template;
  • a loop requires collection once it has a body;
  • a refused end requires message.

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):

node marked
create / update / get / delete record Object
http URL
notify (seed) Recipients, Title
script Function
subflow Flow
map Collection, Per-item flow
connector_action Connector, Action
wait (timer seed) Wait for, Duration
boundary_event Attached to, Event type
decision branch row Label, Expression (not Target)
screen field row Name (not Label / Type / Required / Visible when)
loop (no body), approval, assignment, end (completed), try_catch nothing

aria-required goes on each control the inspector owns: text / textarea / expression inputs, selects, numbers, and a row's scalar cells. The marker itself is aria-hidden, as in SchemaForm.

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.

Pins

  • previews/flow-canvas-seeds.saveErrors-10948.test.tsx covers the measurement above: every palette kind plus map, the clean control, and the two blank row keys through the real inspector.
  • inspectors/FlowNodeInspector.requiredMarkers-10948.test.tsx covers:
    • the markers per seeded kind (an equality, with the unmarked labels asserted present, so an empty set is never a form that drew nothing);
    • the decision and screen rows;
    • the three rule-dependent cases, plus wait on a signal;
    • aria-required on a text input, a select trigger and a row cell;
    • the module's per-key and per-column answers.

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.

  • Tests. pnpm exec vitest run covered every test file under packages/app-shell/src/views/metadata-admin/inspectors/ and …/previews/, in three chunks. The population is 205 files, enumerated with git ls-tree at head.
    • Chunk 1, the flow suites and both new files: 53 files, 726 passed, 1 skipped. The skip is the existing it.skip in flow-node-config.spec-reconciliation.test.ts.
    • Chunk 2, the other inspectors: 78 files, 1109 passed.
    • Chunk 3, the other previews: 74 files, 956 passed.
  • Type-check. pnpm --filter @object-ui/app-shell type-check exited 0. It ran after building the closure with pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (29 of 47 projects, exit 0). tsc --listFilesOnly confirms the new module is in the tsconfig.json pass and both new test files are in the tsconfig.test.json pass.
  • Ablation. It was run with ablation-replace.mjs in wrap mode. It deletes the marker in FlowReferenceField: the anchor count went 1 to 0 and the blob went from e815546c2bd1 to c2a3f7207f94.
    • The marker pin went red: 8 failed and 21 passed. The 8 are exactly the reference-kind rows: the four CRUD kinds, subflow, map, connector_action and boundary_event.
    • Restored: the blob equals HEAD (e815546c2bd1) and git diff HEAD is empty. The same pin re-run on the restored tree passed 29/29.
  • ESLint on the 11 changed source and test files: 0 errors. Every pre-existing file carries the same warning set as at the merge-base, and the new files carry none.
  • Gates. All of these exited 0:
    • node scripts/check-changeset-presence.mjs
    • node scripts/check-control-bytes.mjs
    • pnpm check:new-line-citations, with 0 new citations
    • pnpm check:i18n-keys; no string is added, the marker is *
    • node scripts/markdown-test-inputs.mjs --audit
    • check-changeset-no-major
    • check-changeset-overwrite
    • check-changeset-fixed
    • check-pending-changeset-literals
    • check-changeset-claims, which is report-only
  • Beside the two directories:
    • ResourceEditPage.schemaAdvisory.test.tsx (the step-2 pin) and studio-design/ObjectGroupInspector.test.tsx (the one _shared.tsx consumer outside them): 2 files, 6 passed.
    • The root scripts/__tests__/ suite, run once because the diff adds a changeset (markdown): 177 files passed and 2 skipped; 5371 tests passed and 2 skipped.
  • Declared narrowing. The full @object-ui/app-shell suite and the full lint farm are CI's.

Acceptance notes

These are notes, not filed.

  • Several flow-inspector labels are not programmatically bound to their control: 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 gets aria-required. The reference combobox and the list editors carry no aria-required.
  • The boolean and condition-builder kinds are not threaded. No field of either kind is spec-required today.
  • specRequiredColumns answers per column from an empty-row probe. A column required only by a rule on a sibling cell (a screen lookup field's reference) would not show in the row label; the save-time error still names it. The static screen table has no such column today.
  • Not measured live: the server's saveMetaItem runs the same spec schema on a ?mode=draft save 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

…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>
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/10586-inspector-strings-i18n-2.md

  • names _shared.tsx → packages/app-shell/src/views/metadata-admin/inspectors/_shared.tsx — edited by this change

    HookDefaultInspector and ActionDefaultInspector rendered their section titles, field labels, placeholders, hints, option labels and accessible names as English literals. InspectorComboField and _shared.tsx fell back to English defaults wherever a caller passed none: the combo's placeholder, search box, empty state, loading text and "Use" row, and the shell's close label, the reorder pair's names and the roster-failure notice. So a zh-CN author read English inside an otherwise Chinese designer.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 615346d61 (merge-base with origin/main): 13 file(s) changed outside .changeset/, read against 1829 pending declaration(s) that publish a body (2439 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3577.8 KB 3607.4 KB
Main entry chunk (gzip) 149.8 KB 350 KB
Entry file index-BeZ2EoBa.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 569.02KB 136.04KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.91KB 63.54KB
fields (index.js) 261.17KB 66.27KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.88KB 11.46KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 216.32KB 44.56KB
plugin-detail (index.js) 242.01KB 63.55KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 173.07KB 44.44KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 230.79KB 63.39KB
plugin-kanban (index.js) 48.46KB 15.12KB
plugin-list (index.js) 116.28KB 28.88KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.82KB 7.15KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e962853fb96cc01aa1702f4e5900595d2bdfd438
Local-runs: none

Read at 2026-09-30T17:41Z: card objectui#10948 (body and all five comments, the dev report 5916329288 included), PR #11247 (body, 12-file list, net diff against main at the head), the head's check-runs, plus context reads of the spec at the @objectstack/spec@17.5.0 tag and of the base tree through the contents API. Nothing built, run or re-run.

① Derived judgments

  • Accept set: unchanged — right. The diff touches inspector rendering under packages/app-shell/src/views/metadata-admin/inspectors/ only. No Zod schema, no conversion entry, no save door (clientValidation.ts, ResourceEditPage.tsx, flow-canvas-parts.tsx are untouched). What the designer accepts is the installed spec's, before and after.
  • Public surface: unchanged — right. packages/app-shell/src/index.ts exports none of the touched modules (FlowNodeInspector, FlowNodeConfigField, the list, key-value and reference fields, _shared, flow-required-keys). The new RequiredMarker export and the new optional required, requiredColumns and ariaRequired props are package-internal; nothing existing is renamed, removed or narrowed. No published API widens or narrows.
  • Measurement table — right. Checked against the spec source at the 17.5.0 tag (commit 0f6dcac5e9): getBuiltinNodeConfigContracts in flow-node-config-refusals.ts lists exactly the four CRUD kinds, notify, http, screen, script, subflow, map, loop (parsed only with a body), parallel and try_catch, and flowNodeConfigRefusals reports only a key ABSENT from the authored config — so objectName, url, title, function, flowName, collection plus flowName, branches and try at nodes.0.config.KEY are what the pass must say, and decision, loop without a body, assignment, approval, screen, wait and end saving clean follows. 17.5.0 carries the objectstack#20453 rule: flow.zod.ts at that tag holds connectorActionConfigRefusals, walked by the FlowSchema superRefine, and connector-action-config-required.test.ts sits beside it — the seeded blank ids are refused at nodes.0.connectorConfig.connectorId and .actionId. The clean control flow and the two typed blanks (FlowObjectListField's rowsToList drops the blank cell; refused at conditions.0.label and fields.0.name) are pinned in flow-canvas-seeds.saveErrors-10948.test.tsx, which also puts every issue on the node's badge through buildFlowProblems.
  • Step 2, "not a regression" — sound. At base, ResourceEditPage arms its blocking half (refusalBlocking, gating the button, the shortcut and the autosave timer) only from a server 422 on this document; the live validateMetadataDraft pass stays advisory by design, pinned by ResourceEditPage.schemaAdvisory.test.tsx ("surfaces a schema error the server would accept, and leaves Save enabled"). So the live pass keeps the draft with located errors. The server half of that sentence is carried to ③.
  • No second list — right. flow-required-keys.ts names no key. It probes the node with the key removed and asks three judges, all spec exports: flowNodeConfigRefusals; resolveFlowNodeExpressions with predicateSlotRefusal, under the same guard the FlowSchema superRefine uses at the tag (entry.required and value absent, then the refusal); and FlowNodeSchema, whose transform carries requireTypeScopedConfig, parseEndNodeConfig and the strict connectorConfig, waitEventConfig and boundaryConfig blocks. That is the same source the flow parse runs, not a re-implementation. What the module does re-spell is the ledger path format (fields[0].name, the spec's private ledgerPathOf) — a spelling, not a requiredness. One flow-level judge it cannot ask, connectorActionConfigRefusals, is module-private in the spec; presence of the two connector ids is covered by the strict block, and the blank-id rule is deliberately left to save time ("presence only", stated in the PR). Rule-dependent keys follow from the probe itself: loop's parsedWhen reads body, and NotifyConfigSchema's superRefine addresses title only without a template.
  • Probe cost — acceptable. requiredness is memoised on [node, fields] (the deps pattern the existing memos in the same component already use; the memo's value is read, never keyed on, so objectui commandment [WIP] Enhance every detail of the designer #10 holds) and recomputes on each edit of the selected node. Per field it runs one contract safeParse on the config copy, one ledger walk and one FlowNodeSchema.safeParse of the node copy — a handful to fifteen fields per type. A region container (loop, try_catch, parallel) re-parses its body once per field; bounded, and no card asked for a measurement. Not a finding.
  • Marker — reuse, not a new idiom. RequiredMarker renders the byte-identical span SchemaForm draws at its FieldRow and its repeater header (text-destructive ml-0.5, aria-hidden, data-required-marker), and aria-required on owned controls mirrors SchemaForm's repeater cells (objectui#10367). _shared.tsx's one outside consumer, ObjectGroupInspector, sees only two new optional props and one new export; the dev re-ran its test green.
  • Per-seed FlowNodeSchema ratchet 25/25 — consistent. The new rules live in flowNodeConfigRefusals and the FlowSchema superRefine, not in the node schema; the seeds satisfy the node-level rules (end message, the sibling blocks).
  • Ablation leg — consistent with the table. Deleting the marker in FlowReferenceField can redden exactly the rows whose marked labels are reference-kind fields: Object on the four CRUD kinds, Flow, Per-item flow, Connector and Action, Attached to — eight rows; script's function is a text field, so it stays green. Reported by the dev at this head with the restore leg; read, not re-run.
  • Check-runs on the head (filter=latest, read after the last test shard completed): 43 runs, 40 success, 3 skipped (the two coverage-mode test jobs and dependabot, path- and actor-gated), 0 failed, 0 in progress. Lint, Type Check, all eight Test shards, Test (dist pins), Build & E2E, Governed Surface Queue Guard, Line Citation Gate and every changeset check are success. These conclusions are the gate verdicts; none was re-run here.

② Semver level

  • .changeset/10948-flow-inspector-required-keys.md declares '@object-ui/app-shell': minor — right. The diff changes published src/ of a fixed-group package, so a changeset is owed; it is a feature (a new designer affordance with new optional props), and objectui declares no major (check-changeset-no-major). The body describes what an upgrading author sees and carries no model identifier and no line address.
  • Clause-②: no — right. The accept set is the installed spec's, unchanged here; no arm is declared, none is owed, and no ADR-0087 marker is owed since nothing breaks. Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check and Changeset Claim Re-read all succeeded on the head.

③ Boundary flags

Dev report 5916329288, open_questions: [], out_of_scope_findings: []. The three declared deviations:

  1. Lock-wrapped runs launched in the background with exit codes read from files; queue timeouts (exit 99) recorded NOT MEASURED and retried — process, answered: every quoted count is from an acquired run.
  2. Commit trailers as the model-free Claude-Session plus Co-authored-by: Claude pair rather than the harness reminder — answered: that is the rule in both repos' AGENTS.md, and both commits on the PR carry exactly that pair with no model identifier. Not a deviation.
  3. ESLint at 964780002 on all changed files, re-run at e962853fb on the one file it changed — answered: the second commit touches only FlowNodeInspector.requiredMarkers-10948.test.tsx, and the Lint check-run on the head succeeded.

The four Acceptance-notes observations, each judged:

  • Unbound flow-inspector labels; no aria-required on the reference combobox and the list editors — pre-existing and not widened here, but a reproducible a11y gap, and with this diff the requirement on eight of the seventeen seeded kinds' marked keys (every reference-kind one: Object, Flow, Per-item flow, Connector, Action, Attached to) reaches the eye only. The seat should file it as a follow-up card on the inspector (label association for ReferenceCombobox and the list editors, then aria-required there). Not a finding against this diff.
  • boolean and condition-builder kinds not threaded — no spec-required field of either kind exists today; a future one would first redden the MEASURED table in the saveErrors pin (a new refused path), which forces the re-measure. Note suffices.
  • Empty-row column probe — a column required only under a sibling cell's rule (a lookup screen field's reference) is not in the static screen columns, and the save-time error still names it. Note suffices.
  • Server draft door — read at base: a server 422 is held against the refused slice and gates all three save doors until that slice changes; whether a 17.5.0 server refuses a mode=draft save of a flow holding an unconfigured node was NOT measured, by the dev's own note. If it does, an author who drags an http node cannot persist the flow until a URL is typed — the restart's step-2 shape at the server door rather than at the live pass. Escalated: the seat should measure it against a 17.5.0 server and, if it holds, report it on the card per the triage's step 2 (regrade to p2) as a coordination question to objectstack on whether the draft mode should run the executor-contract refusals. Not a finding against this diff, which does not touch that door.

Not flagged by the dev, noted here: the app-shell README's FlowNodeInspector paragraph does not mention the marker (objectui commandment #2) — a one-sentence docs follow-up, not a blocker. No governed path is in the file list; the PR is a dispatched dev's draft with the report posted, the correct terminal state.

Implemented-by: claude/issue-10948-flow-designer-required-keys
Reviewed-by: session_011p7ikEivgXefNDaE5S5Uec

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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3581.7 KB 3607.4 KB
Main entry chunk (gzip) 149.8 KB 350 KB
Entry file index-BA4I2C0U.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 569.34KB 136.09KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.91KB 63.54KB
fields (index.js) 261.16KB 66.27KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.88KB 11.46KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 216.32KB 44.56KB
plugin-detail (index.js) 242.11KB 63.60KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 173.07KB 44.44KB
plugin-gantt (index.js) 173.03KB 43.07KB
plugin-grid (index.js) 230.79KB 63.39KB
plugin-kanban (index.js) 48.46KB 15.12KB
plugin-list (index.js) 116.28KB 28.88KB
plugin-map (index.js) 23.50KB 7.82KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 33.05KB 9.67KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.82KB 7.15KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c30e89b3eb699645167fc73f2172599f4ecd0134
Local-runs: none

Read at 2026-09-30T18:06Z: card objectui#10948 (body and every comment, the dev reports 5916329288 and 5916808682 included), PR #11247 at this head (body, 14-file list with blob shas, the net diff against main at the base, and the compare from e962853fb), this head's check-runs, plus context reads through the contents API of the spec at the @objectstack/spec@17.5.0 tag, of objectstack at 0f6dcac5e, and of the base tree. Nothing built, run or re-run. This record supersedes 5916533642, which named e962853fb only.

① Derived judgments

  • Code byte-identical to the PASSed head — confirmed. The compare e962853fb...c30e89b3e is one commit ahead, zero behind, touching packages/app-shell/README.md (plus 10) and content/docs/guide/flow-designer.md (plus 10, minus 5) and nothing else. In the PR's file list every code file and the changeset carry the same blob sha at both heads (flow-required-keys.ts e72d9c1a5ed9, FlowNodeInspector.tsx 5aa4488e054a, _shared.tsx 8ccc40e0fbae, FlowReferenceField.tsx e815546c2bd1, the changeset 5073e52a0ed8, and so on); only the two markdown blobs differ. The judgments below on the code are therefore the judgments of record 5916533642, restated here so this record stands alone.
  • Accept set: unchanged — right. The diff touches inspector rendering under packages/app-shell/src/views/metadata-admin/inspectors/ and two markdown files. No Zod schema, no conversion entry, no save door (clientValidation.ts, ResourceEditPage.tsx, flow-canvas-parts.tsx are untouched). What the designer accepts is the installed spec's, before and after.
  • Public surface: unchanged — right. packages/app-shell/src/index.ts exports none of the touched modules; the new RequiredMarker export and the new optional required, requiredColumns and ariaRequired props are package-internal; nothing existing is renamed, removed or narrowed. Markdown publishes no API.
  • Measurement table — right. Checked against the spec source at the 17.5.0 tag (commit 0f6dcac5e9): getBuiltinNodeConfigContracts in flow-node-config-refusals.ts lists exactly the four CRUD kinds, notify, http, screen, script, subflow, map, loop (parsed only with a body), parallel and try_catch, and flowNodeConfigRefusals reports only a key ABSENT from the authored config — so objectName, url, title, function, flowName, collection plus flowName, branches and try at nodes.0.config.KEY are what the pass must say, and decision, loop without a body, assignment, approval, screen, wait and end saving clean follows. 17.5.0 carries the objectstack#20453 rule: flow.zod.ts at that tag holds connectorActionConfigRefusals, walked by the FlowSchema superRefine, beside connector-action-config-required.test.ts — the seeded blank ids are refused at nodes.0.connectorConfig.connectorId and .actionId. The clean control flow and the two typed blanks (FlowObjectListField's rowsToList drops the blank cell; refused at conditions.0.label and fields.0.name) are pinned in flow-canvas-seeds.saveErrors-10948.test.tsx, which also puts every issue on the node's badge through buildFlowProblems.
  • Step 2, "not a regression" — sound for the live pass. At base, ResourceEditPage arms its blocking half (refusalBlocking, gating the button, the shortcut and the autosave timer) only from a server 422 on this document; the live validateMetadataDraft pass stays advisory by design, pinned by ResourceEditPage.schemaAdvisory.test.tsx. The server half is in ③.
  • No second list — right. flow-required-keys.ts names no key. It probes the node with the key removed and asks three judges, all spec exports: flowNodeConfigRefusals; resolveFlowNodeExpressions with predicateSlotRefusal, under the same guard the FlowSchema superRefine uses at the tag (entry.required and value absent, then the refusal); and FlowNodeSchema, whose transform carries requireTypeScopedConfig, parseEndNodeConfig and the strict connectorConfig, waitEventConfig and boundaryConfig blocks. The same source the flow parse runs, not a re-implementation; what the module re-spells is only the ledger path format (fields[0].name). The one flow-level judge it cannot ask, connectorActionConfigRefusals, is module-private; presence of the two ids is covered by the strict block, and the blank-id rule is deliberately left to save time. Rule-dependent keys follow from the probe: loop's parsedWhen reads body; NotifyConfigSchema's superRefine addresses title only without a template.
  • Probe cost — acceptable. requiredness is memoised on [node, fields] (the deps pattern the component's existing memos use; the value is read, never keyed on, so objectui commandment [WIP] Enhance every detail of the designer #10 holds) and recomputes per edit of the selected node: one contract safeParse, one ledger walk and one FlowNodeSchema.safeParse per field, a handful to fifteen fields per type. Not a finding.
  • Marker — reuse, not a new idiom. RequiredMarker renders the byte-identical span SchemaForm draws at its FieldRow and its repeater header (text-destructive ml-0.5, aria-hidden, data-required-marker); aria-required on owned controls mirrors SchemaForm's repeater cells (objectui#10367). _shared.tsx's one outside consumer, ObjectGroupInspector, sees two new optional props and one new export; re-run green by the dev.
  • Per-seed FlowNodeSchema ratchet 25/25 — consistent; the new rules live in flowNodeConfigRefusals and the FlowSchema superRefine, not in the node schema.
  • Ablation leg — consistent with the table. Deleting the marker in FlowReferenceField can redden exactly the rows whose marked labels are reference-kind fields: Object on the four CRUD kinds, Flow, Per-item flow, Connector and Action, Attached to — eight rows; script's function is a text field, so it stays green. Reported at e962853fb, where the code is identical; read, not re-run.
  • The README paragraph — every sentence true against the code. The marked keys it names (an http node's URL, a record node's Object, a decision branch's Label and Expression, a screen field's Name) are rows of the marker pin's equality; "the same required marker SchemaForm draws" is RequiredMarker; "a control the inspector renders itself also carries aria-required" is exactly the scope the code has (VariableTextInput, InspectorNumberField, InspectorSelectField, the row cells) and leaves the reference combobox and list editors out, as the Acceptance note says; "no list of required keys is kept here" and the three judges are flow-required-keys.ts as read; the notify Title rule is the pinned rule-dependent case. It cites by symbol, no line address.
  • The guide edit — right, both the new sentences and the two removals. "ID, Label, Node Type, and a Configuration section": the inspector renders InspectorTextFields for id and label and a type select, and no Description field — its own header names that field as removed in objectui#6287 and strips a stored description on write (SPEC_REFUSED_NODE_KEYS), so removing "an optional Description" corrects a sentence already false. "A Wait node already carries a timer config, and an HTTP node defaults to GET": defaultNodeExtras seeds waitEventConfig with eventType timer and timerDuration PT1H, and http with method GET — true. "Marked with a red *, the same required marker the other metadata forms use": text-destructive, SchemaForm's span — true. The four named keys and the Notify-title rule are pinned. "The spec's error is shown on that node and listed in the Problems panel": at base, previewDiagnostics prefers the live client issues whenever a client validator exists (the flow has one), FlowPreview hands them to buildFlowProblems as serverDiagnostics, and the result feeds both the canvas badge and ProblemsPanel, whose title is the i18n string "Problems" — true. Removing "a freshly dropped block is never in a broken intermediate state" is right: on 17.5.0 a fresh http, CRUD, notify, script, subflow, map, parallel, try_catch or connector_action seed is refused, as the saveErrors pin measures; the replacement "defaults where the spec allows one" claims no more than the seeds deliver.
  • Check-runs on this head (filter=latest, every run on head_sha c30e89b3e, the last completed at 18:01Z): 43 runs, 40 success, 3 skipped (the two coverage-mode test jobs and dependabot, path- and actor-gated), 0 failed, 0 in progress. Lint, Type Check, Test and all eight shards, Test (dist pins), Build & E2E, Internal Docs Link Check, Governed Surface Queue Guard, Line Citation Gate and every changeset check are success. These conclusions are the gate verdicts; none was re-run here.

② Semver level

  • .changeset/10948-flow-inspector-required-keys.md, unchanged at this head, declares '@object-ui/app-shell': minor — right. The diff changes published src/ of a fixed-group package, so a changeset is owed; it is a feature with new optional props; objectui declares no major. The two markdown files publish nothing and owe no second changeset (Changeset Declaration success on this head).
  • Clause-②: no — right. The accept set is the installed spec's, unchanged here; no arm is declared, none is owed, and no ADR-0087 marker is owed since nothing breaks. Changeset Bump Policy, Changeset Fixed Group Check and Changeset Claim Re-read succeeded on this head.

③ Boundary flags

Reports 5916329288 and 5916808682; both open_questions: [], out_of_scope_findings: []. The declared deviations:

  1. (first report) Lock-wrapped runs in the background, queue timeouts recorded NOT MEASURED and retried — process, answered.
  2. (first report) Model-free commit trailers — answered: the rule in both repos' AGENTS.md; all three commits carry the Claude-Session plus Co-authored-by: Claude pair and no model identifier. Not a deviation.
  3. (first report) ESLint at 964780002, re-run on the one file e962853fb changed — answered: that commit touches only the marker test; Lint is success on this head too.
  4. (follow-up) prettier --check not run despite the coordinator's message — right. objectui AGENTS.md names prettier as no check at all (no config, nothing wired, red on files byte-identical to main); the wired markdown checks were run instead and Internal Docs Link Check is success here.
  5. (follow-up) Two already-false sentences removed from the guide paragraph being touched — right, judged in ① above; a published defect inside the paragraph a round edits is fixed, not preserved.

The four Acceptance-notes observations, each judged:

  • Unbound flow-inspector labels; no aria-required on the reference combobox and the list editors — pre-existing and not widened; a reproducible a11y gap, and with this diff the requirement on the eight reference-kind marked keys reaches the eye only. The seat should file it as a follow-up card (label association for ReferenceCombobox and the list editors, then aria-required there). Not a finding against this diff.
  • boolean and condition-builder kinds not threaded — no spec-required field of either kind today; a future one first reddens the saveErrors pin's MEASURED table. Note suffices.
  • Empty-row column probe — the static screen columns hold no rule-dependent column; the save-time error still names one. Note suffices.
  • Server draft door — the follow-up's source reading changes the strength of the escalation, not the verdict. Confirmed at objectstack 0f6dcac5e: saveMetaItem in packages/metadata-protocol/src/protocol.ts derives mode from request.mode, and the schema gate (resolveOverlaySchema(request.type, request.item), then safeParse, then INVALID_METADATA) runs with no draft-mode branch between the two; every mode === 'draft' read in the handler sets the stored state, none skips validation; and packages/spec/src/kernel/metadata-type-schemas.ts maps flow to FlowSchema. So the source predicts a 422 at nodes.N.config.url on a ?mode=draft save of a flow holding a fresh http node, and at base the client then holds that 422 against the slice and gates all three save doors until it changes, the draft staying in editor state. Still NOT MEASURED against a live 17.5.0 server, for the stated reason (no server can be brought up in the box). Previously "measure, then file if it holds"; now, with contract text in hand, the seat should file it now as a coordination card to objectstack — whether a draft-mode save should be exempt from the executor-contract refusals is a maintainer decision (needs-user-decision), with the live measurement as the card's first step — and report it on objectui#10948 per the triage's step 2. It is the step-2 shape at the server door rather than at the live pass, in a door this diff does not touch; the ruled direction ("the author meets the error at save time") reads on it either way, which is why it is a decision to request, not a finding against this PR.

The README note of record 5916533642 is answered by this head. No governed path is in the file list; the PR is a dispatched dev's draft with both reports posted, the correct terminal state.

Implemented-by: claude/issue-10948-flow-designer-required-keys
Reviewed-by: session_011p7ikEivgXefNDaE5S5Uec

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 18:08
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 68c9ca7 Sep 30, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10948-flow-designer-required-keys branch September 30, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: app-shell tests

Projects

None yet

2 participants