Commit graph

80 commits

Author SHA1 Message Date
flemming-it
f0a944dce7 feat(studio): award-tier visual polish
Brightness-aware elevation tokens (FaiElevation low/medium) applied
to cards, sheets and the sidebar active item; gentle hover-lift on
Store + Doc cards; a longer, eased sidebar expand using the
under-used FaiMotion.slow; and a branded empty state carrying the
F∆I delta mark with warmer spacing. Subtle, Linear/Raycast-style —
both light and dark themes tuned.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-11 23:52:31 +02:00
flemming-it
5313266cc4 feat(studio): first-run UX, recovery affordances, l10n, bundled fonts
- Connection-aware Welcome: when the hub is down, show a hero with a
  primary "Start hub" CTA + install fallback instead of a dead,
  all-unchecked onboarding checklist (the first-run cliff).
- Actionable binary-not-found (file picker + install link, not a
  "set FAI_BIN" dead end) and a connect-failure banner after
  repeated failed health polls.
- Localize six hardcoded English error/toast clusters (DE+EN ARB).
- Bundle Inter + JetBrains Mono as assets; drop the runtime
  google_fonts fetch (air-gap / KRITIS safe, no font-swap flash).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-11 23:52:18 +02:00
flemming-it
28fafce7dc feat(studio): localise Settings sidebar + add inline help docs + Approvals doc
Some checks failed
Security / Security check (push) Failing after 1s
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>
2026-06-09 01:57:46 +02:00
flemming-it
2be7d241bf feat(studio): localised + explainer-rich Add module source dialog
Some checks failed
Security / Security check (push) Failing after 1s
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>
2026-06-09 01:36:54 +02:00
flemming-it
63985267d5 feat(studio): pass store catalog to editor + Add-module-source dialog
Some checks failed
Security / Security check (push) Failing after 1s
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>
2026-06-09 00:22:09 +02:00
flemming-it
0cc527b07c feat(studio): wire flow editor quick-fix install handler
Some checks failed
Security / Security check (push) Failing after 1s
FlowsPage now passes onInstallCapability to the editor — the
editor's 'Install <cap>' button on an unknown-capability
diagnostic calls HubService.installModule, refreshes the
capability list, and returns it so the editor reanalyzes and
clears the issue.

On install failure: friendly SnackBar surfaces the error. The
operator never gets stuck on the analyzer reporting a
capability they just clicked to install.

Studio bumped to 0.64.0; editor path-override pulls in 0.17.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-09 00:02:28 +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
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
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
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
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
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
e522267ec1 feat(studio): pencil-into-editor + back button + collapsible rail
Some checks failed
Security / Security check (push) Failing after 2s
Three operator-visible improvements per Stefan's review of
the v0.50.0 editor.

1. Pencil icon on Flows page now opens Studio's own flow
   editor (route push) preloaded with the selected flow,
   replacing the previous "open in OS default editor"
   (SystemActions.openInOs) behaviour. The editor's AppBar
   gains a back arrow when reached via route push; reaching
   the editor via the nav rail leaves it bare. Tooltip
   string updated in EN + DE.

2. New FlowEditorPage `initialFlowName` parameter. When set,
   the page loads that flow on first frame (via post-frame
   callback so the BuildContext is mounted before
   _openByName runs). When null (the nav-rail path), the
   editor opens to its empty state as before.

3. Sidebar (_Sidebar) is now collapsed-by-default: shows
   just icons in a 72px-wide rail with per-destination
   tooltips. Hovering the rail expands it to 220px (the
   old width) with brand-mark + labels + the full footer
   row (theme toggle, language toggle, clock, settings).
   Collapsed footer shows the settings icon only. Brand-
   mark (FaiDeltaMark) stays visible in both states so the
   live/idle status dot is always glanceable.

Side-effect: SystemActions import in flows.dart is no
longer needed (the pencil no longer shells out) — removed.

widget_test.dart: dropped the per-destination Text-presence
asserts since labels are now Tooltips when collapsed.
Hover-expand testing triggers RenderFlex-overflow mid-
animation in widget tests (the AnimatedContainer's width
transitions through a constraint slimmer than the Row's
intrinsic min). The booting-without-throwing assertion
remains; per-destination presence stays in the per-page
test suites.

Both arb files updated with the new strings (navFlowEditor
already existed; added flowEditorBackTooltip + retouched
flowsOpenInEditorTooltip).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 12:04:05 +02:00
flemming-it
647d93a1dc fix(studio): editor — friendly-error mapping for run failures
Some checks failed
Security / Security check (push) Failing after 1s
Previously a failed runSavedFlow dumped the raw exception
into FaiErrorBox in the right panel. Hub gRPC failures
(network, missing module, schema reject, …) are now mapped
through friendlyError so the operator sees a one-sentence
headline + optional recovery hint, with the verbatim error
still accessible behind FaiErrorBox's expand-on-tap.

