A store icon in the Store app bar opens a dialog that lists the
configured module stores + the bundled seed (with per-source module
counts), lets the operator add a store by index URL (the hub fetches +
merges it live so its modules appear immediately), and remove a store.
Backed by the new ListStores/AddStore/RemoveStore RPCs + SDK methods.
This is how a domain app's published modules (e.g. reclaim's) become
visible in the Store without touching the CLI.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The Studio design system, widgets and helpers carried a Fai* / fai_
prefix (FaiSpace, FaiColors, FaiTheme, FaiLog, 17 fai_*.dart files, the
faiBinary* l10n keys). Studio is the Ch∆In product, so rename them to
Chain* / chain_ — carefully preserving English fail/failure/failed.
Also fix stale references: the 'fai' binary in l10n strings -> 'chain',
FAI_* env vars (FAI_BIN/DATA_DIR/MODULES_DIR/TODAY/BOOTSTRAP_TOKEN) ->
CHAIN_*, fai_platform -> fai_chain, fai_hub -> chain_hub. Vendor
security-hook tooling (FAI_BANNED_TERMS_FILE) + the .fai bundle ext left.
flutter analyze + test: clean (20 passed).
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Studio follows the platform rename: product branding F∆I -> Ch∆In in UI
strings, command examples fai -> chain, and — critically — the spawned
hub binary path ~/.fai/bin/fai -> ~/.fai/bin/chain so Studio launches
the renamed binary. The fai_* Dart identifiers (FaiLog, widget files,
the generated SDK) stay = vendor/internal namespace. flutter analyze:
no issues.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
- Connection-aware Welcome: when the hub is down, show a hero with a
primary "Start hub" CTA + install fallback instead of a dead,
all-unchecked onboarding checklist (the first-run cliff).
- Actionable binary-not-found (file picker + install link, not a
"set FAI_BIN" dead end) and a connect-failure banner after
repeated failed health polls.
- Localize six hardcoded English error/toast clusters (DE+EN ARB).
- Bundle Inter + JetBrains Mono as assets; drop the runtime
google_fonts fetch (air-gap / KRITIS safe, no font-swap flash).
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Bundles the error-UX overhaul and the inline log viewer:
- New `FaiLog` (`~/.fai/logs/studio-errors.log`, 256 KiB rotation,
JSON-per-line). Every operator-visible failure is appended so
`fai admin doctor` and the new viewer can show the trail
without the operator having to reproduce the failure.
- New `showFaiErrorSnack` / `showFaiErrorDialog` helpers wrap
`FaiErrorBox` in copyable surfaces; 27 ad-hoc
`SnackBar(content: Text(e.toString()))` sites swept to use
them (settings, doctor, audit, store, module sheet, system-AI
editor, flow output).
- New `FaiLogViewer` modal (`showFaiLogViewer`) renders log
files inline with line numbers, JSON-key + `[level]` token
colouring, Copy-all, Refresh, Open-externally. Doctor's
daemon-paths panel grows a "View" button next to "Open" for
every `.log` row and now also lists the Studio errors log.
- Today carousel: CTAs now actually re-run search after a
`filterCategory` / `runQuery` story is tapped (was only
flipping the chip state). Fallback story list bumped to 8.
- Editor bumped to git ref carrying 0.15.0 (type-token
colouring + analyzer diagnostics).
Studio bumped to 0.62.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
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>
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>
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>
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>
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>