Picks up:
- fai_studio_flow_editor 0.9.0: per-field input + output
ports rendered from ModuleInfo, i18n tooltips, four
additional canvas patterns ported from jai_client.
- fai_client_sdk 0.18.0: regenerated ModuleField bindings
for the schema_version 3 / per-field-description proto
bump in fai/platform.
Wires StudioFlowRunDriver.moduleInfo so the editor can
fetch declared field info per step capability. The hub
maps schema_version 3 manifest fields (inputs/outputs
with description.en/de) through ModuleInfoResponse; this
patch wraps them back into the editor's ModuleSpec /
ModuleField vocabulary so the canvas paints them as
distinct anchors with hover tooltips.
ModuleDetail in hub.dart now carries the same inputs +
outputs lists (with ModuleFieldInfo type + i18n
description map), enabling other Studio surfaces — e.g.
the module-detail sheet — to render bilingual field docs
in a later pass.
Plus: store-doc nested-scroll fix. The module detail
panel's docs section sat inside its own 480px viewport
inside the outer sheet's SingleChildScrollView, producing
the double-scrollbar feel. Setting
physics: NeverScrollableScrollPhysics on the Markdown
widget and dropping the maxHeight lets the docs scroll
as part of the outer sheet — one scrollbar, predictable
mouse-wheel behaviour.
Bumps fai_studio to 0.56.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Two operator surfaces shipped together:
**Multi-version uninstall picker** — when more than one version
of a `(provider, name)` is installed side-by-side, both the
module-sheet "Uninstall" affordance and the store-detail
"Uninstall" affordance now ask the operator which version to
remove before calling the RPC. The hub's wire-level support for
this (UninstallModuleRequest.version) was already there; Studio
just wasn't using it. Picker pre-selects the highest version so
single-version flows still take one click.
- `HubService.installedVersions(name)` enumerates the installed
versions via the capabilities list.
- `HubService.uninstallModule(name, version: ...)` forwards
the version into the RPC.
- `_UninstallVersionPickerDialog` (module sheet) and
`_StoreUninstallVersionPickerDialog` (store) host the
picker — separate widgets so each surface can evolve copy
independently. Uses `RadioGroup<String>` for Flutter
3.32+ deprecation compliance.
**Default scope editor** — new DEFAULT SCOPE panel in Settings
that calls the freshly-added HubAdmin RPCs
`GetDefaultScope` / `SetDefaultScope`. Operators can:
- reorder publisher segments with up/down buttons
(first match wins in the bare-form resolver),
- delete entries (hub still rejects empty list — Studio
surfaces the constraint inline),
- add arbitrary entries via the text field,
- add catalog-known publishers via suggestion chips
(sorted alphabetically, populated from the catalog).
Every change persists to `~/.fai/config.yaml` via the hub
and hot-swaps the in-memory copy without a daemon restart.
Bumped pubspec to 0.48.0. dart analyze clean (No issues
found!); flutter test green (11 tests).
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Store cards + detail-sheet now prefix the version pill with
`~` and attach a tooltip for federated entries (MCP / n8n /
Temporal). Upstream services don't honour semver, so the
version pill is a label, not a contract — the tilde says
"this number may change without a version bump."
Three render sites updated to stay visually consistent:
- compact card pill in grid view
- expanded card pill row
- detail-sheet header pill cluster
EN + DE l10n entries added (`storeAdvisoryVersionTooltip`)
and `app_localizations.dart` regenerated.
dart analyze clean; flutter test green (11 tests).
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Adds the missing UI surface for SPARK / Temporal federated
capabilities. Previously only mcp and n8n showed provenance
pills; temporal-sourced entries fell through to the default
"no badge" case.
- storeSourceTemporal + storeProvenanceTemporal l10n keys
(en + de). Pill format: "temporal · <provider>", icon
Icons.timer_outlined, accent tone.
- Source filter chip row gains a "Temporal" option in the
Store filter dialog.
- `_StoreProvenancePill` adds a `case 'temporal'` branch.
Operators federating a SPARK cluster (`services: -
kind: temporal`) now see their workflows visually labelled
as Temporal-sourced in the Store grid + filter UI.
dart analyze green. No test changes required — the
provenance pill is render-only.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Studio's `_sourceForItem` now prefers the wire-level
`sourceKind` field (populated by 0.12+ hubs) over the legacy
name-prefix heuristic. Adds a "temporal" bucket alongside
"mcp" / "n8n" / "native" / "federated" so SPARK-federated
workflows render with their own transport pill on the store
grid + filter chip.
Pre-0.12 hubs return empty `sourceKind` and the legacy
sniffing path still works -- back-compat preserved.
`StoreItem` data class accepts an optional `sourceKind`
parameter (defaults to empty). dart analyze green.
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>
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>
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>
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>
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>
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>