Matches the same pattern already used by every other Studio
page that surfaces a hub error.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 01:19:13 +02:00
flemming-it
c3f9f862ab feat(studio): Cmd+S / Cmd+Enter shortcuts in flow editor
Some checks are pending
Security / Security check (push) Waiting to run
Wraps the FlowEditorPage in Shortcuts/Actions so
Cmd+S (or Ctrl+S) saves and Cmd+Enter (or Ctrl+Enter) runs
the active flow, no matter whether the code field or
toolbar has focus. Matches the rest of Studio's keyboard-
first ethos.

Disabled when no flow is open or an operation is already
in progress.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 01:16:37 +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
570c38b238 fix(welcome): structured doc-load error + Forgejo fallback link
Some checks failed
Security / Security check (push) Failing after 2s
The doc-reader bottom-sheet in welcome.dart hits rootBundle
to load assets/docs/<slug>[_de].md. When the asset isn't in
the bundle (e.g. the binary on disk predates the
flutter_markdown_plus migration in 69b54629 or an in-progress
asset reorganisation), the FutureBuilder's error branch dumps
the raw PlatformException text into FaiErrorBox with no
hint at what the operator should do.

Switch the throw to a structured `_DocReaderError` that
carries:
  - the slug, locale, and both attempted asset paths
  - the underlying error message
  - a precomputed `forgejoUrl` pointing at the same doc on
    the platform repo's main branch

The error UI gains a TextButton.icon below FaiErrorBox that
opens the Forgejo copy in the OS browser via the existing
SystemActions.openInOs path — operators always have a
working out, even when the local bundle is broken.

The error toString() also surfaces "rebuild Studio" as the
top suggestion, since the most common cause for asset
mismatches is a stale binary running against a newer source
tree.

New regression test (test/load_docs_test.dart) pumps the
exact code path the sheet uses (rootBundle.loadString +
Markdown with FaiTheme.markdownStyle) against all eight asset
paths (architecture/security/audit/flows × en/de). Catches a
future bundle drift before an operator notices.

No behavioural change on the happy path. Both the rendered
markdown widget and the closed-sheet animation are unchanged.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 00:54:46 +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
3237c4415a feat(doctor): per-source-kind capability breakdown line
Some checks failed
Security / Security check (push) Failing after 2s
Studio's Doctor page _ModulesPanel now shows the source
breakdown directly under the module/capability count:

  Modules: 6 loaded, 10 capabilities
  6 bundle · 3 mcp · 1 system

Reads `CapabilityEntry.sourceKind` (Phase D wire field).
Pre-0.12 hubs return empty sourceKind; the breakdown panel
falls back to grouping by the legacy `kind` string so older
deployments still see a useful count.

Two data-layer additions:
  - `DoctorSnapshot.capabilitiesBySource: Map<String, int>`
  - `_sourceBreakdown(Map<String, int>) -> String` renders
    the line in deterministic alphabetical order.

dart analyze + flutter test green (11 integration tests).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-28 14:41:15 +02:00
flemming-it
ceed8aab02 feat(studio): temporal source bucket + provenance pill on store grid
Some checks failed
Security / Security check (push) Failing after 1s
Adds the missing UI surface for SPARK / Temporal federated
capabilities. Previously only mcp and n8n showed provenance
pills; temporal-sourced entries fell through to the default
"no badge" case.

  - storeSourceTemporal + storeProvenanceTemporal l10n keys
    (en + de). Pill format: "temporal · <provider>", icon
    Icons.timer_outlined, accent tone.
  - Source filter chip row gains a "Temporal" option in the
    Store filter dialog.
  - `_StoreProvenancePill` adds a `case 'temporal'` branch.

Operators federating a SPARK cluster (`services: -
kind: temporal`) now see their workflows visually labelled
as Temporal-sourced in the Store grid + filter UI.

dart analyze green. No test changes required — the
provenance pill is render-only.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-28 14:15:39 +02:00
flemming-it
2c13ef58ae feat(store): source-bucket from CapabilityEntry.source_kind
Some checks failed
Security / Security check (push) Failing after 1s
Studio's `_sourceForItem` now prefers the wire-level
`sourceKind` field (populated by 0.12+ hubs) over the legacy
name-prefix heuristic. Adds a "temporal" bucket alongside
"mcp" / "n8n" / "native" / "federated" so SPARK-federated
workflows render with their own transport pill on the store
grid + filter chip.

Pre-0.12 hubs return empty `sourceKind` and the legacy
sniffing path still works -- back-compat preserved.

`StoreItem` data class accepts an optional `sourceKind`
parameter (defaults to empty). dart analyze green.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-28 12:06:59 +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
c11461b0f9 feat(studio): trust pass — friendly errors + store clarity + MCP i18n
Second half of the May-2026 trust pass. Drops the wall of
gRPC trailers from every error surface and makes the Store
honest about what is and isn't installable.

Friendly errors:

- New `friendlyError(Object, AppLocalizations)` mapper turns
  GrpcError + arbitrary throwables into a localised headline,
  optional recovery hint, and a verbatim detail string kept
  behind a "Show details" expander. Duck-typed on `.code` /
  `.message` so Studio doesn't have to depend on package:grpc
  directly.
