Node icon circles in connect__step2 rendered with a medium-purple fill
(Color(0xFF664CAF)) and a blue-purple active-state fill (Color(0xFF435DEA))
that do not exist anywhere in ChirpyDarkColorScheme. Flagged by the nightly
UX audit as a theme-contract violation.
getNodeStateColor() in IconManager.kt mapped several node states to
hardcoded hex literals rather than MaterialTheme.colorScheme.* tokens.
The most visible offender was NodeState.EXECUTED -> Color(0xFF664CAF) (purple).
The fixture nodes in the connect__step2 Roborazzi scenario are all seeded
with state = NodeState.EXECUTED, so every node circle in that screenshot
showed the unthemed purple.
NodeState.PAIRING, NodeState.PROCESSING, and NodeState.EDITING also used
Color(0xFF435DEA) — a vivid indigo not in the token set — for active-state
circles across all node types.
Replaced the four hardcoded values in getNodeStateColor():
NodeState.EXECUTED -> Color(0xFF664CAF) → colorScheme.primaryContainerNodeState.PAIRING -> Color(0xFF435DEA) → colorScheme.primaryNodeState.PROCESSING -> Color(0xFF435DEA) → colorScheme.primaryNodeState.EDITING -> Color(0xFF435DEA) → colorScheme.primaryResting states (EXECUTED, NONE, PAUSED, RESET) all converge on
colorScheme.primaryContainer; active/in-flight states (PAIRING, PROCESSING,
EDITING) use colorScheme.primary to retain the visual distinction without
escaping the token set.
Color(0x…) literal inside getNodeStateColor() (or any composable
that tints node icons) is a theme-contract violation. Prefer
colorScheme.* roles for all node-state circle fills.NodeStateColorTest now asserts that NodeState.EXECUTED returns
colorScheme.primaryContainer and that NodeState.PAIRING returns
colorScheme.primary in a live DarkBlueGrayTheme context, catching
future regressions on every ./gradlew :composeApp:desktopTest.