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>