- `FaiErrorBox` gains an `error:` constructor that runs the
  mapper. Every call site that used to render
  `snap.error.toString()` (flows, welcome, store) switches to
  it.
- 9 .arb entries per locale cover the gRPC codes we actually
  emit (INVALID_ARGUMENT, NOT_FOUND, ALREADY_EXISTS,
  PERMISSION_DENIED, FAILED_PRECONDITION, INTERNAL,
  UNAVAILABLE, UNAUTHENTICATED) plus copy/details affordances.
- `test/friendly_error_test.dart` — 6 unit tests for the
  mapper. Covers the mapping table, locale-switching, and the
  non-gRPC fallback so future regressions show up in CI.

Capability discovery:

- New `HubService.allCapabilities()` reads the kind-aware
  capability list (wasm + builtin + federated) and returns a
  Dart-side `CapabilityInfo` value type. The flow page's
  missing-dependency check uses it so `system.approval` and
  federated MCP/n8n tools count as "available" — fixes the
  Run button staying disabled forever.
- `HubService.listModules()` filters to kind=wasm so the
  Modules page doesn't sprout synthetic "system" entries that
  the operator can't uninstall.

Store clarity:

- New "Installable only" filter, on by default. Roughly 2/3
  of seed entries currently carry `status: planned`; the
  default view stops being noise.
- Featured-strip cards for planned modules now show a
  "Coming soon" pill instead of an empty action area.
- Main-grid cards for non-installable modules dim to 60%
  opacity so the eye lands on actionable cards first.
- Detail-sheet "Nicht installierbar" tooltip → inline hint
  box. The reason is visible without hovering.

MCP localisation:

- `_kMcpSuggestions` no longer holds 11 hardcoded English
  description strings. The `description` field is replaced
  with a `resolveDescription(AppLocalizations)` lookup that
  switches on the suggestion `name` to read the matching
  `mcpSuggestion*Desc` .arb key. EN + DE shipped.
- New `FaiEnBadge` widget renders a small `[EN]` pill when
  the active locale isn't English. Used next to MCP /
  federated store entries' tagline + description because
  the server supplies them in English and we can't translate
  on the fly yet — the badge is the honest signal until the
  planned `studio.translate` plugin lands.

Plus housekeeping: removed the unused `_keepImport` lint
escape in the test and the dangling library doc-comment in
`format.dart`.

Signed-off-by: flemming-it <sf@flemming.it>
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-25 12:36:14 +02:00
flemming-it
34f2b7b313 feat(studio): typed flow outputs + on-disk docs + UX polish
Documentation system (the original "whole docs system is broken"
complaint):
- Studio reads inline docs from disk via the new
  getInstalledModuleDocs RPC. No network, no auth, no provider
  lock-in.
- store.dart probes for MODULE.md eagerly when a module-detail
  sheet opens (a single file stat). The Documentation section
  only renders when the bundle actually shipped docs.
- Shared FaiTheme.markdownStyle helper used by Welcome's
  DocReaderSheet and Store's DocsPanel so every inline doc
  reads in the same typography. The shared style forces
  code.backgroundColor = transparent to suppress the per-span
  bands flutter_markdown's default code style draws on dark
  themes.

Flow run dialog:
- Run-button gating now strips the @version suffix from
  installed capabilities before the contains() check. Without
  this fix the button stayed disabled for every flow with
  dependencies, even after a successful install.
- Studio derives a MIME type from the picked file's extension
  (small per-suffix map; .pdf, .docx, .txt, .json, ...) and
  forwards it to the hub. Fixes "unsupported MIME type:
  application/octet-stream" from text.extract.
- Dialog title tracks the future's state: running -> "extract
  laeuft", success -> "extract -- Ergebnis", failure ->
  "extract -- fehlgeschlagen".
- Output rendering moved from String stringification to a
  sealed FlowOutput type (Text / Json / Bytes / File / Unknown).
  A new FaiFlowOutput widget dispatches per variant: markdown
  for text/markdown (heuristic), pretty JSON for proto Struct,
  inline image preview + Save-As for bytes, Open for file URIs.

Byte sizes:
- New humanBytes() helper renders 23.3 kB / 1.04 MB style values
  with three significant digits, matching Finder / GNOME Files.
  Wired into flow-card pills, picked-file readouts, and the
  bytes-payload preview line.

Signed-off-by: flemming-it <sf@flemming.it>
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-25 11:17:58 +02:00
flemming-it
081ffd7233 fix(store-docs): readable blockquote in dark theme
Some checks failed
Security / Security check (push) Failing after 1s
flutter_markdown's MarkdownStyleSheet.fromTheme defaults the
blockquote background to a hardcoded Colors.blue.shade100. In
Dark Theme the text colour stays white-on-surface, so a
blockquote rendered '> Note: ...' shows as white text on
light blue — unreadable.

