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>
Previous patch dropped the year for current-year events,
showing `05-04 21:25:39` — locale-ambiguous (US reads May 4,
EU reads 5 April). Operators should not have to guess. Now
every non-today timestamp renders as `YYYY-MM-DD HH:mm:ss`;
same-day events keep the compact `HH:mm:ss`.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Previously the audit list only rendered HH:mm:ss, so a row showing
"21:25:39" gave no hint whether it was today, last week, or last
year. The detail dialog already had the full ISO timestamp, but
the operator had to click each row to find out.
_formatTime now keeps the compact HH:mm:ss form for same-day
events, prefixes MM-dd for older days in the current year, and
shows YYYY-MM-dd for the tail of the log. Always rendered in the
operator's local time zone.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
System-AI editor now fetches the detected hardware tier and the
hub's curated model database on open. Each model chip's colour
+ tooltip combines both: green star when curated quality is
good/excellent AND min_hw_tier ≤ host; orange when quality is
fine but the host is below min_hw_tier; warning for "basic"
curated entries. Tooltip exposes the curator's note, parameter
count, context window, and licence. Banner above the form
shows "Detected: <summary> · curation reviewed <date>".
Sidebar footer gains a small monospaced live clock between the
theme toggle and the settings icon. Tooltip exposes full local
ISO + UTC for cross-checking against audit-log timestamps.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Two operator-felt papercuts on the editor:
* Models only appeared after a manual Refresh click. Now the
editor calls `_refreshModels()` from initState (post-frame)
so chips populate immediately. Provider switch also re-fires
the refresh because endpoint / model set changes too.
* Operator had no signal which model is suitable. Tiny models
fail at instruction following; huge models stall on a
laptop. Adds a heuristic suitability rating (parses
parameter count from common names like `gemma3:4b`,
`llama3.2:70b`; falls back to family hints for OpenAI /
Claude). Each chip now carries:
- a coloured leading dot (green=balanced, orange=small
or large, red=huge, grey=unknown)
- a star icon for the hand-curated "tested with F∆I"
allowlist (gemma3:4b, llama3.2:3b, qwen2.5-coder:7b,
gpt-4o-mini, claude-haiku-4-5, …)
- a tooltip ("balanced", "huge — likely too slow on a
laptop", …)
Sorting also moves recommended → balanced → others to the
front so the operator's eye lands on green first.
* Compact legend below the chip wrap explains the colour
code in place — no lookup needed.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Three operator-facing improvements driven by Stefan's testing:
* **Test Connection** runs against the form's *current* values
and no longer requires saving first. Previously we save-then-
test which caused a "system AI is disabled" message when the
form's model field was still empty. Now the test prompt
travels with the request body so unsaved tweaks can be
validated.
* **Model picker** replaces the free-text Model field. A
Refresh button calls HubAdmin.ListSystemAiModels (provider's
`/v1/models`); installed models show up as clickable chips
that fill the field with one tap. The text input stays for
cases where the operator already knows the id.
* **Pull** button (Ollama only) downloads a model via
`/api/pull`. Type `gemma3:4b`, click Pull, spinner, then the
new model lights up in the chip list. No more "switch to a
terminal to ollama-pull" detour.
Helper text updates for the empty-model case ("Required: pick
from the list (Refresh) or type one. Use Pull to download from
Ollama.") so a fresh operator never wonders why Save / Test
errors out.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Operator reported Cmd+, doing nothing. macOS reserves Cmd+,
for the native app-Preferences menu item which Flutter Desktop
doesn't wire up — the keystroke is captured by the OS before
the Shortcuts widget sees it.
Adds Cmd+; as the working alternative; keeps Cmd+, registered
too so a future Flutter version (or a native-menu setup) can
light it up. Tooltip on the sidebar gear icon now reads
"Settings (⌘;)" so the discoverable affordance also tells you
the shortcut.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Replaces the read-only System AI block in Settings with a real
editor. Operator never has to touch ~/.fai/config.yaml again
to change provider, endpoint, model, API-key env-var, or
privacy mode.
UX choices that align with the zero-learning-curve rule:
* Provider dropdown lists Ollama / OpenAI / LM Studio / vLLM
/ Custom by friendly name. Each selection auto-fills the
endpoint and api_key_env defaults — but only when the
field is still empty or matches a different preset's
default, so manual overrides are never clobbered.
* Each provider has a one-line description rendered under
the dropdown ("Ollama → Local `ollama serve`. Models stay
on your machine. No API key needed.") and a model-hint
placeholder ("gemma3:4b · llama3.2:3b · qwen2.5-coder:7b")
that goes away when a model is typed.
* Privacy mode is a vertical radio group with each option's
description in place — no doc-lookup needed.
* "Test connection" sends an `ok`-ping and renders the
same error-kind → fix-hint mapping the audit drill-down
uses.
* "Save" persists + hot-reloads the hub. No daemon restart
required. UI status badge flips to "enabled · <privacy>"
immediately.
`fai_dart_sdk` 0.5.0 carries the underlying `updateSystemAi` /
`testSystemAi` RPCs.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Two new operator affordances powered by `HubAdmin.AskAi`:
* **Audit drill-down → "Explain" button.** Tappable on every
failed event when System AI is configured; greyed out with
a "Configure in Settings" tooltip otherwise. Shows an
inline panel under the event fields with the LLM's plain-
language explanation, a "FIX" suggestion, the privacy-mode
badge (`redacted` / `full`), and round-trip latency. Errors
map to actionable fix hints (`env_missing` → "set $VAR in
your shell, then fai daemon restart"; `network` → "is your
provider running?"; etc.) — no raw HTTP errors surfaced.
* **Settings dialog → System AI panel.** Renders the live
HubAdmin.SystemAiStatus (provider, endpoint, model,
api_key_env name, privacy mode). When disabled, shows a
one-line explainer + pointer to docs/architecture/system-ai.md.
Read-only for now; editing the config still requires
~/.fai/config.yaml (Phase-1+ adds in-place editing).
Both surfaces follow the new zero-learning-curve UX rule:
the user never has to look up what the configurable means or
how to fix an error — every state self-explains in place.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Turns Studio from read-only dashboard into operator console.
Pieces:
* **New Store page** (sidebar destination #3): browses the
hub's bundled store-index with query + category + status
filters. Each card has an Install button that prompts for
the `.fai` bundle source (URL or local path), ships it to
HubAdmin.InstallModule, shows success / error in a progress
dialog, refreshes the list.
* **Audit drill-down**: every event row is now tappable;
opens a modal with all LoggedEvent fields including the
new `detail` JSON pretty-printed in a code block. KRITIS
forensic story: every audit row → full structured detail
in two clicks.
* **Approvals payload preview**: shipped already; now
pretty-prints JSON when the preview parses as such, plus
a clearer `PAYLOAD PREVIEW` label. Reviewer identity
defaults to `$USER@studio` instead of the hard-coded
`studio-mvp` so the audit trail records who acted.
* **Channel switcher in Settings dialog**: the dialog now
pulls HubAdmin.ChannelStatus and renders one row per
channel (local / dev / beta / production) with port,
running indicator, active marker. A "Connect" button
on a running channel sets Studio's endpoint to that
channel's port and reconnects in one click.
Cmd+1..6 keyboard shortcuts updated for the six destinations.
Widget test asserts every destination renders.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Doctor page now opens with a slim banner above the summary strip
in two cases: an actually newer version on the channel ("Update
available — v0.x.y", success-tone "new" pill, release-notes URL
when present) or the release host being unreachable
("Release host unreachable", neutral "offline" pill, with the
network reason). When local matches latest the banner stays
hidden — quiet UI is the right default.
Hooks into the existing parallel-fetch in `HubService.doctor` so
the new RPC adds zero latency to the page load.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Two operator-DX additions:
1. **Flows** is the new third destination in the sidebar.
Lists saved flows from the hub via the new HubAdmin
ListFlows RPC. Each row has a Run button that opens an
input dialog (key=value pairs, one per line, all values
sent as text payloads — the binary-input case stays in
`fai run` CLI). Flow runs in a non-dismissable progress
dialog; output is shown per-key with monospace
selectable text. Errors render with the exception detail
for diagnosis.
2. **Keyboard shortcuts** at the shell level:
- Cmd+1 / Cmd+2 / Cmd+3 / Cmd+4 / Cmd+5 jump to the
matching destination (Doctor / Modules / Flows / Audit
/ Approvals).
- Cmd+, opens the Settings dialog (macOS convention).
Implemented via Flutter's Shortcuts/Actions/Intent triple
so the bindings are discoverable in IDE devtools and
composable with platform-specific overrides later.
Bumps fai_studio 0.7.3 -> 0.8.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The toggle changed _mode and called setState on StudioAppState,
but the sidebar that hosts the toggle button never rebuilt:
StudioApp returns `MaterialApp(home: const StudioShell())`, and
the const child's element doesn't get a didUpdateWidget on
parent rebuild. So the toggle's icon would only flip on the
next health-poll tick (up to 5s later) when the sidebar's own
setState fired for an unrelated reason.
Switch to a ValueNotifier-based pattern:
- StudioAppState exposes `modeNotifier` (ValueNotifier<...>)
instead of a getter.
- StudioApp wraps MaterialApp in a ValueListenableBuilder
bound to the notifier so themeMode flips immediately.
- _ThemeToggle is itself a ValueListenableBuilder against the
same notifier — its icon and onPressed always reflect the
current value without depending on parent rebuild timing.
Bumps fai_studio 0.7.2 -> 0.7.3.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Two issues from live feedback:
1. The version label said "v0.7.1 · warm-minimalism". The
suffix is an internal design-direction codename, not
user-facing. Drop it — the line now just reads "v0.7.2".
2. The ∆ pulse in live mode used only alpha + blur, which is
too subtle to register at first glance. Replace with a real
scale-pulse: the whole mark grows from 0.85x → 1.15x → 0.85x
over 1.6s. Glow halo blur range expanded (8→22) and alpha
range widened. The triangle also gets a soft fill on the
up-beat. Easy to spot from across the room.
Bumps fai_studio 0.7.1 -> 0.7.2.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Two issues:
1. _StatTile in the Doctor page overflowed by ~6px on narrow
widths because the Row containing the icon + label "AUDIT
CHAIN" with letterSpacing=0.6 didn't fit in the ~90px
constraint per tile (4 tiles in a Row sharing the page
width). Fix: smaller icon (16→14), tighter gap, label wrapped
in Flexible with TextOverflow.ellipsis, and the longest label
shortened "AUDIT CHAIN" → "AUDIT" with the chain count moved
to the subtitle.
2. The new redesign was easy to mistake for the previous build
from the outside. Add a small monospaced version line right
under "F∆I Studio" in the sidebar header:
F∆I Studio
v0.7.1 · warm-minimalism
Self-identifying — "wrong build" is now visible without
reading the about dialog.
Bumps fai_studio 0.7.0 -> 0.7.1.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Three-way switch in the sidebar footer next to the settings
gear. Cycles system → light → dark → system. Choice persists
via shared_preferences across app launches; restored before the
first frame so there's no theme flicker on startup.
The icon changes per state (auto / sun / moon) and the tooltip
spells it out so the cycling behaviour is discoverable.
- StudioApp is now stateful; exposes StudioApp.of(context) to
let descendants flip the theme without prop-drilling.
- ThemeModeValue enum lives in data/hub.dart so the persistence
layer stays free of flutter/material imports.
Bumps fai_studio 0.6.0 -> 0.7.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Tapping a module card on the Modules page slides up a detail
sheet showing the full manifest data: directory path
(monospaced + selectable), every capability with version, and
every declared permission with a semantic icon (net = globe,
fs.read = folder, fs.write = edit, env = terminal, hub =
shield).
Empty permission list shows "(none — pure-computation module)"
explicitly — the operator should see *why* a module needed
zero perms, not be left guessing.
Backed by HubAdmin.ModuleInfo. New widget FaiModuleSheet with a
small drag-handle, FutureBuilder for the load state, error
surfacing if the RPC fails.
Bumps fai_studio 0.5.0 -> 0.6.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Studio is no longer hardcoded to localhost:50051. A gear icon
in the sidebar footer opens a small settings dialog where the
operator picks host / port / TLS-on-or-off. Saved values
persist via shared_preferences and Studio reconnects to the
new endpoint immediately.
- data/hub.dart: HubService.loadPersistedEndpoint() called once
before the first frame; reconnect() persists on every change.
- widgets/fai_settings_dialog.dart: typed form with port range
validation, live URL preview pill, error surfacing if the
reconnect fails.
- main.dart: gear icon at the sidebar bottom, replaces the
static "platform v0.10.42" footer.
shared_preferences added as dependency. flutter analyze clean,
flutter test 2/2, macOS debug build succeeds.
Bumps fai_studio 0.4.0 -> 0.5.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Composes the platform's health picture in one view:
- Summary strip: 4 stat tiles for modules / approvals / audit
chain / services with semantic accent colour.
- Event-log panel: WORM-1 badge + chain status, points to the
first tampered event id when integrity fails.
- Modules & approvals panel: counts plus an "attention" pill
when approvals are pending.
- Services panel: declared services from operator config or a
helpful empty-state pointing at ~/.fai/config.yaml.
Backed by the new HubAdmin RPCs (VerifyEventChain, ListServices)
plus the existing ListCapabilities and ListApprovals. One
HubService.doctor() call fans out to all four in parallel.
Doctor is the new first destination in the sidebar — that's the
page an operator wants to land on after starting the app.
Bumps fai_studio 0.3.0 -> 0.4.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Replaces the Material-default look with a deliberate visual
language. Single accent (sky-cyan), Inter + JetBrains Mono via
google_fonts, dense rows over puffy cards, micro-interactions
under 250ms. Dark-first, light reaches parity.
New foundation under lib/theme/:
- tokens.dart FaiColors / FaiSpace / FaiRadius / FaiMotion
- theme.dart ColorScheme + typography + component themes for
light and dark, plus FaiTheme.mono helper for
technical strings (IDs, paths, capability refs).
Six primitives under lib/widgets/:
- FaiCard flat card, optional accent stripe (top or
left). No shadows.
- FaiPill small inline label with five tones
(neutral / accent / success / warning /
danger), optional mono and leading icon.
- FaiStatusDot breathing dot, used as a "live" indicator.
- FaiDataRow Linear-style dense row for the audit
stream — hover-elevation, mono leading,
coloured leading stripe.
- FaiEmptyState gracious icon + title + hint + action,
replaces "(no data)" everywhere.
- FaiDeltaMark the ∆ signature element. Three modes —
idle (still), live (gentle pulse), busy
(slow rotation). Drawn from primitives,
not a font glyph. Lives in the sidebar
header so the brand is always visible.
Page-level changes:
- main.dart custom 220px sidebar replaces the Material
NavigationRail. ∆ on top, hub-connection
pill below it, hover-animated destinations,
page transitions are 200ms slide+fade.
- modules.dart FaiCard rows with capability pills.
- audit.dart FaiDataRow stream, segmented filter chips,
live status bar with hash-chain badge.
- approvals.dart FaiCard with accent-top stripe, structured
action footer, toast on approve/reject,
themed reject-reason dialog.
google_fonts added as dep. flutter analyze clean. flutter test
2/2. flutter build macos --debug succeeds.
Bumps fai_studio 0.2.0 -> 0.3.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Studio's three MVP pages now pull live data from a running hub
via fai_dart_sdk. Mock fixture removed.
- data/hub.dart: HubService singleton wraps fai_dart_sdk's
HubClient and exposes UI-friendly types (ModuleSummary,
AuditEvent, PendingApproval) so pages don't import protobuf.
- pages/modules.dart: FutureBuilder against listModules,
groups CapabilityEntry rows by module, retry-on-error UI.
- pages/audit.dart: 2s polling Timer, status bar shows
"live (polling 2s)" or "disconnected — <error>".
- pages/approvals.dart: live listApprovals, Approve sends
DecideApproval(APPROVE), Reject opens a reason dialog and
sends DecideApproval(REJECT). Both refresh after success.
- Connection-error states across all pages: cloud_off icon +
hint to start `fai serve` + Retry button.
When no hub is running the pages render their disconnected
state instead of crashing. When a hub is running, the data is
real — no more mock fixture.
Bumps fai_studio 0.1.0 -> 0.2.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Initial scaffold for the F∆I Platform Tier-2 generic GUI client.
Flutter Desktop (macOS, Linux, Windows). Three MVP pages with
mock data, sharing one navigation shell:
- Modules — installed modules with capabilities, declared
permissions and required services.
- Audit — event-stream view with type filter and tone-coded
rows (started / completed / failed).
- Approvals — pending system.approval@^0 reviews with prompt,
payload preview, and approve/reject buttons.
Live gRPC connection arrives in the next iteration via
fai_dart_sdk (sibling repo, currently a typed stub).
Future Forgejo path: fai/studio. Local layout matches existing
fai_platform/ convention.
Background: see docs/architecture/client.md in the platform
repo. The tier-2 client was previously called "Stage" — renamed
to "Studio" on 2026-05-05 to avoid confusion with
"staging environment".
flutter analyze: clean. flutter test: 2/2.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>