Commit graph

97 commits

Author SHA1 Message Date
flemming-it
805f1b4c1f fix(studio): theme picker confirms applied + surfaces load failures
Some checks failed
Security / Security check (push) Failing after 2s
Two operator-visible fixes prompted by 'I picked a theme but
nothing changed':

  - Theme picker tile taps now show a brief "Theme applied:
    <name>" snackbar — the picker applies themes instantly, so
    the operator needs a confirming cue. Slow `_pluginThemes`
    loads no longer read as "click did nothing".
  - `_pluginThemes` failures (plugin unreachable, manifest
    drift, etc.) write to `FaiLog` so `fai admin doctor` and
    Studio's inline log viewer can surface them. Previously
    swallowed silently, which is what made debugging this so
    miserable.
  - Settings dialog's primary button relabelled to
    "Connect to endpoint" (was "Save & connect") so operators
    don't mistake it for "save my theme choice". The theme
    selection persists at tile-tap time; the Settings dialog
    has nothing left to "save".
  - Theme section header gains an explicit "(applies instantly)"
    cue for the same reason.

Also fixes a pre-existing curly-braces lint in the n8n add-
endpoint dialog. Studio bumped to 0.62.1, editor pinned via
path override at 0.15.1.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-04 02:25:11 +02:00
flemming-it
8f5cd2528b feat(studio): central error helpers + inline log viewer + Today CTAs
Some checks failed
Security / Security check (push) Failing after 1s
Bundles the error-UX overhaul and the inline log viewer:

  - New `FaiLog` (`~/.fai/logs/studio-errors.log`, 256 KiB rotation,
    JSON-per-line). Every operator-visible failure is appended so
    `fai admin doctor` and the new viewer can show the trail
    without the operator having to reproduce the failure.
  - New `showFaiErrorSnack` / `showFaiErrorDialog` helpers wrap
    `FaiErrorBox` in copyable surfaces; 27 ad-hoc
    `SnackBar(content: Text(e.toString()))` sites swept to use
    them (settings, doctor, audit, store, module sheet, system-AI
    editor, flow output).
  - New `FaiLogViewer` modal (`showFaiLogViewer`) renders log
    files inline with line numbers, JSON-key + `[level]` token
    colouring, Copy-all, Refresh, Open-externally. Doctor's
    daemon-paths panel grows a "View" button next to "Open" for
    every `.log` row and now also lists the Studio errors log.
  - Today carousel: CTAs now actually re-run search after a
    `filterCategory` / `runQuery` story is tapped (was only
    flipping the chip state). Fallback story list bumped to 8.
  - Editor bumped to git ref carrying 0.15.0 (type-token
    colouring + analyzer diagnostics).

Studio bumped to 0.62.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-04 02:08:15 +02:00
flemming-it
2731062a13 feat(studio): generic Today stories + auto-hide after 3 modules + brand chips out + editor 0.13.0
Some checks failed
Security / Security check (push) Failing after 2s
Two visible UX shifts:

1. Store Today carousel.
   - Three hardcoded stories rewritten to be generic:
     'Modules are the building blocks',
     'Flows chain modules deterministically',
     'Bring your own sources' — no DeepWiki / Semgrep /
     text.extract / extract-summarize.yaml mentions. The
     concrete brand suggestions live in Settings → MCP
     Clients where the operator goes deliberately.
   - +DeepWiki / +Semgrep quick-add chips removed from
     the hero footer. They felt like ads on every page
     load. The standalone Suggested Sources strip below
     still picks them up, but the editorial hero stays
     clean.
   - Auto-hide the entire carousel once installedCount
     ≥ 3 — the operator has demonstrably been here
     before; the welcome strip stops earning its scroll
     cost.

2. Editor bump to 0.13.0:
   - Edge selection (click a wire to select it)
   - Properties panel edge-info mode with type-compat
     indicator + Disconnect button
   - Canvas-wide background tap closes the panel (works
     even when zoomed out so the grid doesn't fill the
     viewport)

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-02 15:23:23 +02:00
flemming-it
3db875c903 feat(studio): brand-agnostic onboarding hints + clickable checklist + theme-picker clarity
Some checks failed
Security / Security check (push) Failing after 2s
Three changes:

