Repository navigation
Automations pillar, remainder of objectui#12454: node-card styling on the flow canvas and the node configuration aside (decision outcomes as cards) #12488
Description
This card carries targets 4 and 5 of objectui#12454. objectui#12454 keeps targets 1–3 (rail grouping, item header, tab row) and closes with PR #12481. The two targets live in shared metadata-admin files outside that claim's surface; the developer's report (6190957173, open question 1) and the seat's REWORK (6192072808) moved them here.
Filing gate: ④, a child of the epic #12446 split off an in-flight card. It carries the parent's domain:ui and priority:p2 and stays in the epic PM's reservation (pm:epic). The dispatch seat is domain:ui#4.
Source
Epic decisions 1 and 3. Reference artboard: docs/design/studio-redesign/automations.html and .png on main. The epic's README gives the shared numbers.
Target (restated from objectui#12454's targets 4 and 5)
- Node cards on the canvas: 260 px wide, 8 px radius, and a 28 px tile coloured by kind: start green, screen pink, get-record blue, decision amber, loop violet, end grey. The label is 13 px / 600; the kind is 10 px uppercase with the object api name in mono. The selected node carries the primary border and ring. Node layout and edges stay the existing engine's; only the card styling changes.
- Node configuration aside, in the skeleton's
StudioInspector:- one header (eyebrow "Node · Decision", the node label, close) instead of today's two stacked headers;
- a decision node shows each outcome as a card with its target node name and its CEL condition in a mono textarea;
- "Add outcome" is a dashed button.
File surface (measured by objectui#12454's developer on 1de2c1d)
packages/app-shell/src/views/metadata-admin/previews/flow-canvas-parts.tsx(NodeCard).packages/app-shell/src/views/metadata-admin/inspectors/_shared.tsx(InspectorShell: a variant that drops its own header under the Studio inspector).packages/app-shell/src/views/metadata-admin/inspectors/FlowObjectListField.tsx(the outcomes list).
All three render in the metadata-admin designer as well. Keep that surface working (objectui's verify skill drives it), or scope the Studio look the way PR objectui#12481 scoped its chrome (FlowPreviewChromeProvider).
Acceptance
- The selected decision node and its aside match the artboard's region; light and dark screenshots at 1280 px, with the README's deviation list.
- The metadata-admin designer's flow preview and node inspector are unchanged, or the change is shown in the same screenshots.
- The decision inspector's existing behaviour stays. The outcome cards edit the same branches the Branches table edits today. HotCRM routes decisions by out-edge conditions; its developer noted that the Branches table then reads "No items yet" while the canvas shows two branches. Show what the outcome cards read in that case.
- Every test under
metadata-admin/previews/andmetadata-admin/inspectors/that the diff reaches stays green or is updated in the same PR, each update ablated once.
Constraints
- Tailwind utilities and theme tokens only; no inline style; existing
data-testids and i18n keys stay. - Lands after objectui#12454 (same area, and the line below).
Blocked-by: #12454
Parent: #12446
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 15, 2026
Measurement for acceptance item 3, read 2026-10-15T13:53Z on objectui
d4bc81ewith HotCRM63db83ein a local console. Studio/studio/app.objectstack.hotcrm/automations?surface=flow:campaign_enrollment, decision node "Campaign Open?" (check_campaign_open) selected:e4"Open · Leads" ande7"Open · Contacts", bothconditional).e4inspector: Branch label "Open · Leads", Conditionhas(vars.campaignRecord…, "Default branch (else)" unchecked, Type Conditional.So the Branches table lists
config.conditionsentries only, and a decision that routes on out-edge conditions (HotCRM's shape) shows no branches in its own inspector. The outcome cards this card adds should list the out-edge branches in that case, each with its target and condition. If they list onlyconfig.conditions, the new cards read "No items yet" too.Two things on the same canvas are filed elsewhere and are not this card's:
vars.xorcurrent_user.xis flagged "not a reference in scope", though the engine binds both roots, whileuser.x, which the engine does not bind, passes silently #12499: thevarsfalse positive one4ande7.check_campaign_openhas no default edge (Studio flags it on every open) hotcrm#2121: the decision's missing default edge.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1 (epic #12446 subtree)
Session:
session_01SJn1GEkrbbBQkhGC26wEDwAccount:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12488-flow-node-cardsWorktree:
objectui-issue-12488(in the cloud session's own container)Domain:
domain:uiSeat:
domain:ui#4File surface:
packages/app-shell/src/views/metadata-admin/previews/flow-canvas-parts.tsx(NodeCard),packages/app-shell/src/views/metadata-admin/inspectors/_shared.tsx(anInspectorShellvariant without its own header),inspectors/FlowObjectListField.tsx(the outcomes list),inspectors/FlowNodeConfigField.tsx(the decision branches mount only),previews/FlowPreview.tsx(the node aside's wiring underFlowPreviewChromeProvideronly),studio-design/StudioDesignSurface.tsx(theAutomationsPillarinspector mount only),metadata-admin/i18n.ts(new keys only), new tests beside them, the screenshotsdocs/design/studio-redesign/after/12488-*.png, one.changeset/12488-*.md(stop on breach; explain in the report)Container & model:
M,mode:cloud,model: opus(cloud on the maintainer's stance 「可以的话,ui 后续开发尽量用云 session,并且要求截图给我看」; batch 5 on 「上限提到 5,提速」, this session's chat, 2026-10-15)Clause-②: no
Responsibility:
n/a — not a defect cardThread-read: 6193761050
Serial constraints cleared: objectui#12454, the card this one waits on, merged as PR objectui#12481 (
8146afe). Open PRs at claim time on this surface: only PR objectui#12501 (objectui#12451), on the sharedmetadata-admin/i18n.ts, which is ordinary concurrency. objectui#12456 (AccessPillar) and objectui#12455 (InterfacesPillar) are in other regions ofStudioDesignSurface.tsx. objectui#12499 (the flow checker's root list, filed, not dispatched) is ininspectors/flow-ref-check.ts, which is outside this surface. objectui#12453 follows onAutomationsPillar.objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 12488,
"status": "done",
"branch": "claude/issue-12488-flow-node-cards",
"pr": null,
"session": "session_01Re8dJAwLn4Cd5jvAikubnw (dev cloud session; dispatched by seat domain:ui#4, session_01SJn1GEkrbbBQkhGC26wEDw)",
"premise_still_valid": true,
"summary": "Three parts, pushed as cb887d5 (base 1c3b069, origin/main merged in at cb887d5 with no conflicts). (1) NodeCard now looks the same in the Studio canvas and the metadata-admin designer: 8 px radius, a 28 px tile coloured by kind (start emerald, screen pink, record blue, decision amber, loop violet, end muted grey), the kind shown by its display name in the designer locale with the raw type on hover, and a primary border with a 3 px ring when selected. Layout and edges are unchanged. (2) The Studio Automations aside now has one header instead of two. A new InspectorShellHostProvider in _shared.tsx tells InspectorShell not to draw its own header, and the aside header reads 'Node · Decision', then the node's label, then close (flowSelectionHeading in FlowPreview.tsx). The narrow-row drawer and the designer are unchanged. (3) A top-level decision's Branches now render as cards: target in the header, Label and Expression below (Expression as a mono textarea), and a dashed 'Add branch' button. Edits go through the same FlowObjectListField commit, then applyDecisionBranches, as before. When conditions is empty (HotCRM's edge-routed shape), the out-edges show as read-only cards with target and condition. Under the Studio host each card has an 'Edit connection' link that opens that edge's inspector. All four PM mechanism assumptions held. The contract file and this dispatch disagree on writes: os-dev.md says open the draft PR and post the os-dev-report comment, while the dispatch's degraded channel says I may not. I followed the dispatch: git push only, PR body below. Housekeeping: the shared checkout /home/user/objectui held a local branch with this name and no commits of its own. I detached it at 1c3b069 and deleted that branch before creating the worktree. The worktree ../objectui-issue-12488 is kept for patch rounds, because the PR is not open yet.",
"tests": "Pins at cb887d5 (the merged head):
pnpm exec vitest run --maxWorkers=4 packages/app-shell/src/views/studio-design/→ Test Files 180 passed (180), Tests 1327 passed (1327). This covers every AutomationsPillar.* (tabs-12454 included), StudioDesignSurface.flowCanvasChip-11665, automationsReadOnly*, and the new AutomationsPillar.nodeAside-12488. Also at cb887d5: the six metadata-admin top-level i18n tests, machineLocaleCensus-9909, FlowObjectListField.branchCards-12488, flow-canvas-parts.nodeCard-12488 and flow-canvas-seeds.saveErrors-10948 → Test Files 10 passed (10), Tests 79 passed (79). Pre-merge at 9946d80: metadata-admin/inspectors/ → 162 passed (162), 2186 passed and 1 skipped; metadata-admin/previews/ → 126 passed (126), 1525 passed; the other 207 metadata-admin tests plus the census → 208 passed (208), 1958 passed. The merge changed no inspectors/ or previews/ file; in metadata-admin it changed only i18n.ts (gates and i18n tests re-run on cb887d5) and two Access-pillar files from main. One existing test changed: flow-canvas-seeds.saveErrors-10948 now clicks 'Add branch' (it clicked 'Add item') because the decision's add button is renamed. Ablation: 10 mutations by on-disk anchor replace, each anchor hit once (the first A1 attempt hit twice and was refused, then rerun with a unique anchor). Each was restored withgit checkout HEAD -- path, the HEAD blob hash matched, andgit diff HEADwas 0 bytes. All went red: A1 tile h-7 w-7 changed to h-9 (nodeCard, 1 failed); A1b decision tile tint back to the old /10 ring form (nodeCard, 1 failed); A2 kind shown as raw type (nodeCard, 2 failed); A3 old selected ring (nodeCard, 1 failed); A4 InspectorShell host branch disabled (branchCards plus nodeAside, 2 failed); A5 edge branches not read (branchCards plus nodeAside, 5 failed); A6 branches variant not passed at the mount (branchCards plus saveErrors-10948, 3 failed); A7 add label back to list.add (saveErrors-10948, 1 failed); A8 aside eyebrow fixed at 'Configuration' (nodeAside, 2 failed); A9 no host provider at the Studio mount (nodeAside, 2 failed); A10 expression not multiline (branchCards, 1 failed). No dist build was needed: the tests import src through relative paths.","gates": "Each exit code captured before any pipe, at cb887d5 unless noted.
pnpm --filter @object-ui/app-shell run type-check→ 0 (the script echoedtsc --noEmit && tsc -p tsconfig.test.json; deps closure built first under os-verify-lock, VERDICT command-exit 0).pnpm --filter @object-ui/app-shell run build→ 0, dist completeness 1100 files (at 9946d80; the merge brought only main's own source).eslint --no-inline-configon the 11 touched .ts/.tsx files → exit 0, 0 errors, 38 warnings. Against the base, per file: FlowObjectListField 3→5 (one react-refresh/only-export-components for decisionEdgeBranches, and a second instance of the existing react-hooks/refs warning from the new rows.map); _shared 7→8 and FlowPreview 1→2 (react-refresh/only-export-components for the new exports); flow-canvas-parts 8→8; StudioDesignSurface 15→15.pnpm check:i18n-keys0 ·pnpm check:i18n-drift0 ·pnpm check:i18n-designer-parity0 ·pnpm check:new-line-citations0 (VERDICT 0 new citations) ·node scripts/check-changeset-presence.mjs0 ·node scripts/check-changeset-no-major.mjs0 (at 9946d80). No new Intl.* call, and machineLocaleCensus-9909 is green. objectui has no scripts/pm/dispatch-gates.mjs, so I derived the local set by hand from the dispatch's list; no --ran reconciliation is available. CI families: no PR exists yet, so there are no check-runs to cite; NOT MEASURED: CI, reason: the PM opens the PR.","mcp_calls": "5 GitHub MCP reads, 0 writes: issue_read get #12488, issue_read get_comments #12488, issue_read get #12446, issue_read get_comments #12446, pull_request_read get #12481. Also 2 claude-code-remote add_repo calls (read access; both answered read_available, nothing attached) and 2 ToolSearch schema loads.",
"api_writes": "0 REST writes. The only remote writes were git pushes to objectui branch claude/issue-12488-flow-node-cards (empty branch, 419f2d8, then cb887d5). Local-only: one PUT /api/v1/meta/flow/campaign_enrollment_copy to my own throwaway
objectstack dev --freshHotCRM backend on :4412, to get a writable flow for the designer screenshot. That database was deleted when the process stopped.","open_questions": [
{
"question": "Editing a decision that routes on its out-edges (HotCRM's shape, empty config.conditions). The cards now show each out-edge read-only (target and condition) with an 'Edit connection' link to FlowEdgeInspector. Should the card edit the edge's condition inline?",
"options": [
"A — keep read-only cards with the link (shipped). Edges stay the only store and FlowEdgeInspector stays the only editor. Work: none.",
"B — inline-edit edge.condition from the node inspector. Needs a new write path in FlowNodeInspector.setField that patches draft.edges directly, outside this claim's file surface, and a second editor for the same key. Work: one small card.",
"C — turn the out-edges into config.conditions on first edit. This changes the stored shape, and the spec refuses
modebeside a non-empty conditions list, so HotCRM's mode-carrying decisions would break. Not recommended."],
"recommendation": "A. Actual need: the measured gap was 'the inspector shows no branches', and A closes it; nobody has asked to edit edge guards from the node. Long-term soundness: one store and one editor per key, so nothing to keep in sync. Hard for AI to get wrong: no second write path that could drift from FlowEdgeInspector's isDefault/condition rules. Startup scope: adds nothing."
},
{
"question": "The artboard's decision kind line reads 'Decision · 2 outcomes'. The count comes from nodeSummary in previews/FlowCanvas.tsx (outside the claim), which counts config.conditions only, so an edge-routed decision shows 'Decision' alone. Make it a follow-up?",
"options": [
"A — record as a deviation (done).",
"B — follow-up card: nodeSummary counts plain out-edges when conditions is empty, reusing decisionEdgeBranches."
],
"recommendation": "A for this card. B only if the maintainer wants the count, since nothing else asks for it today."
},
{
"question": "Where the NodeCard styling applies (decided here by the four axes; recorded, not blocking).",
"options": [
"A — both the Studio canvas and the metadata-admin designer (shipped; the designer shot shows it).",
"B — Studio only, through FlowPreviewChromeProvider."
],
"recommendation": "A. One renderer, one look, as Decision D's ruling and #12481's canvas restyle already did. No Studio-only fork of a shared component, and less code: no context gating. Nothing in the end-user runtime renders NodeCard."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed — the aside's eyebrow and title come from flowSelectionHeading, which repeats the title rule from FlowNodeInspector and FlowEdgeInspector (node label or id; selection label, else source → target). Those two files are outside the claim, so the rule could not live in one place. If either inspector's title changes, the Studio header follows only after a matching edit."
],
"deviations": [
"Dispatch vs contract: os-dev.md requires opening the draft PR and posting the os-dev-report comment. The dispatch's degraded channel forbids both, because the relay returns 403. I followed the dispatch.",
"Commit trailers: I used the model-free pair (Claude-Session plus Co-authored-by: Claude), per objectui AGENTS.md. The harness reminder's model-named Co-Authored-By yields to that rule.",
"flowSelectionHeading is a new export in FlowPreview.tsx, beside FlowPreviewChromeProvider. I count it as the node aside's wiring the claim names there; it is the one helper the Studio mount reads.",
"One existing test updated in previews/ (flow-canvas-seeds.saveErrors-10948), ablated (A7).",
"Artboard deviations are listed in pr_body_draft."
],
"files_changed": [
".changeset/12488-flow-node-cards.md",
"docs/design/studio-redesign/after/12488-automations-decision-light.png",
"docs/design/studio-redesign/after/12488-automations-decision-dark.png",
"docs/design/studio-redesign/after/12488-metadata-admin-flow-light.png",
"packages/app-shell/src/views/metadata-admin/i18n.ts (4 new keys, en and zh: engine.inspector.flowNode.branch.add / fromEdges / openEdge / noCondition)",
"packages/app-shell/src/views/metadata-admin/inspectors/_shared.tsx",
"packages/app-shell/src/views/metadata-admin/inspectors/FlowObjectListField.tsx",
"packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeConfigField.tsx (objectList case only)",
"packages/app-shell/src/views/metadata-admin/previews/flow-canvas-parts.tsx",
"packages/app-shell/src/views/metadata-admin/previews/FlowPreview.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (imports plus the AutomationsPillar aside mount only)",
"tests: inspectors/FlowObjectListField.branchCards-12488.test.tsx (new), previews/flow-canvas-parts.nodeCard-12488.test.tsx (new), studio-design/AutomationsPillar.nodeAside-12488.test.tsx (new), previews/flow-canvas-seeds.saveErrors-10948.test.tsx (updated)"
],
"line_budget": "n/a",
"pr_body_draft": "(elided by the seat: opened verbatim as the body of PR #12543, read back identical)"
}
Relayed verbatim by seat
domain:ui#4(sessionsession_01SJn1GEkrbbBQkhGC26wEDw) from the cloud session's final message, the degraded write channel: the session's relay dispatch was refused 403. The seat opened the draft PR #12543 frompr_body_draft, with assigneeos-sales, and wrote its title (the report carries none)."pr": nullabove is the session's own value at report time.Generated by Claude Code