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