1. Onboarding checklist hints no longer mention DeepWiki,
   Semgrep, text.extract, or extract-summarize.yaml by
   name. Brand- and file-specific copy moved to neutral
   feature pointers ('add an MCP source', 'install a
   module'). Brand names belong on the welcome page only
   if they're load-bearing for understanding, which they
   aren't.

2. Checklist rows are now clickable. Tapping a row navigates
   to the page where the operator can complete it — MCP +
   AI open Settings, Module opens Store, Flow opens Flows.
   StudioShellState gains a public navigateTo(pageId) helper
   so descendants can drive sidebar selection without
   plumbing a controller through props.

3. Theme picker hint copy rewritten. Old hint suggested
   'Custom' was a single-colour tweak; new copy spells out
   that Material 3 derives a full palette (primary,
   secondary, tertiary, surface, …) from one seed colour.
   The Custom tile shows a small tune icon so its
   open-the-picker semantics is visually distinct from the
   apply-immediately plugin tiles.

Editor bumped to 0.12.0 (panel toggle + LabVIEW-style port
colours).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-02 11:13:38 +02:00
flemming-it
c1c60d5434 feat(studio): rich theme picker (presets + custom colour) + editor 0.11.0
Some checks failed
Security / Security check (push) Failing after 1s
Settings dialog's Theme Plugin section is now a grid of
swatched tiles:

- Built-in (none) — falls back to FaiTheme.light/.dark
- One tile per installed studio.theme.* plugin, each
  showing the plugin's primary/secondary/tertiary as
  live colour dots. Tile loads its preview lazily so a
  dozen installed themes don't block the picker.
- Custom — opens a colour-picker dialog with 12 curated
  Material presets + a hex input + live preview. Selecting
  applies ColorScheme.fromSeed for both brightnesses.

main.dart's _pluginThemes parses a 'custom:#RRGGBB' sigil
in the same notifier slot as plugin capability ids, so the
existing persistence + restoration paths cover the custom
case with no new state.

Bumps editor to 0.11.0 (type-checked port connections +
dynamic card width fix + card-height border allowance) and
Studio to 0.58.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-02 01:29:34 +02:00
flemming-it
69864da934 fix(studio): bump editor to 0.10.3 (solid filled ports + tooltip fallback)
Some checks failed
Security / Security check (push) Failing after 1s
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-02 00:56:29 +02:00
flemming-it
1353d1f7c1 fix(studio): bump editor to 0.10.2 (per-field output dots + dynamic card width)
Some checks failed
Security / Security check (push) Failing after 1s
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-02 00:37:37 +02:00
flemming-it
64ed0c90cb fix(studio): bump editor to 0.10.1 (per-field ports from YAML refs)
Some checks failed
Security / Security check (push) Failing after 2s
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 23:12:07 +02:00
flemming-it
9f35d725de chore(studio): bump editor to 0.10.0 (reduce-motion clamp, gradient, pulse, style sheet)
Some checks failed
Security / Security check (push) Failing after 1s
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 22:55:52 +02:00
flemming-it
a698be0b57 feat(studio): per-field ports in flow editor + store-doc scroll fix
Some checks failed
Security / Security check (push) Failing after 1s
Picks up:
- fai_studio_flow_editor 0.9.0: per-field input + output
  ports rendered from ModuleInfo, i18n tooltips, four
  additional canvas patterns ported from jai_client.
- fai_client_sdk 0.18.0: regenerated ModuleField bindings
  for the schema_version 3 / per-field-description proto
  bump in fai/platform.

Wires StudioFlowRunDriver.moduleInfo so the editor can
fetch declared field info per step capability. The hub
maps schema_version 3 manifest fields (inputs/outputs
with description.en/de) through ModuleInfoResponse; this
patch wraps them back into the editor's ModuleSpec /
ModuleField vocabulary so the canvas paints them as
distinct anchors with hover tooltips.

ModuleDetail in hub.dart now carries the same inputs +
outputs lists (with ModuleFieldInfo type + i18n
description map), enabling other Studio surfaces — e.g.
the module-detail sheet — to render bilingual field docs
in a later pass.

Plus: store-doc nested-scroll fix. The module detail
panel's docs section sat inside its own 480px viewport
inside the outer sheet's SingleChildScrollView, producing
the double-scrollbar feel. Setting
physics: NeverScrollableScrollPhysics on the Markdown
widget and dropping the maxHeight lets the docs scroll
as part of the outer sheet — one scrollbar, predictable
mouse-wheel behaviour.

Bumps fai_studio to 0.56.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 22:34:08 +02:00
flemming-it
34d086c29b chore(studio): bump flow editor to 0.8.0 (FaiEditorStyle + frosted glass)
Some checks failed
Security / Security check (push) Failing after 1s
Picks up:

  * FaiEditorStyle visual-effect knobs (frosted-glass panels,
    canvas gradient, flow animation, node shadows) the host
    can override. Studio uses the default modern preset for
    now; theme-plugin integration is a follow-up.
  * Properties panel rebuilt as a floating sidebar with
    BackdropFilter blur over the canvas.
  * Pattern + zoom controls promoted to dropdowns so
    operators reach a specific level in one click.

Bumps fai_studio to 0.55.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 18:12:05 +02:00
flemming-it
aeb71b833f feat(studio): editor 0.7.0 — typed wires + animated flow + canvas depth
Some checks failed
Security / Security check (push) Failing after 1s
Picks up fai_studio_flow_editor 0.7.0 (commit 7aeeae7):

 - Wires take the colour of the data they carry. Inputs-
   endpoint edges show their type accent (text=blue,
   bytes=orange, json=purple, file=green, number=amber)
   so the operator can trace flow data by colour alone.
 - Edges entering a currently-running step animate as
   marching dashes in the wire's accent colour. Stops
   when the step completes. The canvas comes alive
   during runs.
 - Canvas backdrop is a subtle gradient (corners recessed,
   centre lit). Nodes get layered shadows; selected nodes
   wear an accent-coloured halo. 180 ms ease-out
   transitions on hover / select / status changes.

Version 0.53.6 -> 0.54.0 (minor bump — first wave of the
2026-feel visual refresh).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 17:45:47 +02:00
flemming-it
54dd957cd0 chore(studio): editor 0.6.0 — edge hover/menu + pattern + zoom
Some checks are pending
Security / Security check (push) Waiting to run
Picks up fai_studio_flow_editor 0.6.0 (commit 5ff7a1c):

 - Edges highlight on hover (8 px hit radius around the
   bezier path).
 - Right-click or long-press on an edge opens a Disconnect
   popup. Same on ports + step nodes — long-press is now
   the trackpad-friendly alternative everywhere a context
   menu lives.
 - Background pattern toggle (dots / grid / blank) on the
   bottom-right canvas controls.
 - Live zoom indicator as a tappable % readout — tap to
   snap back to 100 % without losing pan.

Version 0.53.5 -> 0.53.6.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 17:36:43 +02:00
flemming-it
f2e2433bbc chore(studio): editor 0.5.3 — draft-to-cursor + port hover
Some checks failed
Security / Security check (push) Failing after 1s
Picks up fai_studio_flow_editor 0.5.3 (commit 5c59f3a):

 - Draft connection line now extends all the way to the
   cursor (previously stopped 6 px short).
 - Ports respond to hover with a soft accent glow + size
   bump — clear "this is interactive" affordance before
   the operator clicks.

Version 0.53.4 -> 0.53.5.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 17:13:32 +02:00
flemming-it
36e0113fff chore(studio): editor 0.5.2 — elegant port docking
Some checks failed
Security / Security check (push) Failing after 1s
Picks up fai_studio_flow_editor 0.5.2 (commit 9afb51a):

 - Bezier endpoints now terminate at the port dot's outer
   perimeter, not its centre. Lines "dock" cleanly into
   the socket instead of vanishing under it.
 - End-cap arrow removed (the dot is the terminator).
 - Connected ports gain a tiny inner pin so they read as
   "socket with a plug seated" rather than "hollow circle".

Version 0.53.3 -> 0.53.4.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 17:03:19 +02:00
flemming-it
a8d73ebd18 chore(studio): editor 0.5.1 — 5 type colours + wired/total badge
Some checks failed
Security / Security check (push) Failing after 1s
Picks up fai_studio_flow_editor 0.5.1 (commit e4e2d45):

 - Five payload types each get a distinct hue
   (text/bytes/json/file/number).
 - Step header shows wired/total ratio when partial,
   green check when fully wired.

Version 0.53.2 -> 0.53.3.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 16:47:45 +02:00
flemming-it
251513bc54 chore(studio): editor 0.5.0 — unified port model
Some checks failed
Security / Security check (push) Failing after 1s
Picks up fai_studio_flow_editor 0.5.0 (commit 099cd18):

 - Each port is a single dot (no more inline + canvas
   duplication). Sized to match label rows.
 - Inputs endpoint dots sit on the RIGHT edge (data flows
   out), labels right-aligned. Outputs endpoint stays on
   the left. Step input dots stay on the left.
 - Filled when the port participates in an edge, outlined
   when dangling. Type-coloured for inputs-endpoint dots.
 - Right-click a wired input port to Disconnect — clears
   the with-field / output expression cleanly.

Version 0.53.1 -> 0.53.2.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 16:45:18 +02:00
flemming-it
acdb54ae52 chore(studio): editor 0.4.0 — port geometry + endpoint editor + unsaved badge
Some checks failed
Security / Security check (push) Failing after 1s
Picks up fai_studio_flow_editor 0.4.0 (commit fb88926):

 - Connection dots now line up with their inline port-row
   labels (header geometry rewrite so subtitle space is
   always reserved).
 - Inputs endpoint colours its output ports by declared
   type (text / bytes / json / file / number).
 - Selecting the inputs or outputs endpoint opens a
   dedicated editor in the properties panel — add /
   rename / retype / remove without touching YAML.
 - "Unsaved" badge replaces the easy-to-miss 8-px dot;
   filename also colours primary when dirty.

Version 0.53.0 -> 0.53.1.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 16:31:51 +02:00
flemming-it
f252487c92 feat(studio): editor 0.3 — endpoints stay put + node context menu
Some checks failed
Security / Security check (push) Failing after 1s
Picks up fai_studio_flow_editor 0.3.0 (commit 296e5bf):

 - inputs/outputs endpoints now live in the layout sidecar
   like every other node — they stop drifting when the
   operator drags a step. The "outputs panel jumps when I
   touch anything" pain Stefan flagged is gone by
   construction.
 - Right-click on any step in the graph tab opens a popup
   menu: Duplicate (with unique id + offset), Disconnect
   all inputs (clears with-field values but keeps keys),
   Delete.
 - Floating Reset Layout button on the canvas next to
   Fit-to-screen — wipes the sidecar and re-runs
   AutoLayout from scratch + auto-fits, for when manual
   drags have drifted into spaghetti.

Version 0.52.4 -> 0.53.0 (minor bump — first new
operator-visible editor feature surface since 0.52).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 16:16:37 +02:00
flemming-it
696541e5b3 fix(studio): narrow default window to 1280x900
Some checks failed
Security / Security check (push) Failing after 1s
The 1440x900 default felt too wide for first-run: the
Welcome page's 960 px content column left visible slack on
either side even after the centring fix, and the editor's
sidebar plus canvas plus properties panel still didn't
need that much horizontal room on a fresh open.

1280x900 matches the most common laptop effective
resolution, keeps every Studio surface comfortable, and
the operator can resize larger any time. Same value
across all three host platforms (macOS NSWindow setContentSize,
Linux gtk_window_set_default_size, Windows Win32Window::Size).

Version 0.52.3 -> 0.52.4.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 16:06:50 +02:00
flemming-it
f1b0f65c86 fix(studio): centre Welcome page content on wide windows
Some checks failed
Security / Security check (push) Failing after 2s
Welcome page used ConstrainedBox(maxWidth: 960) inside a
left-aligned Column, so on the 1440x900 default window
the content hugged the left edge and left ~370 px of
empty space on the right. With Stefan's M4 sidebar
collapsed to 72 px the asymmetry was the dominant visual
in the first thing every operator sees.

Wrap the ConstrainedBox in a Center widget. 960 px stays
an opinionated reading width — long-form prose gets
uncomfortable past ~1100 px — but the slack now splits
evenly so the content sits with ~190 px breathing room
on both sides instead of all on one.

Bumps the editor-package lock pointer to ad2e5b5 (same
0.2.2 version, picks up the extract-with-approval test
commit on top of f8138f3) as a side-effect of the
flutter pub upgrade run during verification.

Version 0.52.2 -> 0.52.3.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 10:52:38 +02:00
flemming-it
36ddac2017 chore(studio): bump editor lock to 0.2.2 (save snackbar + drop highlight)
Some checks failed
Security / Security check (push) Failing after 2s
Picks up:
 - Brief green-check snackbar after every successful save
   (Cmd+S keystroke now has visible confirmation).
 - During a connection drag, every input port lights up
   and the closest one within snap distance gets a halo
   glow so the operator sees which port the drop will
   snap to.

Version 0.52.1 -> 0.52.2.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 02:00:45 +02:00
flemming-it
8a220d9c6e chore(studio): bump editor lock to 0.2.1 (live status + fit-to-screen)
Some checks failed
Security / Security check (push) Failing after 1s
Pulls in:
 - Live step status on the graph during runs (the graph
   highlights running / done / failed / awaiting nodes
   alongside the run-tab list).
 - Empty-graph call-to-action overlay with an Add step
   button so the first opened-but-empty flow points the
   operator at the right action.
 - Fit-to-screen button + auto-fit on first open of each
   flow so wide flows aren't cut off in the default
   viewport.

Version 0.52.0 -> 0.52.1.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 01:54:08 +02:00
flemming-it
634fd52635 feat(studio): Flows page IS the editor — three-tab graph+text+run
Some checks failed
Security / Security check (push) Failing after 1s
Studio's Flows destination is now the swappable
fai_studio_flow_editor's full surface: a drag-and-drop
graph view, the raw YAML text editor, and the run tab
with inputs form + live step progress + outputs.

What's gone:
 - The old saved-flows list + per-row run dialog.
 - The separate "flow-editor" navigation destination (one
   icon, not two — operators reach the editor by clicking
   Flows, period).
 - _FlowEditorAdapter (no longer needed; FlowsPage hands
   the runDriver to the editor directly).

What's new:
 - lib/data/flow_run_driver.dart — StudioFlowRunDriver:
   adapter implementing the editor's FlowRunDriver. Maps
   runFlow() to HubService.runSavedFlow and events() to
   HubService.streamEvents filtered to step.* event types.
   Converts proto-shaped FlowOutput variants to the editor's
   host-agnostic FlowOutputValue family.
 - lib/pages/flows.dart — replaced with a 50-line wrapper
   that loads capabilities into the editor's picker dropdown
   and forwards the active locale.

Editor pinned to fai_studio_flow_editor 0.2.0 (commit
870cbc2). All editor state — file list, dirty tracking, tab
selection, graph layout sidecar, properties panel — lives in
the editor package now; Studio just supplies the hub bridge.

Version 0.51.10 -> 0.52.0 (minor bump — Flows-page contract
changed, separate flow-editor destination removed).

flutter analyze: 0 issues. flutter test: 12/12 green.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 00:52:23 +02:00
flemming-it
fe2ee0a323 chore(studio): bump editor lock to 0.1.4 (top-align + refresh move)
Some checks failed
Security / Security check (push) Failing after 2s
Stefan reported the editor still centered short files vertically
and the reload icon was still between New and Save — but his
pubspec.lock pinned fai_studio_flow_editor to resolved-ref
7655e0d (0.1.2), which predates both fixes:

 - a7485ca (0.1.3) stretch attempt
 - daf6732 (0.1.4) actual fix via CodeField expands:true +
   refresh moved into the FileList header

Force lock to daf6732 so `flutter pub get` during `fai studio`
build picks up the current state of the editor's main branch
without needing a manual `flutter pub upgrade` step.

Version 0.51.9 -> 0.51.10.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 00:12:03 +02:00
flemming-it
3097a30428 fix(studio): larger default window — 1440x900 across all platforms
Some checks failed
Security / Security check (push) Failing after 1s
Stefan observed the default Studio window opened smaller
than comfortable when launched fresh (no persisted size).
On macOS specifically the xib-stored default was around
800x600 — too tight to read the sidebar + content + run
panel side-by-side.

Set 1440x900 as the initial content size across all three
host platforms:

 - macOS: MainFlutterWindow.swift sets contentSize via
   NSWindow.setContentSize after the FlutterViewController
   attaches, and centers on the active NSScreen. Clamped to
   the screen's visibleFrame so we never open larger than
   the display (matters for non-Retina external monitors).
 - Linux: gtk_window_set_default_size 1280x720 -> 1440x900
 - Windows: Win32Window::Size 1280x720 -> 1440x900

1440x900 is the effective Retina resolution of a 13" MacBook
(Stefan's dev machine) and the smallest "modern desktop"
footprint that lets the sidebar + content + tool panel
breathe.

Version 0.51.8 -> 0.51.9.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-01 00:06:04 +02:00
flemming-it
b40da56b8d fix(studio): localize sidebar tooltips that stayed English
Some checks failed
Security / Security check (push) Failing after 1s
The connection-row tooltip, the _ConnectionLabel caption,
the footer settings tooltip, and the channel-pill explainer
were hardcoded English even when the running app was set to
German. Move all four through AppLocalizations.

Adds ARB strings (de + en):
 - connectionTapToStart      antippen zum Starten  / tap to start
 - sidebarSettingsTooltip    Einstellungen (Cmd-;) / Settings (Cmd-;)
 - sidebarChannelTooltip     multi-line channel explainer

Existing connectionConnected / connectionUnreachable /
connectionConnecting are reused as the caption pieces; only
those two new strings (plus the channel tooltip) needed
adding.

Version 0.51.7 -> 0.51.8.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-31 23:28:49 +02:00
flemming-it
dbcff6f7a6 fix(studio): sidebar — fixed-height header rows, no more Y jump
Some checks failed
Security / Security check (push) Failing after 1s
The earlier "single AnimationController for width + opacity"
commit (0.51.5) made the *visual* expand smooth but the
underlying layout still jumped because the rows themselves
changed height between states:

  collapsed                 expanded
  ─────────                 ─────────
  triangle (36 px)          triangle + 2-line title (44 px)
  dot (10 px)               dot + multi-line pill with
                             optional "Start hub" button
                             (60-80 px when disconnected)
  letter (16 px)            letter + accented chip (28 px)

So everything BELOW the header — destinations list, footer —
slid down ~50 px every time the rail opened. That's the
"Versatz" Stefan kept seeing. Fading the labels in didn't
help; the row geometry was the wrong source of truth.

Structural fix: every header row is now a SizedBox with a
fixed pixel height. Collapsed-state and expanded-state
content both fit inside the same height:

  _brandRowH   = 48   // FaiDeltaMark (36 px) centered
  _connRowH    = 44   // single conceptual block, two single-
                      // line texts, no multi-line pill
  _channelRowH = 28   // chip + single-line channel name
  _rowGap      = 8

The total header block is therefore a constant pixel sum.
Items below it (the destinations ListView, the footer) sit
at the same Y in both states by construction — not by lucky
math, not by animation tricks. The width animation only
moves the rail's RIGHT edge; the left + top + bottom edges
of every row are immovable.

To make the connection row fit in 44 px we drop the inline
"Start hub" tonal button. The same affordance is preserved
by making the whole row tap-handled when the daemon is
unreachable: tap the red dot (or anywhere on the row) to
fire `fai daemon start`. The tooltip is updated to spell
this out ("Disconnected · tap to start · …").

The channel row is now ALWAYS reserved (28 px placeholder
when no channel is active) so that flipping the operator
config from local→dev at runtime doesn't shift the
destinations list either. The placeholder is invisible.

Side-effects:
 - _ConnectionPill is removed (nothing references it).
 - New _BrandLabel + _ConnectionLabel widgets, both
   trivially Column(MainAxisAlignment.center, …) so their
   content sits visually centered inside the fixed row.

Version 0.51.6 → 0.51.7.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-31 23:12:58 +02:00
flemming-it
ba1a1a4f06 feat(studio): live step-progress in flow-run dialog
Some checks are pending
Security / Security check (push) Waiting to run
The flow-run dialog showed a single spinner + "running…" label
for the whole run. For multi-step flows the operator had no
way to see *which* step was busy or how close the run was to
finishing.

Replace the spinner with a live step list driven by a
StreamEvents subscription:

  ✔ extract       0.41s
   summarize
  ◻ notify
  ━━━━━━━━━━────  35%

Mirrors the `fai run` CLI rendering — one shared visual
language across both surfaces. Steps appear in execution
order as the hub emits step.started events; check + duration
on completion; cross + first-line error on failure; pause
icon on approval gates.

Implementation:

 - HubService.streamEvents(backfill, types) — new public
   stream-facade method that wraps HubClient.streamEvents and
   maps proto LoggedEvent → AuditEvent for the rest of Studio.
   Subscribed with backfill=0 so the dialog only sees events
   from this very run.

 - _FlowRunDialogState.initState subscribes BEFORE submitting
   the run, so the first step.started never gets lost in the
   gRPC handshake gap.

 - Two-layer filter on incoming events: same flow name AND
   timestamp >= dialog open time. The timestamp gate is what
   stops a previous run's tail-end from painting stale rows
   if the user re-runs the same saved flow.

 - _LiveStep + _LiveStepList — insertion-ordered map renders
   rows in runtime execution order (not alphabetical), so
   what the operator sees matches what the hub did.

Version 0.51.5 → 0.51.6.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-31 22:57:21 +02:00
flemming-it
2b65cd771f fix(studio): sidebar — single AnimationController drives width
Some checks failed
Security / Security check (push) Failing after 2s
+ label opacity in lockstep (no more glitchy expand)

The previous "fixed-width icon column" commit already locked
the icon's horizontal pixel-X, but the rail still _felt_
glitchy on expand because the rail's geometry and the rail's
content were animated by two different mechanisms:

 - Width: AnimatedContainer over FaiMotion.fast (120 ms)
 - Content: setState(_hovered = true) → conditional
   `if (expanded) Expanded(label)` snaps in one frame

So on mouse-enter the label widget appeared INSTANTLY while
the rail was still 72 px wide. The label tried to render in
0 px of available space and Flutter's layout engine clamped
it; over the next 7 frames the rail grew to 220 px and the
label visibly "settled in". That's the perceived glitch.

Replace the two-source animation with a single
SingleTickerProviderStateMixin + AnimationController whose
value `t` (0..1, eased via easeInOutCubic) drives both:

 - rail width = lerp(72, 220, t)
 - label opacity = t
 - labelsInteractive = t > 0.5 (so hidden buttons can't
   eat clicks meant for the icon column)

Labels are wrapped in `t > 0 ? IgnorePointer(Opacity(...))
: SizedBox.shrink()`. Once the animation starts, the label
joins the tree at opacity ≈ 0 (invisible — no pop) and
fades up smoothly as t grows. When fully collapsed (t == 0)
the label is removed from layout entirely, so the connection
pill's tall "Start hub" affordance doesn't inflate the
collapsed rail's height (this also fixes the widget_test
vertical-overflow that 0.51.4 introduced).

Same pattern applied to the footer (settings icon stays in
the 72-px column always; theme/lang/clock fade in beside it)
and to each _SidebarItem destination row.

End result: width AND content travel together along the same
animation curve. No more "snap then catch up" — they're
mathematically inseparable.

Version 0.51.4 → 0.51.5.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-31 22:24:53 +02:00
flemming-it
5830198566 fix(studio): sidebar — fixed-width icon column eliminates icon shift
Some checks failed
Security / Security check (push) Failing after 1s
Previous "left-anchor everything via padding" approach made
the math work on paper but the perceived shift persisted. The
root cause was structural, not padding-based: ListView gave
each item a tight cross-axis constraint equal to the rail's
current width (48 collapsed, 196 expanded). Inside the item,
the AnimatedContainer filled that constraint and laid out a
mainAxisSize.min Row at the start of the AC's content area.
The icon's pixel-X depended on the AC's padding _and_ the
constraint; even with equal padding the rendered layout
shifted across the expand animation because Flutter
re-resolved alignment under a moving constraint.

Replace the entire pattern with a fixed-width icon column
that doesn't care about the parent constraint:

  ListView (padding 0) → Item Row
    SizedBox(width: 72) → Center → icon   ← anchored to literal x
    if expanded → Expanded → label text   ← grows into remaining space

Same pattern for the brand/connection/channel rows above. The
72 px matches the rail's collapsed width exactly, so collapsed
rail = one icon column + nothing; expanded rail = same icon
column + label slot. Icon's pixel-X is now provably constant.

Removes the AC's horizontal padding entirely — the icon
column owns the left anchor. The background highlight still
spans the full row width (typical nav-rail UX), and the rail
expand animation only moves the rail's right edge, never any
icon-bearing element.

Also passes an `iconColumnWidth: 72` to _SidebarItem instead
of hard-coding the magic number in two places.

Version 0.51.3 → 0.51.4.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 16:15:03 +02:00
flemming-it
1295675bd9 fix(studio): sidebar left-anchor + pull flow-editor 0.1.2
Some checks failed
Security / Security check (push) Failing after 2s
Two Stefan-reported fixes after the v0.51.x live test:

1. Sidebar icons appeared to shift between collapsed and
   expanded. Mathematically the icons stayed at x=24 (
   ListView padding + AC padding), but the Column's default
   crossAxisAlignment.center re-centered the brand mark
   AND the top indicators (version, connection, channel) on
   every expand → in a 72 px rail the brand sits at x≈36,
   in a 220 px rail it sits at x≈110. Stefan perceived this
   horizontal repositioning of the brand+pills as a shift
   of the icons below.

   Fix: Column.crossAxisAlignment = start + left-pad every
   top item (brand mark, "F∆I Studio" label, version,
   connection pill, channel pill, mini-version, connection
   dot, channel chip) by exactly the same offset the
   destinations use (ListView.padding + AC.padding =
   FaiSpace.md + FaiSpace.md). The whole rail now reads as
   one stable left edge through the expand animation.

2. Pull fai_studio_flow_editor 0.1.2 — fixes the
   "opening hello.yaml shows extract-with-approval content"
   bug by switching `_code.text = ...` to the canonical
   `_code.fullText = ...` setter in the package.

Version: 0.51.2 → 0.51.3
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 16:08:22 +02:00
flemming-it
8ae9441e38 chore(deps): pull flow-editor 0.1.1 — false-positive dirty fix
Some checks failed
Security / Security check (push) Failing after 2s
Updates fai_studio_flow_editor pin from 51f9a1d to 8b918f8.

Upstream fix: the editor's dirty-detection compared
_code.text against a snapshot taken before CodeController
normalisation, so opening any flow whose loaded text the
controller silently rewrote (trailing-newline handling, YAML
fold markers, line-ending normalisation) immediately read as
dirty. Clicking a different file in the list then raised the
"Discard unsaved changes?" prompt even though the operator
hadn't typed anything.

The package now compares _code.fullText (the canonical
post-processed value) against a baseline captured AFTER the
text assignment. Comparing fullText-to-fullText eliminates
the spurious diff.

Version: 0.51.1 → 0.51.2
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 14:57:29 +02:00
flemming-it
4dbe7a28f6 fix(studio): sidebar — fix icon-shift + use collapsed top space
Some checks failed
Security / Security check (push) Failing after 2s
Two UX bugs Stefan flagged on the v0.51.0 collapsible rail:

1. Icons re-positioned when the rail expanded. Operators
   already pointing at an icon had to chase it as the rail
   grew. Two causes: the _SidebarItem switched between
   `Center(icon)` (collapsed) and `Row(icon, gap, label)`
   (expanded), and the AnimatedContainer's horizontal padding
   switched between FaiSpace.sm (collapsed) and FaiSpace.md
   (expanded). Both flipped the icon's effective X-position.

   Now: same Row, same AnimatedContainer padding, in both
   states. The icon sits at the same X-pixel always; only
   the label slot toggles open beside it.

2. Top of the collapsed rail was wasted (only the brand-mark
   was visible; version + connection + channel disappeared
   completely). Use the same vertical real estate for
   glance-able compact equivalents:

   * tiny "v0.51" version text in mono below the brand
   * _CollapsedConnectionDot — 10 px circle, green/red/amber
     tonality matching FaiDeltaMark's mode states. Tooltip
     surfaces the full endpoint string.
   * _CollapsedChannelChip — 18 px circle with the channel's
     first letter (P / B / D / L) in the same accent tone
     the full pill uses, so "production" stays unmistakably
     red even at-a-glance.

When the rail expands, the full pills replace the compact
indicators as before. Tests still green (the existing widget
test booted without hover-expand and that path stays correct
either way).

Version: 0.51.0 → 0.51.1
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 14:53:18 +02:00
flemming-it
0183771689 feat(studio): swap built-in flow editor for fai_studio_flow_editor package (v0.51.0)
Some checks failed
Security / Security check (push) Failing after 2s
The flow editor was internal to Studio (lib/pages/flow_editor.dart).
Per Stefan's review feedback ("austauschbar wäre schöner"),
extract it into its own Forgejo repo so the host can swap
the implementation without touching Studio.

New repo: https://git.flemming.ai/fai/studio-flow-editor

Studio's pubspec.yaml now references the package by git URL:

  dependencies:
    fai_studio_flow_editor:
      git:
        url: https://git.flemming.ai/fai/studio-flow-editor
        ref: main

To swap the editor:
  1. Fork (or write a new) fai/studio-flow-editor.
  2. Keep the FlowEditorPage(initialFlowName, locale, onRun)
     constructor signature — the stable host contract.
  3. Point the pubspec at your fork.
  4. Rebuild Studio.

Adapter pattern: _FlowEditorAdapter in main.dart resolves the
package's runtime dependencies (locale via Localizations,
onRun via HubService) from the BuildContext, then constructs
the package's FlowEditorPage. Same pattern in flows.dart for
the pencil → editor route push, so a future operator-side
locale switch propagates correctly.