Override blockquoteDecoration to use theme tokens
(surfaceContainer + left primary accent border) and pin
blockquote text to onSurface. tableCellsDecoration gets the
same theme-driven treatment for the same class of issue.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-24 23:18:51 +02:00
flemming-it
98a6414eaa fix(store): localised docs, debounced typing, explicit-submit always AI
Some checks failed
Security / Security check (push) Failing after 1s
Three Stefan-found bugs in one pass:

1. Documentation rendered in English even when Studio was set
   to German. _loadDocs now passes Localizations.localeOf(...)
   to fetchModuleDocs(); the hub tries README.de.md before
   README.md when locale='de'.

2. 'Keine Treffer' flickered on every keystroke. _onAskTyping
   used to call _runSearch synchronously per character — a
   short 2-3-letter substring matched nothing, so the empty-
   state showed for a frame, then the next character revealed
   results. Add a 250ms typing debounce (Timer + cancel on
   dispose) so the search only re-runs once typing pauses.

3. Submit button did substring search even when System-AI was
   enabled — the _looksLikeQuestion heuristic only routed
   4+-word / question-mark queries through the LLM. The user
   reasonably expected an explicit submit (with the AI-icon
   showing on the button) to actually use AI. New behaviour:
   when system-AI is enabled, the submit button always calls
   _runAiQuery; live-typing keeps substring filtering. The
   heuristic helper was removed (now unused).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-24 22:07:40 +02:00
flemming-it
ec7417fef5 fix(store): wrap docs-panel hint in Expanded to prevent overflow
Some checks failed
Security / Security check (push) Failing after 2s
The 'Documentation' row in the module-detail sheet showed
a RenderFlex horizontal overflow when the sheet is narrow
(~340dp on mobile/portrait). The Row had:
  [Button] [Spacer] [Text(hint)]
without any Expanded/Flexible wrapper, so the hint text could
not break and pushed past the right edge.

Fix: wrap the hint Text in Expanded. Text now wraps to a
second line at narrow widths instead of overflowing. Also
explicitly set crossAxisAlignment.center so the wrapped text
visually aligns with the button on a single baseline.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-24 21:40:06 +02:00
flemming-it
0cf3b0ed72 fix(welcome): defer doc-load to didChangeDependencies
Some checks failed
Security / Security check (push) Failing after 2s
_DocReaderSheetState.initState() called _load(), which
synchronously calls Localizations.localeOf(context) before any
await. That triggers dependOnInheritedWidgetOfExactType<
_LocalizationsScope>() in initState(), which Flutter forbids
because InheritedWidget wiring is not finished yet.

Symptom (clicking a doc card in Welcome):
  dependOnInheritedWidgetOfExactType<_LocalizationsScope>()
  or dependOnInheritedElement() was called before
  _DocReaderSheetState.initState() completed.

Fix: defer _load() to didChangeDependencies(), which runs
after initState() and after InheritedWidget hookup. Gate on
_content == null so it runs exactly once per widget lifetime
(didChangeDependencies can also fire on later dependency
changes; we only want the first load).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-23 17:47:23 +02:00
flemming-it
e9538b66b7 fix(welcome): module check now looks at capabilities, not module names
Some checks failed
Security / Security check (push) Failing after 1s
The welcome checklist's "Install a text module" item used:
  m.any((x) => x.name.startsWith('text.'))

But ModuleSummary.name is the module identifier (text-extract,
text-summarize, …), not the capability identifier (text.extract,
text.summarize, …). The check never matched even when several
text-* modules were installed — Stefan saw the item pending with
4 text capabilities live.

Fix: check capability names instead, which carry the dot-namespace:
  m.any((x) => x.capabilities.any((c) => c.startsWith('text.')))

