From e4e2d45374b71e48e96bbe8a4a517d060156c362 Mon Sep 17 00:00:00 2001 From: flemming-it Date: Mon, 1 Jun 2026 16:47:29 +0200 Subject: [PATCH] feat(editor): five distinct type colours + wired/total badge in step header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two operator-visible improvements: - Five payload types now get five distinct hues that survive both light and dark themes: text → blue (0xFF42A5F5) bytes → deep orange (0xFFFF7043) json → purple (0xFFAB47BC) file → green (0xFF66BB6A) number → amber (0xFFFFCA28) Previously bytes + file shared a colour and the palette collapsed under custom theme plugins. Hardcoded hues keep them readable everywhere. - Step headers now carry a small "wired/total" badge. When all with-fields carry a `$src.field` reference, the badge collapses to a green check icon — the visual "this module is fully wired" signal Stefan asked for. Partial-wiring shows e.g. "3/5" in a pill matching the step accent. Endpoint nodes don't render the badge (ratio doesn't apply there). Computed live from graph.edges + step.with_ values, so the indicator updates the moment an operator drags a connection in or out. Version 0.5.0 -> 0.5.1. Signed-off-by: flemming-it --- lib/src/widgets/flow_canvas.dart | 30 ++++++++++++++------ lib/src/widgets/flow_node.dart | 48 +++++++++++++++++++++++++++++++- pubspec.yaml | 2 +- 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/lib/src/widgets/flow_canvas.dart b/lib/src/widgets/flow_canvas.dart index 5886441..5761fe7 100644 --- a/lib/src/widgets/flow_canvas.dart +++ b/lib/src/widgets/flow_canvas.dart @@ -311,6 +311,14 @@ class _FlowCanvasState extends State { final selected = widget.controller.selectedStepId == step.id; final raw = widget.controller.stepStatuses[step.id] ?? StepRunStatus.idle; final status = _toNodeStatus(raw); + // How many with-fields carry a wired-up `$src.field` + // expression. Drives the header's "n/total" badge so the + // operator can see at a glance whether the module is + // fully connected. + final wired = step.with_.values + .whereType() + .where((v) => _isWiredExpression(v.toString())) + .length; return Positioned( left: pos.x, top: pos.y, @@ -319,6 +327,7 @@ class _FlowCanvasState extends State { title: step.id, subtitle: step.use, inputPortLabels: step.with_.keys.toList(), + wiredCount: wired, kind: kindForStep(step), selected: selected, status: status, @@ -591,20 +600,26 @@ class _FlowCanvasState extends State { } Color _typeAccent(String type, ThemeData theme) { - final cs = theme.colorScheme; + // Five distinct hues for the five payload types F-Delta-I + // flows declare today. Picked from a high-contrast set + // that survives both light and dark themes; intentionally + // NOT pulled exclusively from the theme's primary / + // secondary / tertiary slots because those collide once + // a custom theme plugin is active. switch (type) { case 'text': - return cs.primary; + return const Color(0xFF42A5F5); // blue case 'bytes': - case 'file': - return cs.tertiary; + return const Color(0xFFFF7043); // deep orange case 'json': - return cs.secondary; + return const Color(0xFFAB47BC); // purple + case 'file': + return const Color(0xFF66BB6A); // green case 'number': case 'integer': - return Colors.amber.shade400; + return const Color(0xFFFFCA28); // amber default: - return cs.onSurfaceVariant; + return theme.colorScheme.onSurfaceVariant; } } @@ -786,7 +801,6 @@ class _FlowCanvasState extends State { return (best - portCenter).distance < 0.5; } - void _finalizeDraft() { final draft = _draft; setState(() => _draft = null); diff --git a/lib/src/widgets/flow_node.dart b/lib/src/widgets/flow_node.dart index a2c1290..c437591 100644 --- a/lib/src/widgets/flow_node.dart +++ b/lib/src/widgets/flow_node.dart @@ -117,6 +117,14 @@ class FlowNode extends StatelessWidget { /// "must wire" and this is empty. final Set optionalLabels; + /// How many of [inputPortLabels] are currently wired to an + /// upstream source. When equal to the total count, the + /// header paints a "complete" check mark; otherwise it + /// renders the ratio (e.g. "3/5") so the operator sees + /// at a glance what's still to wire. Set to null on + /// endpoint nodes where the ratio doesn't apply. + final int? wiredCount; + /// Right-click handler on the node body. Canvas wires this /// to a popup menu at the cursor position (Duplicate / /// Delete / Disconnect inputs etc.). @@ -136,6 +144,7 @@ class FlowNode extends StatelessWidget { this.kind = NodeVisualKind.module, this.portSide = NodePortSide.left, this.optionalLabels = const {}, + this.wiredCount, this.selected = false, this.status = FlowNodeStatus.idle, this.onTap, @@ -224,7 +233,12 @@ class FlowNode extends StatelessWidget { ), ), ), - if (status != FlowNodeStatus.idle) _statusDot(theme), + if (wiredCount != null && inputPortLabels.isNotEmpty) + _wiredBadge(theme, accent), + if (status != FlowNodeStatus.idle) ...[ + const SizedBox(width: 4), + _statusDot(theme), + ], ], ), ), @@ -313,6 +327,38 @@ class FlowNode extends StatelessWidget { ); } + Widget _wiredBadge(ThemeData theme, Color accent) { + final total = inputPortLabels.length; + final wired = wiredCount ?? 0; + final complete = wired >= total && total > 0; + if (complete) { + return Padding( + padding: const EdgeInsets.only(left: 4), + child: Icon(Icons.check_circle, size: 14, color: Colors.green.shade400), + ); + } + return Padding( + padding: const EdgeInsets.only(left: 4), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1), + decoration: BoxDecoration( + color: accent.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: accent.withValues(alpha: 0.4)), + ), + child: Text( + '$wired/$total', + style: theme.textTheme.labelSmall?.copyWith( + fontFamily: 'monospace', + fontSize: 9, + fontWeight: FontWeight.w600, + color: accent, + ), + ), + ), + ); + } + Widget _statusDot(ThemeData theme) { final color = switch (status) { FlowNodeStatus.running => theme.colorScheme.primary, diff --git a/pubspec.yaml b/pubspec.yaml index 8ee09ce..8afeb27 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: fai_studio_flow_editor description: Swappable inline YAML editor for F∆I Studio flows. -version: 0.5.0 +version: 0.5.1 publish_to: 'none' repository: https://git.flemming.ai/fai/studio-flow-editor