The package brings its own copies of FaiSpace tokens, minimal
FaiEmptyState/FaiErrorBox widgets, and an inline EN+DE l10n
table — accepting a small amount of visual drift in exchange
for true package independence. flutter_code_editor +
highlight move from Studio's pubspec to the package's.

Deleted:
  lib/pages/flow_editor.dart  → package's lib/src/flow_editor_page.dart
  test/flow_editor_test.dart  → package's test/ (next commit there)

Bumped:
  pubspec.yaml version 0.50.0 → 0.51.0
  main.dart kStudioVersion 0.50.0 → 0.51.0
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 14:36:54 +02:00
flemming-it
bb793e64a9 feat(studio): inline flow editor — YAML highlighting + Run (v0.50.0)
Some checks failed
Security / Security check (push) Failing after 1s
New top-level destination "Editor" (Cmd+5) ships as Studio's
fifth surface. The editor reads + writes flow YAML directly
under ~/.fai/data/flows/ via dart:io — the hub picks up the
changes on the next listFlows / runSavedFlow call.

Layout: two-pane shell. Left (240 px) is the file list; right
flexes to the code pane and an optional results column when
Run produces output. Top toolbar exposes:
  * filename + dirty-mark
  * New flow (scaffolds from a debug.echo template)
  * Save (writes the active file to disk)
  * Run (saves first if dirty, calls
    HubService.runSavedFlow, surfaces typed FlowOutputs in
    a side panel)
  * Refresh

