Three connected improvements to the analyzer-driven diagnostics:
- **Pulsing halo on broken graph nodes**. Every step / pseudo-
node (inputs / outputs) carrying an analyzer issue now
breathes a red (error) or amber (warning) halo on the graph
tab — operator sees the problem on the canvas without
flipping to text. Honours prefers-reduced-motion: reduce-
motion users get a static halo at the same intensity. The
canvas reads severity via a new `stepIssueSeverity` map on
FlowEditorController; pseudo-nodes use `__inputs__` /
`__outputs__` sentinel ids.
- **Store-aware install button**. The analyzer now takes a
second closure, `storeCapabilities`, listing what the public
store can install. Unknown-capability issues only carry the
"Install …" quick-fix when the bare cap is in that list;
otherwise the issue carries an "Add source for …" fix
instead. Resolves the asymmetry the operator reported: the
Store didn't show `htw.digiscout/onet.lookup` but the editor
happily offered to install it (and would have failed). The
install path no longer lies about itself.
- **Did-you-mean suggestion**. When the unknown cap is within
edit-distance two of an installed or store cap (different
spelling — distance-0 stays hidden because that's an install
case, not a typo), the analyzer emits a `ReplaceLineValueFix`
suggesting the closest match. Preserves the version
constraint by reusing the installed spec when present
(e.g. `text.echi@^0.1` → `text.echo@^0.1`).
New public surface:
- `AddModuleSourceFix` + `AddModuleSourceCallback`
- `FlowEditorPage.storeCapabilities` + `onAddModuleSource`
- `FlowAnalyzer.stepSeverity` + the `kInputsNodeId` /
`kOutputsNodeId` sentinels
- `FlowIssueSeverity` enum + `FlowNode.issueSeverity`
Tests:
- Install fix only when in store
- AddModuleSourceFix as fallback when not in store
- Did-you-mean replaces install when a near miss exists
- stepSeverity populated for both step ids and pseudo-nodes
All 36 editor tests green. Bumped to 0.18.0.
Signed-off-by: flemming-it <sf@flemming.it>
Two visible UX fixes:
1. Properties-panel toggle. Tap a step → opens. Tap the
same step again → closes (deselect via toggle). Tap the
canvas background → also closes. selectStep now returns
to null when called with the already-selected id; the
editor-controller test updated for the new semantics.
2. LabVIEW-style type colours on every port + wire.
_typeAccent now picks vibrant brightness-aware hues per
datatype:
text → magenta/pink (LabVIEW string)
json → amber/orange (cluster feel)
bytes → cyan/teal (raw binary)
file → green (file reference)
number → yellow/amber
Step-input dots, step-output dots, and outputs-endpoint
dots all switch from the generic theme.primary to the
field's declared type accent. Operators read the
payload from the dot alone.
Editor tests still pass (20).
Signed-off-by: flemming-it <sf@flemming.it>
Three changes:
1. Type-checked port connections. Drag from an output port
only highlights compatible-type input ports as drop
targets; incompatible drops are silently refused. Source
of truth: ModuleSpec for declared types. When either side
is unknown (no manifest, implicit YAML field) the check
degrades to 'compatible with anything' so a missing
manifest never blocks composition.
2. NodeGeometry.heightFor now adds a 2 px allowance for the
1 px AnimatedContainer border on top + bottom of the
card. Without this the last port-row's Column would
overflow by 2 px under tight layout constraints (caught
by the new widget tests; production canvas clipped it
silently inside InteractiveViewer).
3. Width parameter on FlowNode now actually drives the
outer SizedBox. The dynamic _stepWidth value from the
canvas finally reaches the card border rather than being
shadowed by NodeGeometry.width. Long labels
(model_endpoint, source_language) no longer ellipsis-clip.
Tests: 8 new widget + geometry tests covering width growth,
two-column body layout, port-tooltip attachment,
row-alignment between input and output sides, and the
existing kindForStep classifier. All 20 editor tests pass.
Bumps fai_studio_flow_editor to 0.11.0.
Signed-off-by: flemming-it <sf@flemming.it>
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 <sf@flemming.it>
Two issues Stefan spotted in the screenshot:
1. Output port labels rendered on the right side of each
step card, but the canvas-side port DOTS were missing —
only the inputs side carried interactive dots. The
_portOverlays generator looped once per step and stamped
a single anchor; with per-field outputs we now loop
per declared (or YAML-implied) output field and yield
one dot per field, each with its own drag-start
handler that stamps fromField into the ConnectionDraft.
New edges out of those dots persist the precise output
name (e.g. $summarize.response) instead of the
placeholder $summarize.result.
2. Long labels (model_endpoint, source_language) were
ellipsis-clipped because every card rendered at the
220 px fixed minimum. NodeGeometry now exposes
widthFor(maxInputChars, maxOutputChars) that grows the
card to fit the longest label on each side, plus
gutters and slack. _stepWidth(step) on the canvas
computes it per step from the merged label lists;
_outputPortPosition, fit-to-content, and _stepPositioned
all route through it so dots, edges, and bounding boxes
stay aligned when cards stretch.
Connected-ports set keys per-field on the source side too
(${stepId}:${fieldName}) so the dot fills the way the
input-side dots do once any edge actually leaves it.
Legacy ${stepId}:__out__ stays seeded for the
no-declared-outputs fallback.
Bumps fai_studio_flow_editor to 0.10.2.
Signed-off-by: flemming-it <sf@flemming.it>
Four polish features in one push.
1. Reduce-motion clamp. FaiEditorStyle.clampedForA11y(
disableAnimations: ...) returns a style with glass, gradient,
flow animation, and node shadows forced off when the OS
reduce-motion preference is set. FlowCanvas + FlowEditorPage
call it on every build with
MediaQuery.disableAnimationsOf(context) so operators on
accessibility settings get a flat, static editor without an
explicit Studio setting.
2. Edge gradient source→target. EdgePainter now accepts an
optional colorEnd per segment and paints a linear shader
along the bezier when both ends carry distinct type accents.
In practice this draws attention to type mismatches: a
text→json wire reads as a colour transition; a same-type
wire stays a solid colour. ModuleSpec lookup at both edge
endpoints feeds the colour pair.
3. Breathing pulse on running steps. FlowNode accepts a
Listenable pulse; when status=running AND the canvas's
_flowAnim is ticking, the node wraps in an AnimatedBuilder
that drives a sine-wave shadow halo (alpha + blur + spread
oscillating). Stops when the run completes. Gated by
_style.flowAnimation so reduce-motion / minimal-style modes
stay still.
4. In-editor style sheet. Bottom-right canvas chrome gains a
⚙ Style button that opens a modal bottom sheet with four
SwitchListTiles (glass, gradient backdrop, flow animation,
node shadows) plus 'Reset to default'. Edits live in
_styleOverride on the canvas state — operator can tweak the
look for the session without restarting or editing code.
No persistence in this version; follow-up could pipe the
override through SharedPreferences via the host.
Bumps fai_studio_flow_editor to 0.10.0.
Signed-off-by: flemming-it <sf@flemming.it>
Phase A of the per-field-output-ports roadmap.
FlowRunDriver gains optional moduleInfo(capability) hook
that returns a ModuleSpec carrying declared inputs +
outputs (each ModuleField has name, type, locale->
description map). Default returns null so legacy hosts
that didn't implement the hook keep compiling — the editor
falls back to YAML-reference-derived ports.
FlowCanvas caches ModuleSpec per step capability, kicked
off lazily on each build. When the spec lands, the canvas
rebuilds with:
- Per-field input ports on the LEFT (declared input names,
in stable manifest order) — replaces the with_-keys-as-
port-labels shape.
- Per-field output ports on the RIGHT — one anchor per
declared output field. A flow like summarize that
declares response/model_endpoint/model_name/model_digest
now exposes four distinct anchors instead of fanning
every downstream reference out of one collapsed point.
- Tooltips on each port label, picked from the field's
description.<locale> with English fallback.
NodeGeometry refactored: heightFor(inputs, outputs) takes
max(inputs, outputs); outputPortY(index) for the new
multi-port output side; outputAnchorY() preserves the
legacy single-anchor fallback so edges still draw before
the spec resolves.
Edges + hit-tester now use _outputPortPosition(stepId,
fieldName: edge.fromField). Field index lookup falls
through to outputAnchorY when the spec isn't loaded yet.
Patterns: ported modern, classic, blueprint, minimal from
jai_client's CanvasPatternPainter. Dropdown now has seven
choices (dots, grid, modern, classic, blueprint, minimal,
blank).
Bumps fai_studio_flow_editor to 0.9.0.
Signed-off-by: flemming-it <sf@flemming.it>
Visual-effect knobs (FaiEditorStyle) the host can override —
distinct from ColorScheme, which stays orthogonal. Today
Studio passes it through FlowEditorPage's new style:
parameter; tomorrow a theme plugin can ship both colours and
effects in one move.
Two presets ship today: modern (frosted glass, gradient
backdrop, animated flow, shadowed nodes — the default) and
minimal (everything off, flat surfaces — accessibility /
reduce-motion / low-spec).
Properties panel rebuilt as a floating sidebar with
BackdropFilter blur when glass-style is on; falls back to
the pre-0.7 flush-divider layout under solid style.
Pattern + zoom controls promoted to PopupMenuButton dropdowns
so operators reach a specific zoom level / pattern in one
click instead of cycling.
Bumps fai_studio_flow_editor to 0.8.0.
Signed-off-by: flemming-it <sf@flemming.it>
A 2026-feel pass on the visual language. Three structural
moves that change how the editor reads at a glance.
1. Wires now carry the colour of the data they transport.
Every edge leaving the inputs endpoint takes the
declared input's type accent — blue for text, orange
for bytes, purple for json, green for file, amber for
number. The operator can trace "this is the document
path, this is the prompt" by following colour, no
labels needed. Edges leaving step outputs stay neutral
(we don't have module manifests to look up the output
type yet — once the hub exposes them, this lookup
grows).
Hover / selection still wins the colour treatment so
the "I'm pointing at this one" signal isn't lost in
the type palette.
2. Edges entering a currently-running step animate.
New AnimationController on the canvas loops a 0..1
phase at 1.5 s when ANY step is running, stopped
otherwise (no idle cost). EdgePainter takes the phase
and renders animated edges as marching dashes in the
wire's accent colour over a 35%-alpha base — the
operator literally sees data moving along the wire in
the direction of flow while a step executes. When the
step completes, the animation stops, the wire returns
to a static stroke. Run a flow with a slow step and
the canvas comes alive.
3. Canvas gets depth.
- Background is now a subtle top-left → bottom-right
gradient between surfaceContainer and surface,
giving the working area a "lit centre, recessed
corners" cue instead of one flat dark page.
- Node cards swap their Material widget for an
AnimatedContainer with two-layer shadows: a sharp
short-blur shadow gives the card a real footprint,
a soft long-blur shadow casts depth onto the canvas
behind. Selected nodes get a third accent-coloured
halo so selection state reads from across the canvas.
- 180 ms ease-out-cubic transitions on the
AnimatedContainer mean hover-into-select and
run-status-changes morph smoothly instead of
snapping.
Edge stroke widths bumped slightly (2.1 / 2.8 from 2.0 /
2.6) so the typed wires read at a confident weight against
the new gradient backdrop.
Version 0.6.0 -> 0.7.0 — visible visual language change.
Signed-off-by: flemming-it <sf@flemming.it>
Closes four operator complaints from the 0.5.3 review.
1. Edges now react to hover.
Added an interaction layer between the edge painter and
the endpoint nodes. A MouseRegion tracks the cursor in
canvas coords; every move runs a spatial hit-test
against each edge's bezier sampled at 24 points. The
closest edge within 8 px highlights in the primary
accent so the operator sees what they're aiming at.
Cursor leaves canvas → highlight clears.
2. Right-click / long-press on edges opens a Disconnect
menu.
Same interaction layer carries `onSecondaryTapDown` and
`onLongPressStart` handlers. Both run the same hit-test
and pop the menu at the cursor. Disconnect clears the
target's expression (step.with[field] = '' or
outputs[name] = ''), edge disappears, target port flips
outlined.
3. Long-press is now available alongside right-click
EVERYWHERE the context menu lives — step nodes, port
dots, edges. Trackpad-only users whose "two-finger
click" isn't bound to secondary get the same affordances
as mouse users.
4. Background pattern is now operator-selectable: cycles
through dots → grid → blank via a small icon button on
the bottom-right canvas controls. The button's icon and
tooltip reflect the next state. Picked dots as the
default because they're least visually noisy at scale.
5. Zoom indicator: a mono "100%" readout next to Fit-to-
screen shows the current InteractiveViewer scale,
updating live as the operator pinches / scrolls. Tap
to snap back to 100 % without losing pan position.
The bottom-right control cluster is now:
[pattern] [reset layout] [fit to screen] [zoom%]
Version 0.5.3 -> 0.6.0 (minor bump — new interaction
surfaces + visible toolbar additions).
Signed-off-by: flemming-it <sf@flemming.it>
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 <sf@flemming.it>
Rewrites the port-rendering model around the operator's
expectation that each port is a single visible dot with
clear state. Closes four concrete complaints from the
0.4.0 review.
Removes the redundant inline dot:
- FlowNode._body no longer paints any inline 8-px dot.
The canvas-side dot is the SINGLE visible port. Size
matches the label row (12-px diameter, NodeGeometry.
portDotSize). No more "two dots per port" doubling.
Sides + alignment:
- New NodePortSide enum on FlowNode. `left` for step
inputs + outputs endpoint inputs; `right` for the
inputs endpoint (its body labels represent OUTPUTS of
the node — data flows OUT to downstream steps, so the
port dots belong on the right edge).
- Body labels right-align on right-side nodes,
left-align elsewhere. The 18-px port-gutter inside the
body keeps the dot from overlapping label text on the
port-bearing edge.
- Inputs endpoint dots now hang off the RIGHT edge of
the node, matching where the visible label gravitates.
Previously the dot was on the right but the labels
crowded the left — visually disconnected.
State-aware rendering:
- Port dots render FILLED when participating in an
edge, OUTLINED when dangling. `_connectedPorts` walks
graph.edges once per build and marks every endpoint.
Operator sees at a glance which inputs are wired vs
which need attention.
- Type-coloured: inputs endpoint dots take the type
accent (text → primary, bytes/file → tertiary,
json → secondary, number → amber, unknown → muted).
Edge deletion via port context menu:
- Right-click on a wired input port (step or outputs
endpoint) opens a Disconnect popup. Confirming clears
the with-field / output expression to empty, edge
disappears, port flips outlined. No need to chase the
YAML buffer manually.
- Unwired ports get no context menu (nothing to
disconnect) — keeps the menu honest.
The drop-target halo on drag stays — closest input port
within snap distance still inflates to 18 px and gets a
brighter halo, so the connection-drawing UX is unchanged.
Version 0.4.0 -> 0.5.0 — refactored API + visible port
model change warrants the minor bump.
Signed-off-by: flemming-it <sf@flemming.it>
Four operator-visible improvements in one cut.
Port geometry (the "connection dots don't line up with the
labels" bug):
- Header now has a fixed two-line structure (title + 16-px
subtitle slot) regardless of node kind, so the body's
port-row Y-offsets are identical across step nodes and
endpoint nodes.
- NodeGeometry constants rewritten so the inline 8-px dot
inside each port row and the 16-px canvas-side dot both
compute to the same Y. Port rows now anchor where the
eye expects them.
Port type colours:
- Inputs endpoint labels carry `name: type` (e.g.
`doc: bytes`), so the FlowNode body parses the type and
tints the inline dot per type: text → primary,
bytes/file → tertiary, json → secondary, number → amber,
unknown → muted. Step input ports stay muted until a
future commit can read module manifests for type info.
Endpoint editor:
- Inputs and outputs nodes are now selectable. Selecting
opens a dedicated editor in the properties panel:
add / rename / retype / remove entries graphically
instead of forcing the operator into the text tab.
Inputs editor offers a Type dropdown
(text / bytes / json / file / number);
outputs editor exposes name + expression
(e.g. `$step.field`).
Unsaved badge:
- The toolbar's tiny 8-px dirty dot was easy to miss.
Replace it with a coloured "unsaved" / "ungespeichert"
chip + colour the file name itself in the primary accent
when dirty. Operators see at a glance that a flow has
unsaved changes, which matters because the Discard
confirmation prompt won't fire if they don't realise
they made changes.
Version 0.3.0 -> 0.4.0 — first new editor feature surface
since 0.3.
Signed-off-by: flemming-it <sf@flemming.it>
Two operator-visible features filling concrete gaps the
jai_client editor exposed:
- Right-click on any step node opens a popup menu at the
cursor position with three actions:
* Duplicate — clones the step with a unique id
(<base>_2, <base>_3, …) and offsets its position so
the copy is visible next to the original.
* Disconnect all inputs — keeps every with-field key
but clears its expression value, so the step still
declares the same inputs but no longer wires them to
any upstream step.
* Delete — removes the step. Any dangling $refs in
downstream steps become run-time errors; we don't
silently rewrite their YAML because the surprise
would be worse than the explicit error.
- Floating "Reset layout" button on the canvas (next to
Fit to screen). Wipes the layout sidecar so AutoLayout
re-runs from scratch and the topological column
placement is restored. Auto-fits afterwards. Useful
when manual drags have drifted into spaghetti and the
operator wants a clean slate without losing the flow.
Implementation:
- FlowNode gains an `onContextMenu(Offset globalPos)`
parameter, wired to GestureDetector.onSecondaryTapDown.
- FlowEditorController.resetLayout() — clears layout,
re-seeds via AutoLayout, saves sidecar, notifies.
- The menu uses Flutter's showMenu() at the global
cursor position for native-feeling positioning.
Version bumped 0.2.2 -> 0.3.0 — first new operator-visible
feature set since the v0.2 WYSIWYG release.
Signed-off-by: flemming-it <sf@flemming.it>
Full rewrite of the editor surface, layered on top of the
FlowGraph foundation. One in-memory flow drives three tabs
that the operator can flip between freely:
- Graph: a drag-and-drop canvas. Nodes are step cards with
port dots on their left (one per `with:` field) and a
combined output port on the right. Pinned inputs and
outputs pseudo-nodes sit at the left and right edges so
every flow has a visually obvious source and sink. Pan +
zoom via InteractiveViewer; drag a node by its body to
reposition it (positions persisted to a sidecar JSON file
under ~/.fai/data/flows/.layout/<name>.json — kept OUT of
the YAML so `fai run` stays byte-stable).
- Text: the existing YAML CodeField with expands:true so
line 1 anchors at the top edge. YAML-aware syntax
highlighting picks up the theme's primary / secondary /
tertiary palette for keys / strings / numbers.
- Run: an inputs form (text fields + file-pick), a Start
button that calls the host's FlowRunDriver, a live step
list driven by the driver's event stream (matches the
`fai run` CLI rendering — ◻ pending, · running, ✔ done +
duration, ✗ failed, ⏸ awaiting approval), and the typed
outputs once the run resolves.
Source of truth = the YAML text. Graph edits emit fresh YAML
into the shared CodeController; text edits re-parse the
graph on a 350 ms debounce. Layout sidecar persists drag
positions only.
New public API (lib/fai_studio_flow_editor.dart):
FlowEditorPage(
initialFlowName: ...,
locale: ...,
runDriver: FlowRunDriver?, // NEW — host bridge
availableCapabilities: List<String>, // NEW — for the
// capability picker
// dialog when adding
// a step
)
The host (Studio) implements FlowRunDriver to bridge the
hub's gRPC SDK into the editor's event vocabulary. The
StepStarted/Completed/Failed/AwaitingApproval events are
shared verbatim with the CLI's run_progress renderer so
both surfaces speak the same visual language.
Files in this commit:
- lib/src/editor_controller.dart — shared state +
debounced reparse loop
- lib/src/run_driver.dart — host bridge
interface + event types
- lib/src/widgets/flow_canvas.dart — pan / zoom / drag /
port-to-port connection drawing
- lib/src/widgets/flow_node.dart — node card primitive
(module / approval / inputs / outputs variants)
- lib/src/widgets/edge_painter.dart — single CustomPainter
for every edge + draft drag line, cubic bezier with
arrow-head caps
- lib/src/widgets/properties_panel.dart — right-side editor
when a step is selected (rename id, change capability, add
/ remove / rename with-fields, delete step)
- lib/src/widgets/capability_picker.dart — searchable list
dialog used by Add-step
- lib/src/widgets/run_tab.dart — inputs form +
live step progress + outputs renderer
- lib/src/flow_editor_page.dart — host scaffolding,
toolbar, file list, three-tab body, keyboard shortcuts
- lib/src/l10n.dart — EN + DE strings for
every new label
- lib/fai_studio_flow_editor.dart — exports the new
public types (FlowRunDriver, FlowRunEvent variants,
FlowOutputValue variants)
flutter analyze: 0 issues. flutter test: 7/7 green.
Signed-off-by: flemming-it <sf@flemming.it>