Generic gRPC-code mapping was right but not specific enough.
A flow failing with an approval-timeout used to land on
'Deadline exceeded — try again later'; now it reads 'Freigabe-
Timeout abgelaufen — entweder timeout_seconds erhöhen oder
den Reviewer informieren.'
New pattern matchers in _matchHubPattern, runs before the
gRPC-code switch. Six FlowExecutionError shapes covered:
- approval rejected ("rejected by")
- approval timeout
- output too large ("exceeding the X MB cap")
- host service not declared
- missing value reference
- MCP endpoint unreachable
- capability not installed (NotFound fallback)
Every match comes with a localised hint pointing at the
concrete fix path (audit log / timeout config / Integrations
panel / Text-tab Fix button).
Five new tests pin the matchers — would catch a silent
regression when the hub renames a variant Display string.
All 11 friendly_error tests + 19 Studio tests green.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Three operator-UX gaps closed:
- Settings dialog's six-category sidebar (General / Appearance /
System AI / Integrations / Security / Maintenance) was
hardcoded English; now flows through AppLocalizations
("Allgemein" / "Darstellung" / "System-KI" / "Integrationen" /
"Sicherheit" / "Wartung"). Same for the per-panel title +
description.
- Per-channel daemon-action labels ('enable autostart' /
'disable autostart' / 'daemon restart' etc.) and the
OK / Failed result line in the toast also moved to l10n,
so the system-action feedback reads as one language.
- New 'Approvals' doc bundle (en + de) under assets/docs/,
registered as a fifth doc card on Welcome plus exposed via
the new public helper. The
Approvals, Audit and Doctor app-bars grow a Help icon button
next to Refresh that opens the matching doc in the existing
bottom-sheet reader — no extra screen, no learning curve.
Studio bumped to 0.67.0; editor path-override pulls in 0.20.1
(flow-list row polish).
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The previous Add-source dialog was English-only and
operator-hostile — answered 'where do I install from?' without
explaining what a private module *is*. New version, DE + EN:
- Title + intro + button labels routed through AppLocalizations
("Modul-Quelle hinzufügen" / "Installation fehlgeschlagen"
instead of raw English strings).
- 'How private modules work' explainer block (3 sentences):
what a module is (`module.yaml` + WASM), how to package
it (`fai pack <dir>` → .fai bundle), where to host it
(any URL: own Forgejo / GitHub / S3), what verification the
hub does (sha256 + signature against trust store).
- Honest about the Studio gap: the dialog can install URLs +
packed bundles, but unpacked source directories still
require `fai install --link <path>` on the CLI. The
example command lives in its own code-style box, selectable.
- Layout: SingleChildScrollView'd so the explainer doesn't
push the buttons off short viewports.
Studio bumped to 0.66.0; editor path-override pulls in 0.20.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Pulls in editor 0.19.0 which closes three operator-reported
gaps: persistent diagnostic strip across all three tabs,
analyzer-error block on Run, and copyable run failures.
Studio bumped to 0.65.1.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Two host-side pieces that complete the editor 0.18 quick-fix
loop:
- FlowsPage now fetches the store catalog (`searchStore` with
a 500-item ceiling) on open and hands it as `storeCapabilities`
to the FlowEditorPage. The editor's analyzer uses it to
decide whether an unknown-cap fix shows "Install …" or the
Add-source alternative.
- New `_AddModuleSourceDialog` — wired through the editor's
`onAddModuleSource` callback. Asks the operator for a URL
or local .fai bundle path; passes the value into
`HubService.installModule(source: …)`. Honest about its
limits: includes a card pointing the operator at
`fai install --link <path>` for unpacked module source
directories (which the gRPC install API doesn't currently
support — that's CLI-only).
Studio bumped to 0.65.0; editor path-override pulls in 0.18.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The Settings dialog was a single 800-line vertical scroll — Hub
endpoint, channels, System AI, MCP, n8n, registry credentials,
hub auth token, default scope, theme plugin, maintenance all
stacked. Stefan: 'inzwischen zu unübersichtlich als langes
feld, untergruppierungen wären toll, wie z.B. in den MacOS
Einstellungen'.
Splits the panel into six focused categories driven by a
left sidebar:
- **General**: Hub endpoint (host/port/TLS), channels,
default scope
- **Appearance**: theme plugin picker (applies instantly)
- **System AI**: operator-managed LLM endpoint for Studio
helpers
- **Integrations**: MCP servers + n8n endpoints
- **Security**: registry credentials + hub auth token
- **Maintenance**: reset / destructive ops
Sidebar uses macOS-style icon + label rows with a highlighted
selection pill. Content area is per-category, scrollable
within its own panel. The dialog widens from 520→820 px and
gains a fixed height (620 px) so the layout doesn't jump as
the operator switches categories.
Each panel opens with a title + one-sentence description so
the operator knows what they're looking at before scanning the
controls — same pattern Apple uses in System Settings.
Also adds the missing 'meta' dependency that FaiLog's
@visibleForTesting needed. Studio bumped to 0.63.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
FaiLog used to compute its destination directly from HOME, which
made it impossible to test without scribbling on the operator's
real ~/.fai/logs/. Adds a static testPathOverride seam tagged
@visibleForTesting so the singleton can be redirected at a per-
test temp file.
The new test/fai_log_test.dart covers:
- append() writes one JSON-shaped line per event
- context field round-trips when supplied
- tail() returns oldest-first
- tail(maxLines:) caps and keeps the newest entries
- rotation moves the previous log to .log.1 past 256 KiB
- path getter honours the override
- writes to an impossible path do not throw — best-effort
contract that protects the UI from log-write failures
Production behaviour is unchanged: when the override is null
the path getter still computes from HOME/USERPROFILE.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
`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>
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>
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>
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>
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>
Cross-store research (Apple, Play, Steam, Docker, VS Code,
Chrome Web Store, Flathub) consistently rewards editorial
curation over algorithmic recommendations — but manual
copywriting per release does not survive a solo-dev cadence.
This commit lands a daily-build pipeline so the Today-Hero
card stays fresh without operator hand-edits per release.
Pipeline shape (full design in docs/today-pipeline.md):
1. tools/today/collect.sh aggregates "what happened in the
last 24 hours" across the F∆I monorepos: git log per repo,
store-index seed.yaml diffs, architecture/system-gaps doc
changes, Studio release tags, and (opt-in) audit-log
highlights. Outputs plain text.
2. tools/today/propose.sh feeds the signal summary plus
prompt.template.md to the operator's already-configured
System-AI (Ollama default; OpenAI-compatible endpoints
work via env-var override). Drafts N candidate stories as
YAML files under ~/.fai/today/proposals/<date>/.
3. tools/today/accept.sh validates a chosen candidate against
the today/v1 schema and the no-marketing-speak banned-word
list, then atomic-renames it into ~/.fai/today/active.yaml.
4. Studio reads active.yaml at store-page init via the new
TodayStoryLoader (lib/data/today_story_loader.dart). On any
failure (file missing, schema mismatch, banned-words hit,
parse error) it falls back to the compiled-in
_kFallbackTodayStory so KRITIS deployments and fresh
installs always render something sensible.
Trust + audit:
- All proposed and accepted stories live as plain YAML on disk.
- The pipeline calls only the operator's already-configured
System-AI; it never reaches a CMS, never phones home, works
air-gapped if the System-AI does.
- The bash accept gate AND the Dart loader both enforce the
banned-word list — a hand-edited active.yaml that bypassed
the shell still won't reach the UI.
- Removing the cron entry disables the pipeline; Studio falls
back to the const story and continues to work.
Cron / launchd / systemd recipes documented in
tools/today/README.md.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Replaces the Store-only DE/EN toggle with an app-wide one
parked in the sidebar footer next to the theme button.
Pressing it flips every translated string at once: nav
labels, page titles, common buttons, the bilingual
store-index content.
Implementation:
- Adds `flutter_localizations` + `intl` to pubspec, plus
`flutter.generate: true` so `flutter gen-l10n` runs in the
build pipeline.
- ARB sources at `lib/l10n/app_en.arb` and `app_de.arb`. The
EN file is the template; DE carries the German strings.
Initial coverage: navigation, common buttons, page titles,
channels / store / audit / modules / approvals headers,
hub-unreachable copy, MCP + n8n panel headers + hints.
Rest of the UI strings are still English-literal — those
fall in incrementally as we touch each surface.
- Generated `AppLocalizations` lives at
`lib/l10n/app_localizations*.dart` (regenerated via
`flutter gen-l10n` on every ARB edit).
- `StudioAppState` gains `localeNotifier` alongside
`modeNotifier`; persisted via SharedPreferences key
`locale.code`.
- Sidebar `_LanguageToggle` reads/writes through the
notifier. The Store's per-page locale state is gone:
`_locale` now reads `Localizations.localeOf(context)
.languageCode`, so the bilingual store-index content
follows the global setting without a second toggle.
- `_NavPage.label` becomes `_NavPage.id` + `labelOf(context)`;
Cmd+K palette and Sidebar both read the localized label.
Out of scope this iteration: localizing the remaining
~80% of UI strings (Settings dialog labels, Store search
hint, error messages). Those land incrementally — the i18n
infrastructure now means each is a one-line ARB edit + one
call-site swap.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Mirror of the MCP-clients panel: Add / Remove / Refresh
with last-discovery health (workflow count or error_kind),
sister Add-dialog with name + base_url + optional
api_key_env + notes. HubService gains the four passthroughs
(`listN8nEndpoints`, `refreshN8nEndpoints`,
`addN8nEndpoint`, `removeN8nEndpoint`) and a
`N8nEndpointInfo` data class.
Settings dialog now hosts: hub endpoint, channels,
system AI, MCP clients, n8n endpoints — all behind the
existing 520×640 SingleChildScrollView so the layout
doesn't crowd.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The viral-bridge story now has a UI surface. Settings →
MCP Clients lists every configured server, shows last-
discovery health (green dot + tool count, red dot + error
kind), and offers Add / Remove / Refresh.
Add-server dialog: name (no dots), endpoint, optional
api_key_env, optional notes. On submit the hub persists +
re-runs discovery in one round-trip; the Store fills with
synthetic `mcp.<name>.<tool>` entries the operator can see
immediately.
StoreItem now carries `kind` and `provider`. Federated
entries route through a bridge — no install bundle to
download — and Studio's install button renders accordingly
(via the existing "Not installable" path; the Phase-2
follow-up adds an explicit "Configure bridge" call-to-action).
Settings dialog grew to 520×640 with a SingleChildScrollView
so the additional panels don't crowd. Channel pill, system-AI
panel, MCP-clients panel all coexist without a redesign.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Three sweeps in a single bundle.
Universal command palette (⌘K / ctrl+K):
- Modal at top-of-window with a single search input. Indexes
pages, installed modules, store entries, saved flows on
open; filters client-side as the operator types.
- Keyboard nav (↑/↓ + Enter), grouped sections, hover-to-
highlight. Hit selection navigates / opens the right
surface in one keystroke. Closes with Esc.
- Designed to mirror the VSCode / Linear / 1Password
ergonomics — gives non-CLI operators a "jump anywhere"
affordance that scales with the number of installed
modules.
Modules page recent-activity panel:
- Top-of-page strip lists the last 10 install / uninstall
events from the audit log, color-coded by direction.
Hidden when no relevant events exist (fresh installs).
Same locale-unambiguous timestamp format used in audit.
Store detail sheet now renders icons + screenshots:
- `_ModuleIcon` widget loads the explicit `iconUrl` if
provided, falls back to the category glyph on missing
URL or load failure (no broken-image rectangle).
- Screenshots strip below the description: 320×200 tiles,
horizontally-scrollable, click opens full-size via the
OS handler. Placeholder card on load failure.
StoreItem extended with `iconUrl`, `screenshotUrls`,
`docsUrl` so the new content paths through unchanged.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Sidebar:
- New active-channel pill below the connection pill,
color-coded so production deployments look visibly
different from local-dev. Click opens Settings; tooltip
explains the channel taxonomy. Polled in lockstep with the
health probe so it tracks CLI-driven `fai channel switch`.
Store:
- Featured strip above the main grid renders editorial
picks as bigger hero tiles with a subtle gradient
backdrop, version + status pills, and a one-click
Install. Hidden the moment the operator types in the
search box or picks any filter — the result list is the
answer they want, not editorial chrome.
StoreItem now carries the `featured` flag so the page can
filter without an extra round-trip.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Two Phase-2 Store improvements that operators feel
immediately:
- Per-module update badge: store cards compare the operator's
installed version against the index's `best_version`, raise
an "update" pill + "Update to vX.Y.Z" button when newer is
available. Result: a single glance at the Store reveals
what's behind. The page now fetches `listModules` in
parallel with the search so the comparison is on the
current snapshot.
- Inline README rendering: detail sheet has a new
Documentation section. "Load documentation" button (lazy —
no network until clicked) calls FetchModuleDocs and renders
the markdown via flutter_markdown, with the operator's
theme colors and link-tap routed through SystemActions.
Auth / not-found / network failures fall back to a
friendly fix-hint panel + "Open repository in browser"
button so the operator never hits a dead end.
Adds flutter_markdown ^0.7.7 to pubspec; the package is
maintenance-only upstream but is the only mature option
for Material-themed markdown right now. Easy to swap to
markdown_widget if/when we need GitHub-flavored extras.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
UI parity for Windows operators who never touch a shell:
- Module sheet gains an Uninstall button at the bottom.
Two-step confirmation, then calls UninstallModule + closes
the sheet + refreshes the Modules page.
- Doctor page picks up two new sections:
* Daemon files — log / config / audit DB / modules /
flows / pid, each with a one-click "Open" button that
shells out to the OS handler (open / xdg-open /
explorer).
* Daemon control — Restart / Stop / Status buttons that
spawn `fai daemon …`. Captured stdout/stderr renders
inline so the operator sees what happened.
- Update banner gains an "Apply update" button that spawns
`fai update apply --channel <c>`. The previous version
showed the command as text — Windows users had no way to
execute it.
- Event log panel gains a "Verify now" button that re-runs
the chain check (via the existing doctor refresh).
New SystemActions helper resolves the `fai` binary via
$FAI_BIN → PATH → `~/.fai/bin/fai` (or the Windows
equivalent), so the buttons work whether the operator
restarted their shell after installing or not.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Audit drilldown's explanation panel now surfaces whether the
answer came from the cache:
- "cached" pill next to the SYSTEM AI label, tooltip shows
the original generation date + hit count.
- Latency renders as "orig 47000 ms" so the operator sees
what a live call would have cost.
- Refresh icon next to the latency triggers Regenerate —
drops the entry, re-asks the model.
System-AI editor shows a cache-status row when the cache is
non-empty with a one-click Clear button. Hidden on fresh
installs to keep the dialog quiet.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Operators can now clear dev-noise from the audit log without
dropping to the CLI. Top-bar broom icon next to the filter
chips opens a two-field dialog (reviewer pre-filled from $USER,
reason required). On submit the hub call returns the purged
count + active channel; both are shown back in a snackbar.
Compliance-locked channels (beta/production) are refused
server-side; the dialog surfaces the rejection reason via a
friendly error message instead of the raw gRPC status string.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
System-AI editor now fetches the detected hardware tier and the
hub's curated model database on open. Each model chip's colour
+ tooltip combines both: green star when curated quality is
good/excellent AND min_hw_tier ≤ host; orange when quality is
fine but the host is below min_hw_tier; warning for "basic"
curated entries. Tooltip exposes the curator's note, parameter
count, context window, and licence. Banner above the form
shows "Detected: <summary> · curation reviewed <date>".
Sidebar footer gains a small monospaced live clock between the
theme toggle and the settings icon. Tooltip exposes full local
ISO + UTC for cross-checking against audit-log timestamps.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>