YAML highlighting via flutter_code_editor + the highlight
package's yaml language. Lightweight style map mapping the
five token classes that actually appear in flow YAML
(attr / string / number / comment / subst for the
${{ ... }} template syntax) to FaiTheme colors — keeps the
editor visually consistent with the rest of Studio.

New-flow naming uses a FilteringTextInputFormatter that
restricts the name to [a-z0-9_-]. A "name already exists"
SnackBar surfaces the conflict instead of silently
overwriting.

Bilingual strings shipped (en.arb + de.arb) for every
operator-facing string: toolbar buttons, dialogs, empty
states, file-exists error, run-output header.

New deps:
  * flutter_code_editor ^0.3.5
  * highlight (transitive — pinned as direct so the
    yaml-language import has its declared dependency).

Smoke-test (test/flow_editor_test.dart) pumps the page and
asserts the empty-state + toolbar render without throwing
on hosts that don't have ~/.fai/data/flows yet. The full
file-list + open-on-tap flow needs a writable HOME override
which dart:io's read-only Platform.environment doesn't allow
inside a test isolate — that path lives in the integration
suite as a follow-up.

Version bumps:
  * pubspec.yaml: 0.49.1 → 0.50.0
  * main.dart kStudioVersion: 0.42.0 → 0.50.0 (had drifted
    behind pubspec; brought back into sync as part of this
    bump)

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 01:10:05 +02:00
flemming-it
1c306916aa fix(security): atomic hub-auth-token writes (P1-5)
Some checks failed
Security / Security check (push) Failing after 1s
`hub_auth_token.dart` write() previously did

  await f.writeAsString(token);          // file is now 0644 (umask)
  await Process.run('chmod', ['600', f.path]);

