From a1d25fd48ac3a06f5edd564fbb72587d92f64432 Mon Sep 17 00:00:00 2001 From: flemming-it Date: Tue, 2 Jun 2026 00:56:14 +0200 Subject: [PATCH] fix(editor): solid-filled connected port + larger dot + tooltip fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three issues from Stefan's screenshot: 1. Connected port dots rendered as a ring with a small surface-coloured pin in the centre — read as a hollow bullseye target rather than 'this port is wired'. Stefan asked for a plain filled circle: removed the inner pin so connected = solid disc, disconnected = ring with surface fill. One unambiguous visual switch. 2. Port dots bumped from 12 px to 14 px diameter. At 50 % zoom (and under the wider cards we now draw) the 12 px outline was sinking into the canvas background. 3. Tooltips now appear on every visible port label, not only on those whose ModuleSpec carries a description. Fallback chain: - manifest description in active locale - manifest description in English (peer) - 'fieldname · type' when the field is declared but the description is missing - bare 'fieldname' for implicit (YAML-only) fields Operators always see something on hover, useful when the hub binary is older than the per-field ModuleInfo RPC and the editor is running in implicit-only mode. Bumps fai_studio_flow_editor to 0.10.3. Signed-off-by: flemming-it --- lib/src/widgets/flow_canvas.dart | 98 ++++++++++++++++---------------- lib/src/widgets/flow_node.dart | 11 ++-- pubspec.yaml | 2 +- 3 files changed, 57 insertions(+), 54 deletions(-) diff --git a/lib/src/widgets/flow_canvas.dart b/lib/src/widgets/flow_canvas.dart index 213eff1..bf32ba8 100644 --- a/lib/src/widgets/flow_canvas.dart +++ b/lib/src/widgets/flow_canvas.dart @@ -746,16 +746,34 @@ class _FlowCanvasState extends State final spec = _specForStep(step); final inputLabels = _inputLabelsForStep(step); final outputLabels = _outputLabelsForStep(step); + // Tooltips: every visible label gets one. When the + // manifest carries a locale description, use it. Else + // fall back to `name (type)` for declared fields whose + // description is missing, or to the bare field name for + // implicit (YAML-only) fields. Operators always see + // something on hover — useful at least to confirm the + // exact field they're pointing at. final tooltips = {}; + final loc = widget.locale == FlowEditorLocale.de ? 'de' : 'en'; + final declaredFields = {}; if (spec != null) { - final loc = widget.locale == FlowEditorLocale.de ? 'de' : 'en'; - for (final f in spec.inputs) { - final d = f.descriptionFor(loc); - if (d != null) tooltips[f.name] = d; + for (final f in [...spec.inputs, ...spec.outputs]) { + declaredFields[f.name] = f; } - for (final f in spec.outputs) { + } + for (final label in [...inputLabels, ...outputLabels]) { + final f = declaredFields[label]; + if (f != null) { final d = f.descriptionFor(loc); - if (d != null) tooltips[f.name] = d; + if (d != null && d.isNotEmpty) { + tooltips[label] = d; + } else if (f.type.isNotEmpty) { + tooltips[label] = '$label · ${f.type}'; + } else { + tooltips[label] = label; + } + } else { + tooltips[label] = label; } } final cardHeight = NodeGeometry.heightFor( @@ -1714,50 +1732,32 @@ class _FlowCanvasState extends State onLongPressStart: onContextMenu == null ? null : (details) => onContextMenu(details.globalPosition), - child: Stack( - alignment: Alignment.center, - children: [ - // Outer ring — defines the port's footprint. - // Filled when connected (so the line "docks" - // into a visible target), surface-filled when - // dangling (a clear empty socket). Drop-target - // halo grows + glows. - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: filled ? accent : theme.colorScheme.surface, - border: Border.all( - color: accent, - width: isClosest ? 2.5 : (isHovered ? 2.2 : 1.8), - ), - boxShadow: (isClosest || isHovered) - ? [ - BoxShadow( - color: accent.withValues( - alpha: isClosest ? 0.55 : 0.35, - ), - blurRadius: isClosest ? 10 : 6, - ), - ] - : null, - ), + child: Container( + // Connected = solid filled circle in accent. + // Dangling = clear ring with surface fill so the + // empty socket is obvious. Drop-target halo + // glows in addition. No inner pin — Stefan + // explicitly wants the connected variant to read + // as one continuous filled disc, not a ring with + // a bullseye in it. + decoration: BoxDecoration( + shape: BoxShape.circle, + color: filled ? accent : theme.colorScheme.surface, + border: Border.all( + color: accent, + width: isClosest ? 2.5 : (isHovered ? 2.2 : 1.8), ), - // Inner pin — tiny surface-coloured dot at the - // centre when connected, giving the port the - // "socket with a pin in it" look that reads as - // an active electrical connector rather than a - // free-floating indicator. Skipped on empty - // ports so the hollow ring is unambiguous. - if (connected) - Container( - width: 4, - height: 4, - decoration: BoxDecoration( - color: theme.colorScheme.surface, - shape: BoxShape.circle, - ), - ), - ], + boxShadow: (isClosest || isHovered) + ? [ + BoxShadow( + color: accent.withValues( + alpha: isClosest ? 0.55 : 0.35, + ), + blurRadius: isClosest ? 10 : 6, + ), + ] + : null, + ), ), ), ), diff --git a/lib/src/widgets/flow_node.dart b/lib/src/widgets/flow_node.dart index 7d1414b..1628fc7 100644 --- a/lib/src/widgets/flow_node.dart +++ b/lib/src/widgets/flow_node.dart @@ -83,10 +83,13 @@ class NodeGeometry { /// without overlapping the label text. static const double portGutter = 18; - /// Diameter of the canvas-side port dot — sized to the - /// body's labelSmall row (~11 px text + 11 px breathing - /// room) so the dot reads as the port without crowding. - static const double portDotSize = 12; + /// Diameter of the canvas-side port dot. Sized so the + /// connected-vs-outlined state reads clearly at any zoom: + /// a 12 px outline tends to disappear into the background + /// at 50 % zoom; 14 px keeps the ring visible and gives + /// the filled (connected) variant enough mass to stand out + /// against the card shadow. + static const double portDotSize = 14; /// Total card height for a node carrying [inputCount] inputs /// on the left + [outputCount] outputs on the right. The diff --git a/pubspec.yaml b/pubspec.yaml index b2c9d04..7bed44a 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.10.2 +version: 0.10.3 publish_to: 'none' repository: https://git.flemming.ai/fai/studio-flow-editor