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>
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>
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>
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>
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>