which leaves a TOCTOU window where the file is world-readable
between the writeAsString and the chmod call. On a multi-user
host another user could read the bearer token during that
window.

New flow:

  await tmp.writeAsString(token);        // .tmp file
  await chmod(600, tmp.path);            // restrict mode FIRST
  await tmp.rename(f.path);              // atomic POSIX rename

The destination is never visible with permissive perms. Also
sets `~/.fai/` itself to 0700 on Unix on first creation so
other users on a shared host can't enumerate the directory.

dart analyze clean; flutter test green (12 tests).
Bumped pubspec to 0.49.1.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-29 17:47:29 +02:00
flemming-it
23529bae82 chore(deps): absorb fai_client_sdk 0.17.0 (grpc 5, protobuf 6)
Some checks failed
Security / Security check (push) Failing after 2s
Lifts every direct dependency Studio carries to its latest
available version:

  fai_client_sdk: 0.16.0 → 0.17.0  (grpc 5, protobuf 6, new
                                    bindings)
  + the SDK major dragged dbus 0.7.13, xml 7.0.1, analyzer
    13.0.0, _fe_analyzer_shared 100.0.0 via the constraint
    solver in the platform→sdk→studio chain.

What Studio's call sites needed in code: nothing. `HubClient`
already wraps every gRPC + protobuf type, so the major bumps
land transparently. The integration tests against the live
hub fixture pass without changes (11/11 green).