Semantically aligned with the onboarding intent ("any text.*
capability available").

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-23 10:37:37 +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
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
flemming-it
63e19974c0 feat(studio): @-syntax file inputs in flows, copyable doc errors, services-row overflow (v0.40.0)
Three concrete fixes against today's user feedback.

- Flows that take binary inputs (extract / extract-summarize /
  …) work from the Flows tab. The run-flow input dialog now
  accepts the same `@/path/to/file` syntax `fai run --input`
  uses on the CLI: any value beginning with `@` is read as
  bytes and sent as a binary Payload; plain values still flow
  through as text. The dialog hint copy and the example
  placeholder reflect the new syntax. File-read failures
  surface as a SnackBar before the run dialog opens, so a
  typo in the path doesn't reach the hub. Threaded through
  `_FlowRunDialog` and `HubService.runSavedFlow`, which both
  carry separate `textInputs` and `fileInputs` maps now and
  forward to the SDK's mixed-mode runSavedFlow (v0.14.0).

- The Welcome doc-reader's error path uses `FaiErrorBox` so
  the actual underlying error is selectable + copy-to-
  clipboard via the existing widget. Plus the loader throws
  a richer error string that names *both* attempted asset
  paths (`<slug>_<lang>.md` and the EN fallback) and the
  underlying exception each, so the operator can paste a
  diagnostic into a chat without us having to ship a
  separate "how to read Flutter asset errors" doc.

- Doctor's empty Services panel had a horizontal RenderFlex
  overflow at narrow widths because the long mono-spaced
  hint ("add to ~/.fai/config.yaml under services:") and
  the leading icon+text both demanded full intrinsic width
  in a single Row. Now wraps via a `Wrap` widget so the
  hint flows to a second line on narrow viewports and stays
  in the same row when there's space.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-09 02:07:31 +02:00
flemming-it
eb447c6ec0 fix(studio): Welcome layout exception, drop awkward header, Store at slot 2 (v0.39.1)
Three fixes against the v0.39.0 user feedback.

- Layout exception "BoxConstraints forces an infinite
  height". `_PillarRow`'s wide-window path used
  `Row(crossAxisAlignment: CrossAxisAlignment.stretch)`
  inside a `Column` inside a `SingleChildScrollView` — Row
  inherits unbounded height, can't stretch to anything,
  Flutter throws. Wrapped the Row in `IntrinsicHeight` so
  the Row's height is bounded to the tallest child first.
  Same shape `_DocsRow`'s narrow-window path was triggering
  with `Wrap` + `SizedBox(width: double.infinity)` — replaced
  with a plain `Column` for that path; the wide path keeps
  the Wrap with the proper finite cardWidth.

- Dropped the "WIE ES ZUSAMMENPASST" / "HOW IT FITS
  TOGETHER" section header. Operator-feedback was that the
  label sounded like a question without an answer. The hero
  already establishes the page; the three Hub/Module/Flow
  cards are self-explaining beneath it. Header was visual
  clutter and the underlying ARB keys are gone.

- Reordered the sidebar: Welcome, **Store**, Doctor, Flows,
  Audit, Approvals. Store is the operator's daily-use
  surface and belongs above Doctor (which is a diagnostics
  page). Welcome stays slot 0 as the first-launch landing.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-09 00:42:39 +02:00
flemming-it
930e246649 feat(studio): Welcome page Phase C — live getting-started checklist (v0.39.0)
Final slice of the Welcome surface. Four steps that prove
the operator has the basic loop wired up; each step is
a live probe against the running hub, no manual ticks.

The four checked signals:

  1. System AI configured
     → `HubService.systemAiStatus().enabled`
  2. Public capability source added
     → `listMcpClients()` returns at least one entry
  3. Text module installed
     → `listModules()` contains a name starting with `text.`
  4. Saved flow run
     → any `flow.completed` event in the last 100 audit
       events

All four probes fire in parallel from `_OnboardingChecklist`'s
`_refresh()`. Failures stay false (catchError) so a hub that
is briefly unreachable doesn't blank the whole row — the
operator hits the explicit Refresh icon to retry.

UX:

- Renders between hero and pillars so the actionable path
  beats the educational content for screen real estate.
- Each row: status icon (○ pending / ✓ done) + title +
  one-line hint pointing the operator at the right Studio
  surface + status pill.
- Done rows strike-through the title and dim it; pending
  rows stay full-strength.
- When all four flip to done, an "All four steps complete"
  footer appears with a `Hide checklist` button. Clicking
  persists `welcome.checklist.dismissed = true` via
  SharedPreferences; the section is gone for good (we don't
  re-nag operators who chose a non-default path).

15 new ARB keys for the section header, body, four rows
plus done/pending pills, all-done footer, dismiss /
refresh / refreshing button labels.

Three-phase plan from `docs/landing-page-design.md` is now
fully shipped: Phase A scaffolding (v0.37.0), Phase B
embedded doc reader (v0.38.0), Phase C live checklist
(v0.39.0).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-09 00:28:41 +02:00
flemming-it
3b07d340d5 feat(studio): Welcome page Phase B — embedded doc reader (v0.38.0)
Second slice of the Welcome surface. Operator-facing
documentation now lives inside Studio as bundled assets and
renders inline via a modal sheet — no browser, no external
link, air-gap-tauglich.

- Four operator-readable explainers under `assets/docs/`,
  each with an EN + DE pair:
    architecture[_de].md  — Hub / Module / Flow + how they fit
    security[_de].md       — sandbox model, declared perms,
                              operator ceiling
    audit[_de].md          — hash-chained log, WORM-1 mechanics,
                              `fai admin verify-events`
    flows[_de].md          — flow YAML, templating reference,
                              extract→summarize example
  These are short (≈ 300-500 words each), operator-shaped
  prose. Not copies of the architecture docs in
  fai_platform/docs/architecture/ — those are
  contributor-dense.

- pubspec.yaml declares `assets/docs/` so the markdown ships
  inside the Studio binary. Air-gap deployments read them
  with no network access.

- New `_DocReaderSheet` modal: 85 %-of-viewport bottom sheet,
  drag handle + title bar with the doc icon and close button,
  scrollable Markdown body styled to match Studio chrome.
  Loads `assets/docs/<slug>_<locale>.md` first, falls back to
  the EN file. Locale comes from
  `Localizations.localeOf(context)`.

- `_DocsRow` on the Welcome page sits below the trust-posture
  deck. Two-column grid on ≥ 640 dp, single column below.
  Each card is icon + title + one-line blurb + chevron;
  click opens the reader sheet for that slug.

- 12 new ARB keys for the docs section (header, blurb, four
  card titles + blurbs, close button, error message).
- 4 new icons reused: `account_tree_outlined` (architecture),
  `shield_outlined` (security), `verified_outlined` (audit),
  `alt_route_outlined` (flows).

Implements Phase B of `docs/landing-page-design.md`. Phase C
(live getting-started checklist with persistent state) is the
last remaining slice.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-09 00:24:31 +02:00
flemming-it
3a7d0e74ad feat(studio): Welcome page — Phase A scaffolding (v0.37.0)
First slice of the new sidebar destination outlined in
docs/landing-page-design.md. Static content only — embedded
docs (Phase B) and the live getting-started checklist
(Phase C) follow.

Phase A:

- New `WelcomePage` at `lib/pages/welcome.dart`, registered
  as sidebar slot 0 above Doctor. Default selectedIndex stays
  0, so a fresh launch lands on Welcome.
- Hero card: gradient backdrop matching the Today-Hero in the
  store, F∆I Platform headline, subtitle taken from CLAUDE.md
  ("deterministic workflow engine for AI-assisted document
  processing in regulated environments").
- Three-pillar row "Hub / Module / Flow" — operator-readable
  prose, not architecture-doc dense. Stacks to a column under
  640 dp window width so card text never gets cropped.
- Trust-posture deck — "Sandbox by default", "Tamper-evident
  audit log", "Air-gap ready". Same content that used to
  rotate as carousel slides in the store; reading them as a
  single deck with full prose works better than rotating
  through fragments.
- AppBar follows the same `titleSpacing: FaiSpace.xl`
  alignment as the Store so the title sits flush with the
  body padding.
- 13 new ARB keys for the page content (EN + DE).

Smoke test now expects "Welcome" in the navigation rail; the
existing `Doctor / Store / Flows / Audit / Approvals`
expectations stay unchanged.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-09 00:17:54 +02:00
flemming-it
711436b71d feat(studio): carousel size lock, store-actionable today stories, HTTPS MCP suggestions in settings (v0.36.0)
Three concrete changes against operator feedback that the
Today carousel was visually jumping when arrowed and
genre-mixing platform-architecture education with store-
actionable highlights.

- Carousel size pinned. The hero's outer Container picks up
  `BoxConstraints(minHeight: 240)` so a slide with one
  paragraph and a slide with three render at the same
  height. Prev/next no longer reflows the rest of the page.

- Today fallback stories trimmed and re-themed. The four
  shipped slides drop to three:
  - "Public sources" (DeepWiki / Semgrep one-click) — kept
  - "Three text modules already in the store" — new,
    points the operator at text.extract / text.summarize /
    text.translate in the grid below
  - "Try the extract → summarize flow" — new, points at
    flows/extract-summarize.yaml
  Architecture-education stories (sandbox model, hash-
  chained audit, air-gap posture) are gone from this surface
  — they belong on the Welcome page that
  `docs/landing-page-design.md` lays out.

- DeepWiki + Semgrep added to the Settings → MCP-Clients
  add-server suggestion-chip catalogue. Until now the chips
  were nine stdio servers that need Node + npx; the two
  HTTPS public sources only existed as one-click cards in
  the Today hero. Operators who dismissed the hero had no
  in-Settings path to find them. The new entries sit at the
  top of the catalogue with an explicit "Public HTTPS — no
  Node, no API key" descriptor and the same icons the Today
  hero already uses.

- `docs/landing-page-design.md` (new). Captures the design
  for a sidebar Welcome page that hosts the three-pillar
  intro, the trust-posture deck, the getting-started
  checklist, and an embedded-doc reader so operator-facing
  documentation stays inside Studio (`flutter_markdown`
  rendering of bundled `assets/docs/*.md`) instead of
  clicking out to a browser. Three-phase implementation
  plan: scaffolding, embedded docs, computed checklist.
  Build is gated on operator alignment; this doc is the
  alignment artefact.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-08 23:53:05 +02:00
flemming-it
a7e2eb101a feat(studio): drop Modules tab, fold its content into Store detail (v0.35.0)
Modules-as-a-tab was redundant with the Store after the
"Installed" filter and the federation work landed. Two pieces
of unique content kept it alive: the per-module declared
permissions list and the on-disk module directory. Both now
live inside the Store detail sheet.

Changes:

- Sidebar nav loses the Modules entry. `_pages` no longer
  carries a `'modules'` slot. The unused `import
  'pages/modules.dart'` is dropped from main.dart. Smoke test
  updated to skip the Modules-text expectation.

- Store detail sheet (`_StoreDetailSheet`) gains two new
  sections, rendered only when the entry is installed and the
  hub returned `ModuleDetail` for it:

    Declared permissions  →  same icon-prefixed list the
                              old Modules sheet shipped
                              (`net:`, `fs.read:`, `fs.write:`,
                              `env:`, `hub:`).
    Module directory      →  selectable mono path so the
                              operator can paste it into a
                              shell.

  An async `moduleInfo` fetch fires from `initState` only when
  `widget.item.installed` is true, so the regular
  not-installed detail-sheet path takes no extra round-trip.
  Failures stay silent — the sections just hide.

- The `FaiModuleSheet` widget stays intact. Cmd+K still uses
  it as a quick-info modal for installed modules; the
  longer-form Store detail sheet covers the same data plus
  the description, screenshots, and docs that operators
  reach for in the Store.

Three new ARB keys: `storeSectionPermissions`,
`storeSectionDirectory`, `storeSectionPermissionsNone`.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-08 14:57:51 +02:00
flemming-it
3b5fb027c3 feat(studio): de-jargonize, reveal-in-OS, copyable errors, today carousel, AppBar alignment, F∆I window title (v0.34.0)
Sweeping pass against six user reports collected this session.

1. "Capabilities ist nicht deutsch, Chain auch nicht."
   The DE locale still leaked English vocabulary. Replaced
   "Capabilities" → "Fähigkeiten" and "Chain" / "Hash-Chain"
   → "Kette" / "Hash-Kette" everywhere — store search hint,
   recommended-source body, federated toast, doctor summary
   chain row, modules panel summary, MCP / n8n hints, the
   approvals history blurb. Wire-level identifiers
   (`chain.reset`) stay as code.

2. "Bei Fehlern unten muss man die auch ins clipboard
   kopieren können." New `FaiErrorBox` widget: selectable
   monospace block with a small copy-to-clipboard icon
   button that flips to a checkmark for two seconds after
   click. Applied to the Doctor update banner output and
   the Settings channel toast — the two places long
   stderr / stdout lands.

3. "Öffnen bei Log kann es nicht öffnen. Audit-DB auch
   nicht. PID auch nicht."
   Cause: `SystemActions.openInOs` shells out to `open` /
   `xdg-open` on file paths the OS has no default handler
   for (SQLite DB, PID file, log without an .ext that
   binds). New `revealInOs` uses `open -R` on macOS,
   `explorer /select,` on Windows, and the parent
   directory via `xdg-open` on Linux. Doctor's path rows
   carry an `isDirectory` flag that routes through the new
   `openOrReveal` so files reveal in Finder / Explorer
   instead of failing silently.

4. "Oben im Store könnte man diesen Redaktionshinweis auch
   so bauen, dass man mit pfeil nach rechts links auch
   weitere anzeigen kann."
   The Today-Hero became a carousel. Curated fallback
   list grew from one entry to four (public sources, the
   sandbox-by-default permission story, the hash-chained
   audit story, the air-gap-ready single-binary pitch).
   Hero gets prev / next chevrons plus a dot indicator
   when the current snapshot has more than one slide.
   Operator-accepted stories stay single — the carousel
   collapses when there's only one to show.

5. "Ich fände es schöner wenn rechts und links im Store
   die Abstände konsistent sind, das Reload-Symbol rechts
   ist zu weit rechts und Store links auch nicht bündig."
   AppBar now has `titleSpacing: FaiSpace.xl` so the
   title's left edge sits flush with the body's left
   padding (24 dp), and the trailing `SizedBox` after the
   reload icon shrunk so the icon's outer edge meets the
   right edge of the rightmost grid card.

6. "Oben der Titel zeigt fai_studio an, das sollte F∆I
   Studio sein." The OS window title was the
   pubspec-derived "fai_studio". Macos/Linux/Windows
   runners now hard-code "F∆I Studio" (with the U+2206
   triangle escape so the C++ source stays ASCII). macOS
   bundle name and display name lifted out of the
   PRODUCT_NAME variable for the same reason.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-08 14:48:33 +02:00
flemming-it
22a851b7e2 fix(studio): doctor i18n + light-mode AppBar contrast (v0.33.1)
Two reports:

1. "In Diagnose ist viel nicht übersetzt, wie pending, chain
   etc." The Doctor summary strip (Modules / Approvals / Audit
   / Services tiles) and the modules panel still rendered
   English wire labels: "pending", "chain", "declared",
   "loaded", "empty", "attention", "No pending approvals",
   "No host services declared", and the matching "{n} modules
   · {m} capabilities" / "{n} approvals awaiting review"
   strings.

   Fix: 13 new ARB keys covering the summary tiles, modules
   panel, and services panel; doctor.dart now reads them via
   AppLocalizations. German operators no longer see English
   labels on Diagnose.

2. "Im Light mode ist der kontrast zur überschrift falsch,
   store ist weiß auf weiß." The AppBar title rendered
   white-on-white in light mode.

   Cause: `appBarTheme.titleTextStyle = textTheme.headlineSmall`
   passed a TextStyle built fresh from GoogleFonts.inter(...)
   with `color: null`. Material's "merge foregroundColor at
   draw time" path didn't always populate it — depended on
   build configuration. The colour fell through to whatever
   the surrounding DefaultTextStyle had, which on the
   light-mode AppBar was the surface colour.

   Fix: bake the foreground colour straight into the title
   style via `copyWith(color: scheme.onSurface)`. Also pin
   `iconTheme: IconThemeData(color: scheme.onSurface)` so
   actions-row icons get the same treatment defensively.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-08 13:43:17 +02:00
flemming-it
de22791e86 feat(studio): plain-language store, bottom-anchored ask bar (v0.33.0)
The store had three structural problems flagged in user
feedback:

1. "Lernkurve zu hoch — alle bridge, debug, alpha, planned,
   keiner weiß was das heißt." Wire-level enum values bled
   through into the UI: `bridge`, `debug`, `published`,
   `alpha`, `planned`. Operators don't share the data model.

2. "Filter / Kategorien sollten oben in die Leiste neben Store
   zum ausklappen, der Body soll übersichtlicher werden." A
   chip-row plus category strip plus result-count line ate a
   full row of viewport on every screen.

3. "Suche passt besser an den unteren Bildschirmrand." The
   chat-style ask bar belongs at the bottom — Claude / ChatGPT
   / Slack pattern — not at the top where it competes with
   the Today hero.

Plus: "Es gibt noch ganz viele overflows in den Beschreibungen."

Changes:

- Plain-language display names. New ARB keys for category
  labels (Connectors / Sample modules / AI models / Storage /
  Channels / Authentication / Orchestration / …) and updated
  status values (stable / experimental / coming soon). Wire
  ids stay in StoreEntry, RPCs, seed.yaml — only the rendered
  pills and dropdowns get translated. Helpers
  `_categoryDisplayName` and `_statusDisplayName` keep the
  mapping in one place.

- Toolbar moves into the AppBar. The new `_CategoryDropdown`
  hosts the category picker as a popup-menu with localised
  labels; the existing `_FilterButton` lives next to it; the
  reload icon stays where it always was. The body no longer
  carries any chip strip or result-count line.

- Bottom-anchored `_AskBar`. The Scaffold body becomes a
  Column of `Expanded(scrollable content)` plus a pinned
  composer row at the foot of the viewport, with a top
  divider matching the chat-input pattern of modern AI
  assistants. AI answers now render at the top of the scroll
  (above the Today-Hero) so the operator sees them right
  after submitting the question at the bottom.

- Overflow sweep on descriptions. Today-Hero header row
  becomes a Wrap (badge + deck flow naturally on narrow
  windows), the title is bound to 3 lines, the body to 6.
  Card category text gets `maxLines: 1, ellipsis` in both
  StoreCard and FeaturedTile. Recommended-source card title
  gets the same treatment.

- Better toast for the "added but zero capabilities" path.
  When MCP discovery succeeds but returns no tools (typical
  Streamable-HTTP servers without Mcp-Session-Id support, or
  servers that need `notifications/initialized`), the toast
  now explains the situation in plain language and points
  the operator at Settings → MCP Clients to retry. Old
  pluralised toast still fires when N > 0.

Hub-side `notifications/initialized` for HTTP MCP and
Mcp-Session-Id support are out of scope for this commit;
tracked as a separate fai_hub follow-up.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-08 13:37:35 +02:00
flemming-it
c7869870c4 feat(studio): plain-language ask bar + collapsible filter + AI search (v0.32.0)
Three changes against the user feedback "der text ist zu
zerhackt, die filter-leiste nimmt zu viel sicht weg, llm-suche
wenn das geht":

1. Today-Hero copy rewritten in flowing prose. The fallback
   story drops the code-fence templates ("`mcp.<server>.<tool>`")
   and reads as a single arc per language. Same trust model,
   gentler register.

2. Filter row collapses behind a single `_FilterButton` with a
   "Filter · N active" count badge. Status / Source / Installed
   chips now live inside `_FilterDialog` — invisible until the
   operator wants them, recovers a full row of viewport. The
   category strip stays inline because category is the most
   common cut and benefits from being one click away.

3. New `_AskBar` replaces the old single-line `_SearchField`:

   - Multi-line input (1-4 lines auto-grow) so questions don't
     overflow horizontally.
   - Cmd+Enter / Ctrl+Enter submits; bare Enter inserts a
     newline so the operator can write multi-line questions
     naturally.
   - Heuristic question detector (ends in `?` or ≥4 words)
     routes the submit to the System-AI's askAi RPC instead of
     the substring search. Live keystrokes still keyword-search
     for short queries; question-shaped input holds the grid
     steady until submit so the visible result set doesn't
     wipe with every space.
   - The LLM is asked for a strict-JSON ranking of up to 5
     modules with a one-phrase reason each. Unknown module
     names are dropped silently — operators must not see
     hallucinated entries.
   - The new `_AiAnswerCard` renders the LLM's answer plus the
     ranked match list above the grid; the grid filters to
     just those names so the answer and the visible cards
     stay coherent. Clearing the question collapses everything
     back.
   - Falls back to plain keyword search when System AI is off
     (with an explanatory hint instead of the AI hint).

The AI path uses the System-AI the operator already configured
in Settings; same privacy mode, same audit-log trail. Nothing
new leaves the hub.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-08 13:21:40 +02:00