Studio's UI-side `CapabilityInfo` record now carries the
three fields that the F∆I 0.12.0 hub exposes on
CapabilityEntry:
- `provider` — publisher identity ("fai", "fai.system",
"bmds.spark", ...)
- `sourceKind` — transport ("bundle", "system", "mcp",
"n8n", "temporal", "webhook")
- `versionAdvisory` — true for federation sources whose
version pin is a label, not a contract
`allCapabilities()` populates them from the freshly-regenerated
protobuf bindings. Defaults are backward-compatible (empty
strings + false) so a pre-0.12 hub still works against this
Studio build.
Badge rendering in the capability picker / store grid lands
in a follow-up Studio commit; this change is the data-layer
plumbing only. dart analyze green.
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 pieces of the Studio plugin host:
- `data/theme_plugin.dart`: discovery, persistence, ColorScheme
translation. Plugins listed via list_capabilities filtered
to studio.theme.*; selection persisted in SharedPreferences;
loadThemePluginSchemes builds a ColorScheme pair from the
plugin's 14 ARGB tokens per brightness.
- `main.dart`: StudioApp gains themePluginNotifier +
setThemePlugin(). MaterialApp wraps a FutureBuilder that
re-fetches the plugin's ColorSchemes whenever the
capability flips; falls back to FaiTheme.light/dark on
null or load failure.
- `fai_settings_dialog.dart`: new `_ThemePluginPanel` shows a
dropdown of installed studio.theme.* capabilities + a
"Built-in" entry. Switching applies immediately.
- HubService.invokePluginTranslate added as the typed wrapper
for the new gRPC RPC — ready for the FaiEnBadge swap-out
in the next iteration.
- 4 new l10n keys (themePluginHeader / None / Hint / Empty),
EN+DE.
flutter analyze + flutter test (widget + friendly_error): both
green. The integration test for the theme picker is the
existing fai_runtime plugin_theme + Studio-side
invokePluginTheme path that round-trips through the new
mirror-installable studio-theme-solarized bundle.
Signed-off-by: flemming-it <sf@flemming.it>
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Wraps the new InvokePluginTheme RPC and ships an end-to-end
test that loads studio-theme-solarized into a fresh
HubFixture and asserts the light/dark schemes round-trip.
Signed-off-by: flemming-it <sf@flemming.it>
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Two clean-ups:
- `fai_system_ai_editor.dart` uses the new `RadioGroup<T>`
ancestor pattern Material 3 introduced after Flutter 3.32.
Same UX (three radios for off/redacted/full), no
deprecation warnings, `flutter analyze` is now zero-issue
for the whole project.
- New `install_install_planned_test.dart` asserts that
installing a `planned` seed entry surfaces a clear,
human-readable error class (no panic, no silent success).
Locks in the failed-precondition path Studio's
friendly-error mapper depends on.
Integration test suite is now 3 scenarios:
- capabilities_test.dart: system.approval+kind tag
- install_install_planned_test.dart: planned-install error
All run against a fresh `fai serve` subprocess via
HubFixture, ~2 s total.
Signed-off-by: flemming-it <sf@flemming.it>
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>
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>
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>
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>
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>
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>
_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>
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>
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>
UI parity with the platform CLI: `fai reset` (v0.10.93) is now
reachable from Settings. The panel shows the reset blurb plus
two checkboxes mirroring the CLI flags (Keep modules / Keep
audit log + saved flows), then a destructive-styled "Reset hub
state" button. Click goes through a confirm dialog before
SystemActions.faiReset is invoked.
After the CLI returns, Studio bounces its gRPC channel against
the same endpoint (the daemon restarts on the same channel +
port, so no Settings change needed) and reloads every panel:
channel status, system AI, MCP clients, n8n endpoints, registry
token. Output (success or failure) is shown in a copyable error
box so any unexpected stderr lands somewhere the operator can
paste from.
Sits at the bottom of the dialog under "HUB MAINTENANCE",
separated from the everyday config so a destructive button
doesn't crowd the day-to-day controls.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The hub install path now reads `~/.fai/registry-token` as a
fallback when `FAI_REGISTRY_TOKEN` is unset (platform v0.10.92).
Studio now writes that file directly: a fresh operator pastes
the PAT into Settings → Registry credentials, hits Save, and
the next install attempt resolves the auth wall without any
shell or env-var setup.
The token never round-trips back into Studio after save —
status is shown only as "Configured (40 chars)" / "Not set"
with no display of the secret itself. The Clear action deletes
the file. On Unix the file is chmod-ed to 0600 (owner-only
read/write); Windows leaves the default user ACL in place.
Storage is strictly local: `~/.fai/registry-token`, never sent
to a remote service. The hint text under the field says so to
make the data flow explicit.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Two issues from the user feedback after v0.31.0:
1. RenderFlex overflow in the store body. The inner Column
wrapped the editorial chrome (Today, Recommended,
Featured) plus an Expanded grid; on small windows the
chrome alone exceeded the available height and the grid
got squeezed past zero, painting the yellow-and-black
stripe.
Fix: wrap the inner Column in a SingleChildScrollView so
chrome and grid scroll as one continuous surface
(App-Store / Play-Store behaviour). The grid switches to
`shrinkWrap: true` + `NeverScrollableScrollPhysics()` so
it doesn't try to claim its own viewport.
2. "Der Store ist zu unaufgeräumt, ich fühle mich
erschlagen." — three editorial cards stacked above the
filter row + grid was too much.
Visual cleanup:
- The recommended-source quick-add chips are now inlined
into the Today hero's footer (same row as the CTA).
The standalone `_RecommendedSourcesStrip` only fires in
the corner case where Today is dismissed AND no
federation exists yet.
- The Featured strip no longer competes with Today —
it only renders once Today has been dismissed. Today
plays the editorial-hero role; Featured is the
fallback editorial surface.
- Provenance pill ("native") is dropped on native cards.
Native is the default and doesn't earn a badge — only
foreign code (mcp / n8n) gets the provenance pill, the
same way "verified" is opt-in in commercial app stores.
Net: at most one editorial card above the grid at any time.
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>
App-store research shows that the most loved discovery
surfaces — Apple's Today tab being the canonical example —
work because editors tell stories instead of stacking
algorithmic recommendations. F∆I has no surveillance budget
and no monetization pressure, so we lean fully into editorial
curation.
This commit adds `_StoreTodayHero`, the first surface a
browsing operator sees:
- Single curated story per release, kept const in
[_kCurrentTodayStory] so the narrative is reviewable in code
review and ships in the audit log via the binary hash. No
CMS, no network, no surveillance.
- Bilingual content shipped inline (`titleEn`/`titleDe`,
`bodyEn`/`bodyDe`) so KRITIS deployments don't need a
translation backend.
- Gradient backdrop with hero icon, deck, narrative paragraph,
optional CTA. Visual treatment matches Apple Today's
hierarchy: badge → headline → body → action.
- Auto-hides whenever a filter is active so a purposeful
search isn't pushed below the fold.
- Per-session dismiss button — no permanent suppression, the
next Studio launch shows it again so a release-bumped story
has a chance to be seen.
Current story (Studio v0.30.x) directs operators to the new
recommended-sources strip, closing the loop between editorial
context and one-click action.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The Store is now the centre of capability discovery — provider
configuration moves out of Settings into the Store itself.
Three changes work together:
1. Source filter chips next to the existing status chips:
All sources / Native / MCP / n8n. Applied client-side after
the hub returns results so toggling is instant. The result
count and the "Clear filters" reset both account for the
source filter too.
2. Per-card provenance pill (`_ProvenancePill`): shows whether
each entry is native, mcp · <provider>, or n8n · <provider>
so the operator can triage the source at a glance — same
role the "verified" badge plays in commercial app stores.
3. `_RecommendedSourcesStrip` replaces the older
`_FederationNudge`. Renders curated public MCP servers
(DeepWiki, Semgrep) as one-click cards with an inline
`[+ Add]` button — no Settings detour, no form. Both servers
are HTTPS Streamable-HTTP, no API key, no subprocess.
Useful AI was considered but its 340+ tools would drown the
index — kept out of the curated list. The strip auto-hides
the moment any federated entry exists.
Removed the now-orphaned storeFederationNudge* ARB keys.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
- Localize Flows page: app-bar title and reload tooltip,
hub-unreachable / no-saved-flows empty states, run-flow input
dialog (title with flow name, description, hint, Cancel /
Run), running dialog (title, "Flow running…" status, no-output
message, Close button), flow-card Run button.
- Localize the module-sheet bottom sheet: failed-to-load text,
Capabilities and "Declared permissions" section headers,
no-permissions placeholder copy.
- Localize the System-AI configuration dialog: title, intro
paragraph, provider dropdown label, endpoint label, API-key
env-var label (required vs optional) and disclaimer, privacy
mode header and three options (Off / Redacted / Full) plus
their descriptions, test-result panel ("Connection ok" /
"Connection failed", "Reply: …" prefix), model picker (label,
hint fallback, helper text with Ollama variant, Refresh /
Pull / Pulling… buttons, list errors and empty states),
hardware banner ("Detected: …" + " · curation reviewed …"
suffix), suitability legend (recommended / balanced / small /
large / huge / unknown), cache row with pluralized count and
Clear button, cache cleared / clear-failed toasts, pull-empty
/ pull-failed errors. Suitability label moved off a getter
onto a `labelFor(BuildContext)` method so it can read the
current locale.
Provider preset descriptions and modelHint strings stay in
English in `_ProviderPreset.all` — they're tightly coupled to
the wire-protocol identifiers and would require a runtime
factory rebuild rather than a const list.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Two threads.
Translation expansion: every visible string on the Doctor
page (section headers, status text, button labels, daemon-
control card, daemon-files panel, update banner) and the
Modules page (recent-activity strip, capabilities label,
uninstall dialog + toast) flips between DE and EN with the
sidebar toggle. Adds ~50 ARB keys split between
`app_en.arb` / `app_de.arb`. Pluralised + parametrised
strings (`{n} events verified`, `Running on {name}: {endpoint}`)
use the standard ICU placeholder syntax so future locales
slot in without code changes.
Sparse-store federation nudge: the Store page renders an
inline banner above the grid when the operator has zero
federated entries — single biggest store-fullness lever is
configuring an MCP server / n8n endpoint, so the banner
says exactly that and the button opens Settings straight
to the editor. Banner dismisses automatically the next
render after a federated entry appears.
Coverage stand for translation: Doctor + Modules + sidebar
+ page titles + nav + Cmd+K labels are bilingual. Settings
dialog, Approvals cards, Audit drilldown, MCP/n8n editors,
Store search hint + filter chips remain English-literal.
The infrastructure (ARB plumbing, generator hookup,
locale-notifier) means each follow-up surface is a
one-line ARB edit + one call-site swap.
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>
Three reported bugs and one onramp gap:
- Store live-search threw `setState() callback returned a
Future`. The `=>` body of `_runSearch` evaluated to the
Future returned by `_load()`. Switched to a block body so
the closure resolves to void; the future itself is still
awaited by the FutureBuilder.
- Doctor's Daemon-control card was visibly narrower than its
siblings — its only stretching child was a Wrap of small
buttons. Wrapped the content in `SizedBox(width: infinity)`
so the card fills the section's width like every other
panel. Card now leads with the live `running on local:
127.0.0.1:50051` status row + status pill so operators see
state at a glance, then offers Restart / Start (when
stopped) / Stop / Status.
- Featured tiles were 320×152 against the regular grid's
~360×168, so the editorial picks looked subordinate to
the dense grid. Bumped to 480×220 with bigger icon, larger
title, larger tagline so the strip reads as a hero band.
- ConnectionPill in the sidebar now offers an inline "Start
hub" button when the daemon is unreachable. Spawns
`fai daemon start` via SystemActions; the shell-level
health poll picks the daemon up on its next tick. Closes
the chicken-and-egg for Windows users who can't open a
shell to bootstrap.
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 operators who never touch a CLI, plus a Store
that reads more like an app store.
Settings dialog:
- Channel rows gain a popup menu: Connect / Make active /
Enable autostart / Disable autostart. "Connect" still
re-points Studio's wire; "Make active" actually writes
~/.fai/current-channel via `fai channel switch`.
- Inline output panel surfaces the spawned binary's stdout
on success or stderr on failure, so operators see what
happened without opening a terminal.
Store page rewrite:
- Big top search bar with a clear button. Live filter on
every keystroke.
- Horizontal category strip auto-populated from the index;
segmented status row (All / Published / Alpha / Planned),
Installed-only chip, result count.
- Grid of cards that reflows to fit the viewport — replaces
the previous single-column list. Each card shows
category-aware icon, version, status, tagline preview, and
a one-click Install (or Details for installed / planned).
- Per-module detail sheet renders the full bilingual
description with a DE/EN toggle, separate Required-
capabilities + Required-host-services sections, repo link,
Read-docs button. Install + Uninstall live at the bottom.
- StoreItem and HubService.searchStore now carry the German
tagline + description so the locale toggle has something
to switch to.
SystemActions extended with `faiChannelSwitch`,
`faiDaemonEnable`, `faiDaemonDisable` so Settings can spawn
the right CLI without each call site reimplementing the
`fai` resolution rules.
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>
Pending tab keeps the existing card layout for actionable
approvals. New History tab shows decided rows (approved /
rejected / expired) as compact one-liners, color-coded by
status with reviewer + decided-at metadata. Click expands to
the full record (prompt, payload preview, reason, ids).
Closes the visibility gap from the recent fix: rejected
approvals were correctly written to the hash-chained audit
log but invisible to operators on the Approvals page itself.
HubService gains `listApprovalsRecords(statuses:)` returning
the decided-side fields the Studio wire previously dropped.
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>