Remaining outdated transitives are all Flutter SDK 3.44.0
pins:
  - meta 1.18.0 → 1.18.2
  - vector_math 2.2.0 → 2.3.0
  - win32 5.15.0 → 6.3.0

These lift with the next Flutter stable bump. No action
possible from Studio's pubspec today.

Bumped pubspec to 0.49.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-29 14:36:30 +02:00
flemming-it
1d68858899 chore(deps): bump file_picker 8→11 + transitive lifts
Some checks failed
Security / Security check (push) Failing after 1s
`flutter pub upgrade` lifts every transitive dep that the
constraint solver can reach: google_cloud 0.4.1→0.5.0,
googleapis_auth 2.3.0→2.3.1, hooks 1.0.3→2.0.0, objective_c
9.3.0→9.4.1 (plus xml 6.6.1).

Direct: file_picker 8.0.0 → 11.0.0. The major jump is the
move from instance API (`FilePicker.platform.pickFiles(...)`)
to static API (`FilePicker.pickFiles(...)`). Two call sites
updated (`lib/pages/flows.dart` for the run-flow form's
file input, `lib/widgets/fai_flow_output.dart` for the
flow-output "Save as" affordance) — both functionally
identical, just the surface that lost the `.platform.`
hop.

Locked behind external pins (no action possible from
Studio side this pass):
- grpc 4.2.0 → 5.1.0  ─┐
- protobuf 4.2.0 → 6.0.0 ┤── pinned by fai_client_sdk; a
                          │   major SDK bump is its own
                          │   piece of work (regenerated
                          │   bindings, possibly Studio
                          │   call-site shifts).
- characters / meta / vector_math / win32 / native_toolchain_c
   ─ all pinned by the Flutter SDK; lift with the next
     Flutter SDK upgrade.

dart analyze clean; flutter test green (12 tests pass).
Bumped pubspec to 0.48.1.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-29 07:15:25 +02:00
flemming-it
1903babc5a feat(settings): multi-version uninstall picker + default_scope editor
Some checks failed
Security / Security check (push) Failing after 2s
Two operator surfaces shipped together:

**Multi-version uninstall picker** — when more than one version
of a `(provider, name)` is installed side-by-side, both the
module-sheet "Uninstall" affordance and the store-detail
"Uninstall" affordance now ask the operator which version to
remove before calling the RPC. The hub's wire-level support for
this (UninstallModuleRequest.version) was already there; Studio
just wasn't using it. Picker pre-selects the highest version so
single-version flows still take one click.

  - `HubService.installedVersions(name)` enumerates the installed
    versions via the capabilities list.
  - `HubService.uninstallModule(name, version: ...)` forwards
    the version into the RPC.
  - `_UninstallVersionPickerDialog` (module sheet) and
    `_StoreUninstallVersionPickerDialog` (store) host the
    picker — separate widgets so each surface can evolve copy
    independently. Uses `RadioGroup<String>` for Flutter
    3.32+ deprecation compliance.

