Commit graph

35 commits

Author SHA1 Message Date
303e318ba8 feat(editor): Fix-dialog from diagnostic strip + inline approval in Run tab
Two big operator-UX wins that the previous batch missed:

1. Diagnostic-strip header gets a 'Beheben' (Fix) button next
   to 'Copy all'. Clicking opens a focused modal listing every
   issue with its quick-fix actions plus an 'apply every quick
   fix' master button. Replaces the cramped expand-strip flow
   for operators who want a deliberate dialog instead of
   scrolling in a 200-px footer.

2. Run tab inlines the Approve / Reject form right under any
   step that the hub paused on system.approval@^0. The
   FlowRunDriver gains three new methods (host-implements
   them; defaults throw with a clear message):

     - pendingApprovalIdForStep(flowName, stepId) → String?
     - approveApproval(approvalId, reviewer) → Future<void>
     - rejectApproval(approvalId, reviewer, reason)

   The _InlineApprovalCard polls the driver for ~3s waiting for
   the hub to materialise the approval row (event-stream race
   against the hub's create), then renders the same form the
   Approvals page does — reviewer + optional reason + Approve /
   Reject buttons. After submit the hub picks up the decision
   on its next poll and emits step.approved / step.rejected,
   which the existing event stream already maps to the right
   step status.

   Closes the operator question 'wie soll das gehen dass da
   Freigaben landen?': they land in the Approvals page AND
   inline in the Run tab; the inline path is now the natural
   workflow.

All 36 editor tests + 24 Studio tests green. Bumped to 0.21.0.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-09 09:18:17 +02:00
8b38584555 feat(editor): flow list rows get icons + smarter byte formatting
The FLOWS sidebar entries showed just "name" + "1234 B" — two
plain lines, no glyph, no visual hierarchy. Replaced with:

  - leading 28×28 rounded icon tile (account_tree_outlined),
    accent-tinted when the row is active so selection reads
    at a glance.
  - active row title bumps to FontWeight.w600 + onSecondaryContainer.
  - sizes formatted compactly: bytes / KB / MB so a 4.3 KB
    flow doesn't render as "4321 B" and a long one doesn't
    overflow into a multi-line ellipsis.

Bumped to 0.20.1.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-09 01:57:30 +02:00
5cd2745db5 feat(editor): localised analyzer/strip/run messages + monospace font fallback
Closes operator-reported issues around the text-tab font and
the English-only run + diagnostic surfaces.

  - **Real monospace everywhere**. The package never bundled
    JetBrains Mono as an asset (it relied on Studio's
    google_fonts pre-load), so a host that doesn't ship the
    font saw the YAML editor render in the system proportional
    default. New _monoTextStyle() pins fontFamilyFallback to a
    cross-platform monospace chain (Menlo / Consolas / Courier
    New / monospace) so the editor stays a grid in every host.
    Applied to the code field, gutter, diagnostic strip,
    issue rows, hover card, fix buttons, and the run tab's
    error box.

  - **Locale-aware analyzer messages**. New AnalyzerStrings
    adapter holds every string the analyzer emits, with an
    .english default + an .from(FlowEditorStrings) factory.
    FlowYamlCodeController.setCapabilityProviders takes the
    strings; FlowEditorPage wires them from the active locale.
    The analyzer's 'Unknown capability', 'Did you mean',
    'Not in the store — install locally with fai install
    --link', 'Unknown input/output type', YAML parse errors,
    and every quick-fix label (Install / Add source / Use /
    Change to) now flip to DE when the editor's locale is DE.

  - **Localised run + diagnostic chrome**. The bottom strip's
    'No issues', '2 errors · 1 warning', 'Copy all'; the
    issue row's L-prefix + Copy tooltip; the hover card's
    L-prefix + Copy tooltip; the run-block tooltip + inline
    message ('2 Fehler verhindern den Lauf · siehe
    Diagnose-Leiste unten'); the step-row 'awaiting approval'
    suffix; the CopyableErrorBox's Copy / Copied tooltip —
    all now flow through FlowEditorStrings.

Bumped to 0.20.0.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-09 01:36:39 +02:00
b6bb8741a9 feat(editor): persistent diagnostic strip + run-block + copyable run errors
Three connected fixes that close the loop the analyzer started:

  - **Strip lives at page level, not text-tab**. Moved
    _DiagnosticStrip out of _textTab and into _tabbedBody
    below the TabBarView, so the same error list + quick-fix
    buttons are visible on Graph + Text + Run. The Graph-tab
    operator now sees both the pulsing node AND the message
    explaining what's broken, without flipping tabs. One strip,
    one source of truth.

  - **Run button disabled when there are analyzer errors**.
    FlowEditorController.analyzerErrorCount counts `IssueType.error`
    issues; the action strip's Run button + the RunTab's Start
    button both check it. When > 0:
      · button greys out + icon flips to Icons.block
      · tooltip names the count ('2 errors prevent the run')
      · the Run tab grows an inline red explanation next to
        the disabled Start so the operator isn't left guessing
    Warnings (orange) do NOT block — they're nudges. Operators
    who want to run a half-broken flow during dev can still
    silence the analyzer via the strip.

  - **Run failures are copyable everywhere**. New
    _CopyableErrorBox replaces both the per-run failure detail
    and the per-step failure suffix in the RunTab. Selectable
    monospace + explicit Copy button with 'Copied' feedback.
    Scrollbar-capped at 220 px so the box doesn't push outputs
    off-screen. Mirrors the operator-visible contract:
    'every error is copyable, always.'

Closes the operator-reported regression where a failed run
showed 'Lauf fehlgeschlagen' as plain Text, leaving the
operator unable to paste it into a bug report.

Bumped to 0.19.0.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-09 01:13:45 +02:00
efdfa7dd79 feat(editor): graph-pulse on errors + store-aware install / add-source
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>
2026-06-09 00:21:48 +02:00
f43c1ac6cf feat(editor): copyable diagnostics + hover tooltip + quick fixes
Three operator-UX gaps closed in one pass — the diagnostic
strip stayed plain Text (no copy), the wavy underline gave no
hover tooltip (had to read the strip), and there was no way to
act on an issue without leaving the editor.

  - **Selectable + copyable strip**. Both the header summary
    and the per-row issue message are now SelectableText. Per-
    row Copy button (compact icon) lives next to the message;
    header carries a 'Copy all' that copies every issue as
    'L7: <message>' lines. Trackpad-only operators no longer
    have to use the system selection gesture.

  - **Hover tooltip on the wavy underline**. The controller
    attaches TextSpan.onEnter / onExit handlers to every
    issue-overlapping leaf and publishes IssueHoverRequest via
    a ValueNotifier. FlowEditorPage listens and inserts an
    OverlayEntry tooltip card near the cursor. Card carries
    its own MouseRegion that cancels the dismiss timer so the
    operator can slide INTO it to click the action buttons.

  - **Quick fixes for the two most common issues**:
      · 'Unknown capability X' → InstallCapabilityFix (delegated
        to host via the new onInstallCapability callback). On
        success, controller.setAvailableCapabilities + reanalyze
        clear the issue automatically.
      · 'Unknown type Y' with a Levenshtein-distance-≤2 match
        → ReplaceLineValueFix. Applied by the editor itself:
        line is mutated, key + indent preserved, comment
        preserved, then reanalyze.
    Distance > 2 stays unfixed — pushing 'zonglefax' to 'bytes'
    would be worse than no suggestion.

New public surface (exported from the package):

  - QuickFix sealed base + InstallCapabilityFix + ReplaceLineValueFix
  - InstallCapabilityCallback typedef
  - IssueHoverRequest + IssueHoverSeverity
  - FlowEditorPage.onInstallCapability prop

Tests:
  - FlowAnalyzer attaches InstallCapabilityFix to capability
    issues
  - FlowAnalyzer suggests the closest valid type for typos
  - FlowAnalyzer emits no fix when the typo is too far

All 33 editor tests green. Bumped to 0.17.0.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-09 00:01:51 +02:00
1b50924e16 fix(editor): drop misaligned gutter error pin, add bottom diagnostic strip
Two glitches visible in 0.15.1:

  1. The flutter_code_editor gutter renders an Icons.cancel pin
     for every analyzer issue, hard-positioned inside a 16-px
     slot. With a single-digit indent the icon centres look off;
     with double-digit line numbers it shifts further.
  2. The hover popup for that pin paints at `offset.dx + width`
     of the gutter cell — which lands INSIDE the code area, so
     'Unknown capability ...' overlaps the YAML text the
     operator is trying to read.

The package doesn't expose a fix for either — the icon is a
const in error.dart, the popup position is hard-coded in the
StatefulWidget. Cleanest path: hide the gutter error column
entirely (`GutterStyle.showErrors: false`) and surface the
analyzer issues through a dedicated bottom strip we own.

The new _DiagnosticStrip renders as a single 22-px line under
the editor: a coloured dot + 'N errors · M warnings · L7:
${first.message}'. Tap expands into a per-issue list capped at
140-px scroll, dot-coloured by IssueType. Empty state shows a
muted 'No issues' so the strip never disappears and never
shifts the layout.

Wavy underlines in the text body keep working untouched — the
two surfaces complement each other: the underline points at
the broken token, the strip names what's wrong.

Bumped to 0.16.0.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-04 10:32:40 +02:00
911f368362 fix(editor): match real F∆I YAML type shape + warn on bad types
The 0.15.0 type-token coloring regex hunted for `type: <name>`,
but the F∆I YAML actually shapes types as `<fieldname>: <name>`
under `inputs:` / `outputs:` blocks. Real-world flows
(`hello.yaml`, `extract.yaml`) and module manifests (echo,
text-summarize, …) never produce the `type:` keyword unless
the operator hand-authors the long form.

Fix the regex to match an indented `KEY: VALUE` line where the
value is one of `text|json|bytes|file|number|integer`. Leading
whitespace is required so top-level keys (`name: foo`,
`version: 0.1.0`) can't false-match. This handles both
shapes — the implicit `pdf: bytes` and the explicit
`type: bytes` (used by module schema v2 inputs lists) —
because either way the pattern boils down to "key colon known
type token".

Analyzer also grows a type-token check: any inputs/outputs
field whose value isn't a known type lights up as a warning
("Unknown input type 'byes' …"), modulo `$ref` expressions
(flow outputs) and empty values (operator is mid-keystroke).

Adds `test/flow_analyzer_test.dart` with seven cases covering
empty, valid hello, unknown capability, unknown type, parse
error, version-bare matching, and the empty-installed-list
silence path.

Bumps the package to 0.15.1.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-04 02:24:29 +02:00
885d2db4e1 feat(editor): type-token coloring + analyzer diagnostics
The text tab now colours `type: text|json|bytes|file|number`
values in the same hues the graph canvas uses for the wire of
that type — a glance at the YAML confirms what a glance at the
graph shows. Promoted the wire-colour palette to a single
source of truth in `wire_colors.dart` so the graph, the
properties panel and the text tab can't drift.

Adds `FlowAnalyzer` (extends `AbstractAnalyzer`) so the gutter
shows error pins and broken lines get wavy red underlines for:

  - YAML parse errors (source-span pinned to the offending line)
  - `use:` referencing a capability the operator hasn't installed
    (bare provider/name match — `text.echo@^1` and `text.echo`
    compare equal)

Editor host passes a closure into `setAvailableCapabilities` so
the analyser always sees the current installed list without
re-creating the controller on every Studio rebuild. Bumps the
package version to 0.15.0.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-04 00:46:51 +02:00
flemming-it
73736769a0 feat(editor): tab-aware action strip + simpler tap handling
Reposition the per-tab action buttons under the TabBar so the
operator's eye flows TabBar → context buttons → canvas. Graph
keeps Add-Step + Save + Run, Text trims to Save + Run, Run
hides them entirely (the Run tab has its own start button).

Drop the outer wrapping GestureDetector that competed with the
inner Positioned.fill detector inside the canvas Stack — two
TapGestureRecognizers in the same arena meant the inner one
sometimes lost edge-tap and background-tap.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-02 16:32:27 +02:00
59aa8fe78e feat(editor): edge selection + properties-panel edge-info + canvas-wide deselect
Three flow-editor UX upgrades:

1. Wider deselect zone. A new translucent
   Positioned.fill GestureDetector wraps the outermost
   Stack (above the InteractiveViewer). Tapping the dark
   area outside the canvas grid — which used to do
   nothing — now closes the properties panel.

2. Edge selection. Click a wire → controller.selectEdge
   stamps the edge key. Click again or click background →
   deselects. Selection is mutually exclusive with step
   selection, enforced inside the controller so the panel
   never tries to render both. Selected edges highlight
   with the same accent the hover state uses.

3. Properties panel grows an edge-info mode. When an
   edge is selected, the panel shows:
   - source qualified name (e.g. inputs.document or
     summarize.response)
   - target qualified name
   - type colour swatch + name per endpoint (LabVIEW
     palette)
   - type-compatibility pill (green / red)
   - Disconnect button that removes the edge in one click
   l10n: 8 new edgeInfo* strings in EN + DE.

Editor tests still pass (20).

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-02 15:22:46 +02:00
1e9968496e feat(editor): properties-panel toggle + LabVIEW-style type colours
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>
2026-06-02 11:13:01 +02:00
7b256b5e35 feat(editor): type-checked connections + card-height fix + widget tests
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>
2026-06-02 01:29:00 +02:00
a1d25fd48a fix(editor): solid-filled connected port + larger dot + tooltip fallback
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>
2026-06-02 00:56:14 +02:00
3a6e92fe09 fix(editor): per-field output dots + dynamic card width
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>
2026-06-02 00:37:03 +02:00
7797654572 fix(editor): per-field ports render from YAML refs when ModuleSpec is missing
Stefan reported: per-field output ports never appeared even
after the hub-side capability-lookup fix. Root cause: the
editor only used ModuleSpec when the host returned one. If
the operator's hub still ran the pre-fix binary, or hadn't
re-installed v3 manifests, or the step's capability was a
built-in (system.approval) that ships no manifest at all,
the editor would silently collapse to a single output anchor
even though the flow YAML clearly references named fields.

Robust fix: per-field labels are now a UNION of declared
fields (from the manifest, if any) and implicit ones
(inferred from the flow graph itself — every $step.field
reference an outgoing edge carries adds a field name).
Declared fields keep manifest order; implicit ones follow
alphabetically. Two helpers (_inputLabelsForStep,
_outputLabelsForStep) single-source the merge so the
renderer, port-overlay generator, edge router, and
hit-tester all see the same list.

What this means at run time:
- v3 modules with description.en/de → full per-field ports
  with tooltips, exactly as designed.
- v2 modules without descriptions → per-field ports, no
  tooltips. Was the case already.
- v1 modules / hubs without inputs/outputs in
  ModuleInfoResponse → per-field ports inferred from the
  YAML references. New behaviour, no regression.
- Built-in capabilities (system.approval) → per-field ports
  from with_-keys and outgoing edges. Works without any
  hub manifest.

Bumps fai_studio_flow_editor to 0.10.1.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-01 23:11:39 +02:00
a71f654162 feat(editor): reduce-motion clamp + edge gradient + breathing pulse + style sheet
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>
2026-06-01 22:55:18 +02:00
b1fe765468 feat(editor): per-field input/output ports + i18n tooltips + 4 jai_client patterns
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>
2026-06-01 22:32:26 +02:00
1f5601a461 feat(editor): FaiEditorStyle + frosted glass panel + pattern/zoom dropdowns
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>
2026-06-01 18:10:51 +02:00
7aeeae717f feat(editor): wires carry their type — animated flow + canvas depth
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>
2026-06-01 17:45:27 +02:00
5ff7a1c118 feat(editor): edge hover + edge menu + pattern picker + zoom indicator
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>
2026-06-01 17:36:24 +02:00
5c59f3a554 fix(editor): draft line reaches cursor + ports react on hover
Two operator-visible fixes from the 0.5.2 review:

(1) Draft line stopped short of the cursor. EdgePainter
shortens every segment endpoint by portRadius to dock at
port-dot perimeters — but the DRAFT segment's `to` is the
cursor itself, not a port. Result: while dragging a
connection, the live line ended 6 px short of where the
operator's mouse actually was. Felt like the cable
wouldn't reach.

EdgeSegment gains `shortenFrom` / `shortenTo` flags
(default true). The draft segment passes `shortenTo:
false`, so the line tip tracks the cursor exactly. From-
side stays shortened because the FROM is still a real
port dot.

(2) Ports had no hover affordance — mouse-over was
invisible. Operators couldn't tell ahead of time that a
port was interactive.

Adds `_hoveredPort` state on the canvas, updated via the
existing MouseRegion's onEnter / onExit. Port dot now has
three focus levels:

  resting → 12 px, 1.8 px border, no glow
  hover   → 15 px, 2.2 px border, soft accent glow
  drop-target (drag is over it) → 18 px, 2.5 px border,
            stronger glow

The hover state pre-shadows the drop-target state —
operators see "yes, this port is grabbable / droppable"
before they commit to dragging. When the drag does start,
the drop-target halo is the same family of treatment,
just stronger, so the visual progression reads as one
continuous interaction.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-01 17:13:14 +02:00
9afb51a4b5 fix(editor): port docks elegantly — line terminates at perimeter
Stefan's "the connections to the round ports look
unelegant" feedback was about three concrete things:

(1) The bezier endpoint sat at the port dot's GEOMETRIC
CENTRE. The dot was rendered ON TOP, so the line visually
disappeared into the centre of the dot — looked like a
thread being eaten by a bead.

(2) A separate arrow-head triangle was drawn on top of the
dot, doubling the visual terminator and making the
endpoint look "noisy".

(3) The dots were hollow rings the same colour as the
line, so the boundary between line and dot blurred
visually. They didn't read as connectors.

Fix:

 - EdgeSegment now carries `fromSide` + `toSide`
   (left / right) so the painter knows which way to
   shorten each endpoint. The bezier ends `portRadius` px
   short of the centre — exactly on the dot's outer
   perimeter, on the side facing the line. The line now
   "docks" cleanly at the rim.

 - The end-cap triangle arrow is gone. The dot itself is
   the visual terminator; an arrow on top was redundant.

 - Port dot redesigned as a connector-socket: outer ring
   defines the footprint, inner 4-px surface-coloured pin
   appears when connected. The result reads as "an active
   socket with a plug seated in it" rather than "a hollow
   circle next to a card edge". Empty ports stay surface-
   filled rings — unambiguous empty-slot signal.

 - Bezier handle length floor raised from 40 to 60 px so
   vertical detours (e.g. inputs-endpoint → step far below)
   curve gracefully out of the start before dropping
   instead of kinking near the origin.

 - Stroke width nudged from 1.8 to 2.0 px for the normal
   accent so the line carries the right visual weight
   against the new docked-at-perimeter port treatment.

Visual: lines now look like cables seated into sockets,
not strings vanishing under beads.

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-01 17:03:01 +02:00
e4e2d45374 feat(editor): five distinct type colours + wired/total badge in step header
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>
2026-06-01 16:47:29 +02:00
099cd182b9 feat(editor): unified port model — one dot per port, sided + typed + state-aware
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>
2026-06-01 16:44:59 +02:00
fb8892687d feat(editor): port geometry fix + type colours + endpoint editor + unsaved badge
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>
2026-06-01 16:31:28 +02:00
296e5bfd01 feat(editor): right-click context menu + reset layout
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>
2026-06-01 16:15:57 +02:00
f8138f3516 chore(editor): version 0.2.2 — picks up save snackbar + drop highlights
0.2.1 + the save-success snackbar (307eacc) + drop-target
highlighting during a connection drag (fb87309).

Signed-off-by: flemming-it <sf@flemming.it>
2026-06-01 02:00:03 +02:00
cae5c94c4d docs(editor): README + version bump to 0.2.1
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>
2026-06-01 01:53:29 +02:00
dbd9a0004f feat(model): FlowGraph + LayoutStore foundation for WYSIWYG editor
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>
2026-06-01 00:37:19 +02:00
daf6732893 fix(editor): true top-align via CodeField expands + move refresh
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>
2026-06-01 00:05:49 +02:00
a7485cadb5 fix(editor): always top-align content, even for short files
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>
2026-05-31 23:29:00 +02:00
7655e0dc32 fix: use fullText setter — content from previous file no longer leaks
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>
2026-05-30 16:07:44 +02:00
8b918f8f2a fix: dirty detection — compare fullText, snapshot after load
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>
2026-05-30 14:56:39 +02:00
F∆I Platform
51f9a1d2b1 feat: initial scaffold — swappable flow editor
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>
2026-05-30 14:33:03 +02:00