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>
Expands README to describe the three-tab UX, the source-of-
truth model (YAML + sidecar layout), the FlowRunDriver
contract, the folder layout, and how to swap the editor.
Version 0.2.0 -> 0.2.1 — picks up the live step status,
the empty-graph CTA, and the fit-to-screen button added
since the initial 0.2.0 commit.
Signed-off-by: flemming-it <sf@flemming.it>
First piece of the v0.2.0 rewrite. The graph view, text view,
and run view will all share a single source of truth: the
YAML text. This commit introduces the parsing layer + the
sidecar that keeps layout metadata OUT of the YAML.
FlowGraph (lib/src/model/flow_graph.dart):
- Parses + emits the F-Delta-I flow YAML shape (inputs +
steps + outputs + leading comment block).
- Recognises both inputs shorthand (`name: text`) and the
expanded form (`name: { type: text, default: ..., hint: ... }`).
- Detects edges by scanning step.with + outputs for
$source.field references; understands both the short
$x.y form (canonical) and the long ${{ x.y }} form (legacy).
- Flags system.approval@ steps via FlowStep.isApproval so
the graph can paint them with the human-gate styling.
- Immutable update helpers (withStepUpdated, withStepAdded,
withStepRemoved) used by the property panel + add-step
button.
- tryParse returns null on YAML syntax errors so the text
tab can keep the last valid graph while the operator
types.
LayoutStore + FlowLayout (lib/src/model/layout_store.dart):
- Per-flow JSON sidecar at
~/.fai/data/flows/.layout/<name>.json. Atomic write via
tmp + rename so a crash mid-save leaves the previous
copy intact.
- The YAML stays byte-identical to what `fai run` consumes
— positions never bleed into the flow file.
AutoLayout (lib/src/model/auto_layout.dart):
- Deterministic topological column layout for flows that
have never been opened in the graph view before.
- Steps with no $step refs sit in column 0; steps whose
refs all point at column-0 steps sit in column 1; etc.
- Cycles + dangling refs collapse to column 0 so nothing
is ever invisible.
Tests (test/flow_graph_test.dart): 7 cases cover canonical
parsing, edge detection across both ref forms, approval-step
flagging, leading-comment round-trip, structural round-trip,
parser robustness on broken YAML, immutability invariant.
All pass.
Version 0.1.4 -> 0.2.0 (minor bump — significant new module
graph; public FlowEditorPage constructor API unchanged).
Signed-off-by: flemming-it <sf@flemming.it>
Two changes Stefan flagged on 0.1.3:
(1) Short files were still vertically centered. The previous
fix (CrossAxisAlignment.stretch on the Row) only stretched
the SingleChildScrollView viewport — the CodeField inside
still had its natural content-sized height and sat centered
in the larger viewport. Drop the SCV wrapper entirely and
set `expands: true` (with `minLines: null` + `maxLines:
null`, the underlying TextField's required combination) on
the CodeField. The widget now fills its parent's bounded
height, the line-number gutter runs the full editor height,
and line 1 sits at the literal top edge. Standard IDE
behaviour.
(2) The round outlined Refresh button sandwiched between
New (filled-tonal) and Save (filled-tonal) in the editor
toolbar was a visual-style outlier — Stefan called it
"dazwischen, sieht falsch aus". Refresh is also
semantically scoped to the file list, not the open
editor, so move it out of the editor toolbar entirely and
into a small FLOWS header bar at the top of the file-list
panel. The editor toolbar now reads:
[Back] file.yaml ● [Spacer] [New] [Save] [Run]
— four filled-tonal/filled buttons, no style outliers.
The file-list panel reads:
┌───────────────────────┐
│ FLOWS ⟲ │
├───────────────────────┤
│ flow-a │
│ flow-b │
└───────────────────────┘
Adds `listHeader` to FlowEditorStrings (de + en both
"FLOWS" — same word, all-caps).
Version 0.1.3 -> 0.1.4.
Signed-off-by: flemming-it <sf@flemming.it>
Row was using CrossAxisAlignment.center by default, so a
short YAML file appeared visually centered in the editor
viewport — uncomfortable in any IDE-style surface where
content is expected to start at the top edge regardless of
how much of the viewport it fills.
Set CrossAxisAlignment.stretch on the outer Row so the
SingleChildScrollView fills the full vertical extent. The
SCV's default scroll origin is the top, so once the
viewport is taller than the content the file renders at
the top with empty space below.
Version 0.1.2 -> 0.1.3.
Signed-off-by: flemming-it <sf@flemming.it>
Stefan reported that opening a flow (hello) sometimes showed
content from a different flow (extract-with-approval). Cause:
flutter_code_editor's CodeController distinguishes between
`text` (the visible buffer, post-fold) and `fullText` (the
underlying source). Setting `_code.text = newFile` only
updates the visible buffer; the underlying `_code` data
structure retains the previous file's source, so subsequent
folding/scrolling can surface its content.
Switch all three load sites + the save site to the canonical
setter:
Before _code.text = text (visible buffer only)
After _code.fullText = text (replaces underlying source)
Save also moves to writing `_code.fullText` so any text the
operator collapsed behind a fold survives the round-trip.
Verified against flutter_code_editor 0.3.5 source — the
fullText setter calls `_updateCodeIfChanged` then re-emits
`TextEditingValue(text: _code.visibleText)`, which is the
documented "fully reset the editor" path.
Bump version 0.1.1 → 0.1.2. Studio's pubspec already pins
`ref: main` so the next `flutter pub upgrade` picks it up.
Signed-off-by: flemming-it <sf@flemming.it>
Stefan reported false-positive "Discard unsaved changes?"
dialogs when clicking around in the file list. Cause: the
naive `_code.text == _loadedText` check fired stale even
right after _code.text = text, because CodeController
silently rewrites the visible text during the assignment
(trailing-newline normalisation, fold marker insertion on
some YAML constructs, …) so _code.text deviated from the
just-stored _loadedText immediately.
Fix:
* Drop `_loadedText` field, replace with `_baseline`.
* Set `_code.text = ...` first (outside setState), then
capture `_baseline = _code.fullText` inside setState.
fullText is the canonical post-processed value, not the
visible-after-folding text — comparing fullText against
fullText eliminates the spurious diff.
* _dirty getter now compares `_code.fullText != _baseline`.
Applied to all four load / save / new-flow sites.
Bump version 0.1.0 → 0.1.1. Studio's pubspec already pins
`ref: main` so the next `flutter pub get` picks the fix up.
Signed-off-by: flemming-it <sf@flemming.it>
Extracted from fai/studio's lib/pages/flow_editor.dart so the
editor can be swapped out independently of the host.
Public surface kept minimal — a single FlowEditorPage widget
with three named parameters (initialFlowName, locale, onRun).
The package brings its own design tokens, empty/error
widgets, l10n table; no host-internal types leak through.
Studio depends on this repo via pubspec.yaml git reference.
Forks point Studio at a different URL and rebuild.
See README.md for the swap recipe.
Signed-off-by: F∆I Platform <platform@flemming.ai>