**Default scope editor** — new DEFAULT SCOPE panel in Settings
that calls the freshly-added HubAdmin RPCs
`GetDefaultScope` / `SetDefaultScope`. Operators can:

  - reorder publisher segments with up/down buttons
    (first match wins in the bare-form resolver),
  - delete entries (hub still rejects empty list — Studio
    surfaces the constraint inline),
  - add arbitrary entries via the text field,
  - add catalog-known publishers via suggestion chips
    (sorted alphabetically, populated from the catalog).

Every change persists to `~/.fai/config.yaml` via the hub
and hot-swaps the in-memory copy without a daemon restart.

Bumped pubspec to 0.48.0. dart analyze clean (No issues
found!); flutter test green (11 tests).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-29 00:21:51 +02:00
flemming-it
327bc0fea2 feat(store): advisory-version badge for federated entries
Some checks failed
Security / Security check (push) Failing after 1s
Store cards + detail-sheet now prefix the version pill with
`~` and attach a tooltip for federated entries (MCP / n8n /
Temporal). Upstream services don't honour semver, so the
version pill is a label, not a contract — the tilde says
"this number may change without a version bump."

Three render sites updated to stay visually consistent:
- compact card pill in grid view
- expanded card pill row
- detail-sheet header pill cluster

EN + DE l10n entries added (`storeAdvisoryVersionTooltip`)
and `app_localizations.dart` regenerated.

dart analyze clean; flutter test green (11 tests).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-28 23:26:49 +02:00
flemming-it
c5a96bc8d4 feat(settings): hub bearer-token panel + auto-attach on every RPC
Some checks failed
Security / Security check (push) Failing after 1s
Studio gains a new "HUB AUTHENTICATION" panel in Settings
mirroring the existing registry-credentials panel:

- `lib/data/hub_auth_token.dart` — `~/.fai/hub-auth-token`
  helper (read / write / clear, mode 0600 on Unix). Sister
  of `RegistryToken` with the same on-disk hygiene.
- `HubService.loadPersistedEndpoint` now reads the token at
  startup and reconnects with it. `reconnect()` grew an
  `authToken:` parameter with a sentinel that distinguishes
  "keep current" from "drop". `reloadAuthToken()` is the
  one-liner Settings calls after save / clear.
- `_HubAuthTokenPanel` in `fai_settings_dialog.dart` — paste
  with show/hide toggle, save button, clear button, status
  pill ("Configured (40 chars)" / "Not set (anonymous)"),
  storage-location hint. Trimmed token length only — the
  secret never round-trips back into the UI after save.
- EN + DE ARB entries (`hubAuthToken*`) + regenerated
  `app_localizations.dart` keep the bilingual surface
  consistent.

The hub side (auth.tokens config + tower middleware) shipped
on the platform side 2026-05-28 (43a54a2). Until now an
operator had no GUI path to consume it: they had to find the
file in their home dir, paste the token by hand, and bounce
Studio. This panel closes that loop.

Bumped pubspec to 0.47.0. dart analyze clean (No issues
found!); flutter test green (11 tests pass).

Block E item 1 of 4 done. Capability-picker badges,
default_scope editor, multi-version uninstall picker follow.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-28 23:24:15 +02:00
flemming-it
a5bd51de33 chore(studio): switch to fai_client_sdk (renamed from fai_dart_sdk)
Some checks failed
Security / Security check (push) Failing after 1s
The Dart SDK package was renamed package: fai_dart_sdk →
fai_client_sdk and the dir + Forgejo repo got a -dart language
suffix per the three SDK families convention in
fai/platform/docs/architecture/sdks.md. Updates the path
dep, dep name, every `package:fai_dart_sdk/...` import, and
the one comment that named the SDK.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-26 13:46:32 +02:00
flemming-it
69b54629d3 chore(studio): flutter_markdown_plus migration + integration-test scaffold
Two follow-ups to the May-2026 trust pass.

flutter_markdown_plus migration:

- pubspec swaps `flutter_markdown ^0.7.7` (discontinued
  upstream) for `flutter_markdown_plus ^1.0.3`, the
  actively-maintained fork. API surface
  (MarkdownStyleSheet, Markdown, MarkdownBody) is
  unchanged — the four import sites in welcome, store,
  flow_output, and theme.dart get an updated package
  string and that's it.
- All Studio analyzer + unit-test suites stay green.

Integration test scaffold:

- New `test/integration/hub_fixture.dart` boots a real
  `fai serve` subprocess on a free port against a temp
  FAI_DATA_DIR, polls until Healthy, exposes a ready
  HubClient. Idempotent teardown wipes the temp dir.
- Resolves the `fai` binary from PATH first, then from
  `../fai_platform/target/release/fai`. When neither
  exists, the fixture calls `markTestSkipped` with a
  clear message — fresh checkouts don't fail.
- One canonical test in `capabilities_test.dart` asserts
  on the bug class the May trust pass surfaced: that
  `system.approval` appears in `list_capabilities` with
  `kind=builtin` so Studio's missing-deps check never
  tries to install it. Plus a contract-shape test that
  every cap's `kind` is one of the three known wire
  values.
- README documents the cold-start gotcha (first
  `fai serve` per machine takes ~30s to build the
  curated-model DB) plus the manual warmup recipe.

Not in CI yet — wiring needs the platform build job to
publish `fai` as a CI artifact for downstream consumption.
Deferred until enough integration tests exist to justify
the CI minutes.

Signed-off-by: flemming-it <sf@flemming.it>
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-25 13:22:04 +02:00
flemming-it
7416c34d7c feat(studio): contextual "Add registry token" CTA on install auth wall (v0.46.0)
Some checks are pending
Security / Security check (push) Waiting to run
The hub's v0.10.91 install error message already tells the
operator exactly what to do — set FAI_REGISTRY_TOKEN or save
to ~/.fai/registry-token via Settings. But that text lives in
a FaiErrorBox inside the install dialog. Reading the buried
recovery instruction, finding Settings, scrolling to the
Registry credentials panel, pasting, then re-clicking the
install pill is five hops for what should be one.

When at least one item in the install dialog fails with the
"registry returned an HTML page" / "no registry token
configured" signature, the dialog now surfaces a prominent
filled "Add registry token" button next to Close. Click closes
the install dialog and opens the Settings dialog directly so
the operator pastes + saves without losing context. After
Settings dismisses, the operator is back at the flow card and
re-clicks the same pill to retry.

Detection is signature-based (substring match on the hub's
error text) so it activates whether the install was a single-
pill click or an "Install all" sequence. Other failure modes
(network down, signature invalid, sha mismatch) still show
just the Close button — the CTA is specific to the auth-wall
case it can actually unblock.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-09 19:32:13 +02:00
flemming-it
3db7db7330 feat(studio): hub maintenance panel with reset action (v0.45.0)
Some checks are pending
Security / Security check (push) Waiting to run
UI parity with the platform CLI: `fai reset` (v0.10.93) is now
reachable from Settings. The panel shows the reset blurb plus
two checkboxes mirroring the CLI flags (Keep modules / Keep
audit log + saved flows), then a destructive-styled "Reset hub
state" button. Click goes through a confirm dialog before
SystemActions.faiReset is invoked.

After the CLI returns, Studio bounces its gRPC channel against
the same endpoint (the daemon restarts on the same channel +
port, so no Settings change needed) and reloads every panel:
channel status, system AI, MCP clients, n8n endpoints, registry
token. Output (success or failure) is shown in a copyable error
box so any unexpected stderr lands somewhere the operator can
paste from.

Sits at the bottom of the dialog under "HUB MAINTENANCE",
separated from the everyday config so a destructive button
doesn't crowd the day-to-day controls.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-09 18:52:19 +02:00
flemming-it
878f0a4e28 feat(studio): registry credentials panel in Settings (v0.44.0)
The hub install path now reads `~/.fai/registry-token` as a
fallback when `FAI_REGISTRY_TOKEN` is unset (platform v0.10.92).
Studio now writes that file directly: a fresh operator pastes
the PAT into Settings → Registry credentials, hits Save, and
the next install attempt resolves the auth wall without any
shell or env-var setup.

The token never round-trips back into Studio after save —
status is shown only as "Configured (40 chars)" / "Not set"
with no display of the secret itself. The Clear action deletes
the file. On Unix the file is chmod-ed to 0600 (owner-only
read/write); Windows leaves the default user ACL in place.

Storage is strictly local: `~/.fai/registry-token`, never sent
to a remote service. The hint text under the field says so to
make the data flow explicit.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-09 13:38:47 +02:00
flemming-it
c389876a2f feat(studio): clickable install pills + open-in-editor on flow cards (v0.43.0)
When a saved flow is missing modules, each capability pill in the
"Needs:" row is now clickable: tap one and a per-item progress
dialog walks the bare capability name through installModule, then
the flows page refreshes. When more than one capability is missing,
an extra "Install all (N)" button runs the same dialog over the
whole list sequentially so a fresh operator can take an unrunnable
flow and one click later have its dependencies resolved.

Also adds a pencil icon next to Run that hands the YAML path to
the OS via SystemActions.openInOs — the operator's default editor
for .yaml decides what opens. Makes the path next to the flow name
actionable instead of decorative.

The install dialog renders one row per spec with a status icon
(pending circle / spinner / check / error), shows the installed
version on success, and surfaces the full error in a copyable
FaiErrorBox on failure so the operator can paste it back.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-09 11:58:24 +02:00
flemming-it
2002486828 feat(studio): operator QoL — flow runnability, audit grouping, batch approvals, welcome celebration (v0.42.0)
Four UX threads stitched into one commit. Each pulls Studio
toward Stefan's "zero-learning-curve" goal — the feedback
that earned its own memory entry.

1. Flow-Runnability-Indikator
   ─────────────────────────
   The Flows tab now fetches `listFlows` and `listModules`
   in parallel. Each card compares the flow's
   `requiredCapabilities` against the installed-modules'
   capability set; rows with missing modules show a "Needs:
   text.extract@^0" red pill row beneath the path and have
   their Run button greyed out + tooltip
   "Install the missing modules first." Operators stop
   hitting Run → cryptic hub error → frustration.

2. Welcome-Checklist Celebration
   ─────────────────────────────
   Once all four checklist signals flip to done, an
   `_AllDoneCelebration` card replaces the bare
   "All four steps complete" + Hide button. Three concrete
   next-threads with action buttons: "Read the audit log",
   "Set up the daily Today story" (opens the Flows / Today
   doc inline via `_DocReaderSheet`), and "Build your own
   module" (opens the architecture doc). Operator who just
   got set up sees what to do next instead of an empty
   "what now?" feeling.

3. Audit-Page Time-Bucket Headers + Flow-Run Detail
   ────────────────────────────────────────────────
   The flat event list grows tiny "TODAY / YESTERDAY /
   EARLIER THIS WEEK / OLDER" section headers — bucket is
   computed in the operator's local timezone so an event at
   23:55 yesterday in Berlin doesn't end up in "today"
   because UTC happened to spill into a new day.
   Plus: the event-detail dialog gains a "View flow run"
   action when the picked event has a `flow_execution`. It
   opens a drill-down that lists every event in the
   already-fetched 100-event window sharing the same
   execution id, sorted ascending — the operator reads the
   run from step.started top to flow.completed bottom.

4. Approvals-Batch-Aktionen
   ────────────────────────
   Each pending approval card grows a checkbox. When ≥1
   selected, a floating action bar appears at the bottom
   with "N selected · Select all · Clear · Reject all ·
   Approve all". The parent loops sequentially through the
   per-record SDK calls so a partial failure produces
   "X done, Y failed" instead of a confusing all-or-nothing
   rollback. Reject prompts for a reason once and applies
   to the whole picked set.

13 new ARB keys cover the strings the four features
needed. Studio's tests stay green.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-09 11:24:57 +02:00
flemming-it
f90d8cc7a7 feat(studio): typed run-flow form, file picker, copyable errors (v0.41.0)
The free-form key=value run-flow dialog produced two
unhelpful failures every time an operator hit Run:

  1. Submit empty → "step references missing value
     '\$inputs.document'" from the hub. Studio sent a zero-
     entry inputs map because nothing told the operator the
     flow declared a required input.
  2. Type `document=@/path/to/file` → "path not found".
     Dart's `File()` doesn't expand `~`, and on macOS
     sandboxed Studio can't read arbitrary paths anyway.

Both failure modes are gone. The dialog is now a typed form:

- On open, fetches `getFlowDefinition(name)` (new SDK
  v0.15.0 wrapper around the v0.10.89 hub RPC). While that
  resolves, a small spinner shows
  "Loading inputs…"; on failure, an inline `FaiErrorBox`
  with the exact RPC error and a copy button replaces the
  spinner.
- Renders one form-field per declared input. The flow
  YAML's verbatim type tag drives the widget choice:
  `bytes` / `file` → "Choose file…" button + picked-file
  readout, plain TextField for everything else. The type
  tag is shown next to the input name as a pill so a flow
  author who picks a less-common type still gets a hint.
- Bytes inputs route through `file_picker` with
  `withData: true`, which means the OS file dialog handles
  read access — sandboxed Studio gets the bytes inline
  rather than a path it can't open. Falls back to
  `File(path).readAsBytes()` on Linux configs that don't
  honour `withData` for large files; failures show a
  copyable SnackBar.
- The Run button enables only when every declared input has
  a value (text non-empty / file picked). Empty submission
  is impossible; the cryptic hub-side
  "missing value" error stops surfacing.
- The run-result dialog's error path uses `FaiErrorBox`
  instead of the previous plain `SelectableText`, so any
  hub-side flow error (permission denied, module crash,
  whatever) is one click to clipboard.

11 new ARB keys cover the form's labels, the file-picker
states, the loading / failed-definition messages, the
"This flow declares no inputs" empty state, and the
run-error dialog title.

`file_picker: ^8.0.0` added to pubspec.yaml.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-09 10:18:08 +02:00