From 711436b71d75d73b510e8579b647f5733ac16389 Mon Sep 17 00:00:00 2001 From: flemming-it Date: Fri, 8 May 2026 23:53:05 +0200 Subject: [PATCH] feat(studio): carousel size lock, store-actionable today stories, HTTPS MCP suggestions in settings (v0.36.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/landing-page-design.md | 178 +++++++++++++++++++++++++++ lib/main.dart | 2 +- lib/pages/store.dart | 54 ++++---- lib/widgets/fai_settings_dialog.dart | 21 ++++ pubspec.yaml | 2 +- 5 files changed, 227 insertions(+), 30 deletions(-) create mode 100644 docs/landing-page-design.md diff --git a/docs/landing-page-design.md b/docs/landing-page-design.md new file mode 100644 index 0000000..39eee96 --- /dev/null +++ b/docs/landing-page-design.md @@ -0,0 +1,178 @@ +# Welcome / Landing-page design + +Status: **Design draft.** Not built yet. + +## Why we need it + +The Today-Hero in the Store mixed two genres of editorial +content: +- **Operator-actionable**: "DeepWiki is two clicks away", + "text.summarize is in the grid", "try the extract→summarize + flow". These belong in the Store carousel — they point at + store / federation items the operator can act on. +- **Architecture education**: "every module is sandboxed", + "the audit log is hash-chained", "the hub fits in one + binary, no Docker". These are *what F∆I is* — they belong + on a Welcome page that introduces the platform on first + launch and stays available afterwards as a reference. + +The operator feedback that made this distinction explicit: + +> "interessante store einträge oder auch mcp etc zeigen und +> nicht einfach etwas übers system. Stattdessen brauchen wir +> als erstes im system eine art landing page die genau das +> macht und auch zu doku verlinkt, aber diese auch in das +> system optisch integriert und nicht einfach nach außen +> verlinkt." + +The architectural-education stories already left the Store +carousel in the same release. The Welcome page is where they +land. + +## Page shape + +A new top-level Studio surface, accessed from the sidebar +above Doctor. First-launch operators land here automatically; +returning operators can come back any time. + +``` +┌──────────────────────────────────────────────────────────┐ +│ AppBar: Welcome │ +├──────────────────────────────────────────────────────────┤ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ HERO │ │ +│ │ F∆I Platform │ │ +│ │ Deterministic workflow engine for AI-assisted │ │ +│ │ document processing in regulated environments. │ │ +│ └────────────────────────────────────────────────────┘ │ +│ │ +│ ┌──── Three-pillar row (Hub / Module / Flow) ────┐ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ +│ │ │ HUB │ │ MODULE │ │ FLOW │ │ │ +│ │ │ icon │ │ icon │ │ icon │ │ │ +│ │ │ One bin- │ │ Does one │ │ YAML │ │ │ +│ │ │ ary that │ │ thing, │ │ chains │ │ │ +│ │ │ runs the │ │ sandboxed│ │ modules │ │ │ +│ │ │ flows │ │ WASM │ │ together │ │ │ +│ │ └──────────┘ └──────────┘ └──────────┘ │ │ +│ └────────────────────────────────────────────────┘ │ +│ │ +│ ┌──── Trust posture ─────────────────────────────┐ │ +│ │ 🛡 Sandbox by default │ │ +│ │ Every module declares fs / net / env perms; │ │ +│ │ the hub enforces them. Click "Doctor → │ │ +│ │ Modules" to see live permissions per module.│ │ +│ │ │ │ +│ │ ⛓ Tamper-evident audit log │ │ +│ │ Every event hash-chained; Doctor verifies │ │ +│ │ end-to-end on every load. WORM-1 promise. │ │ +│ │ │ │ +│ │ ✈ Air-gap ready │ │ +│ │ One binary, SQLite state, no Docker, no │ │ +│ │ external services. Once a module is │ │ +│ │ installed, the flow runs without further │ │ +│ │ network access. │ │ +│ └────────────────────────────────────────────────┘ │ +│ │ +│ ┌──── Getting-started checklist ─────────────────┐ │ +│ │ ☐ Configure System AI (Settings → System AI) │ │ +│ │ ☐ Add a public MCP source (Store → DeepWiki) │ │ +│ │ ☐ Install your first text module │ │ +│ │ ☐ Run the extract → summarize sample flow │ │ +│ └────────────────────────────────────────────────┘ │ +│ │ +│ ┌──── Documentation, embedded ───────────────────┐ │ +│ │ Architecture map · Module experience · │ │ +│ │ Flow composition · Compliance posture │ │ +│ │ → click opens an in-app reader, not a browser │ │ +│ └────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────┘ +``` + +## Optical integration of docs + +The user constraint: + +> "diese auch in das system optisch integriert und nicht +> einfach nach außen verlinkt" + +So clicking a documentation card does **not** open a browser. +Instead a `_DocReaderSheet` (modal bottom-sheet, same shape as +`_StoreDetailSheet`) renders the markdown inline using the +existing `flutter_markdown` package — same one the Store uses +for module READMEs. The doc source is bundled with Studio as +`assets/docs/*.md`, kept in sync via a build step that copies +the relevant files from `fai_platform/docs/`. + +Pros: +- Air-gap-tauglich (no network call to read docs) +- Operator stays in Studio context — Cmd-Tab to a browser + breaks flow +- Same look-and-feel as module-doc rendering +- Bilingual: each doc has a `_de.md` sibling next to `.md` + +Cons / tradeoffs: +- Studio binary grows by the size of bundled docs (a few + hundred KB) +- Doc-update lifecycle: a Studio release ships a fixed + snapshot. Live-fetched-from-hub is a Phase 1 question. + +## Getting-started checklist + +Stateful — the page tracks which items the operator has +completed and persists state to `~/.fai/studio/onboarding.json` +(same shape as theme/locale persistence in `hub.dart`). Each +checkmark is computed: + +- ✓ System AI configured: `HubService.systemAiStatus().enabled` +- ✓ Public MCP source added: any + `mcp..` in store +- ✓ First text module installed: any installed module + whose name starts with `text.` +- ✓ Sample flow run: any `flow.completed` event in audit + log naming `extract-summarize.yaml` (or any flow that + uses both `text.extract` and `text.summarize`) + +Operator can dismiss the checklist once everything is checked. + +## Sidebar placement + +The Welcome page becomes the new sidebar default — index 0, +above Doctor. First-launch experience hits it; afterward +operators most often go to Store / Audit, but the home icon +stays one click away. + +`labelOf(context)` for the Welcome page returns +`l.navWelcome` ("Welcome" / "Willkommen"). New ARB keys: +`navWelcome`, plus the section-header strings (already mostly +covered by existing `doctorChain*` / `doctorPathLog` keys +that we can reference from the doc-card descriptions). + +## Implementation phases + +Big enough that a single commit doesn't make sense. Three +phased PRs: + +1. **Phase A** — sidebar + scaffolding + hero + three-pillar + row + trust-posture row. No docs yet, just the static + content with hard-coded strings (with i18n). Lands + without the embedded-doc reader. +2. **Phase B** — bundled markdown docs + `_DocReaderSheet`. + Pick 4-5 source docs from `fai_platform/docs/architecture/` + that map to operator concerns (security, host-services, + flow-experience, compliance-gaps). Add a build script that + syncs them into `assets/docs/`. +3. **Phase C** — getting-started checklist with computed + state. Adds the persistence layer + the audit-log + inspection helper. + +## Out of scope + +- A "tutorial mode" that walks the operator through the + steps interactively. Editorial checklist + concise + explanations is enough for v1; tutorial state is a Phase + 1+ idea. +- Live-fetched docs from `hub.fetch_doc(...)`. Air-gap + posture requires bundled docs. +- Per-deployment branding (custom logo / color). Not a + KRITIS need. diff --git a/lib/main.dart b/lib/main.dart index e06083d..edb7a09 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -25,7 +25,7 @@ import 'widgets/widgets.dart'; /// Studio's own build version. Bump on every UI commit so the /// running app self-identifies — visible in the sidebar header /// and quick-glance proof that you're seeing the current build. -const String kStudioVersion = '0.35.0'; +const String kStudioVersion = '0.36.0'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); diff --git a/lib/pages/store.dart b/lib/pages/store.dart index 69c856d..2e39942 100644 --- a/lib/pages/store.dart +++ b/lib/pages/store.dart @@ -2802,6 +2802,12 @@ class _ScreenshotPlaceholder extends StatelessWidget { /// recompiling Studio — see docs/today-pipeline.md. When the /// operator accepts a story, that single story becomes the /// active item and the carousel collapses to a single slide. +/// +/// **Editorial discipline:** every entry here points at a +/// concrete store / federation item the operator can act on, +/// not abstract platform philosophy. Architecture education +/// (sandbox model, hash-chained audit, air-gap posture) lives +/// on the dedicated Welcome page, not in the store carousel. const List _kFallbackTodayStories = [ TodayStoryData( badgeEn: 'PUBLIC SOURCES', @@ -2818,45 +2824,31 @@ const List _kFallbackTodayStories = [ cta: TodayCta.openSettings, ), TodayStoryData( - badgeEn: 'SANDBOX BY DEFAULT', - badgeDe: 'SANDBOX VON ANFANG AN', - titleEn: 'Every module declares what it can touch', - titleDe: 'Jedes Modul deklariert, worauf es zugreifen darf', + badgeEn: 'TEXT MODULES', + badgeDe: 'TEXT-MODULE', + titleEn: 'Three text modules already in the store', + titleDe: 'Drei Text-Module sind schon im Store', bodyEn: - 'F∆I modules ship with an explicit permission list — network endpoints, files, environment variables. The hub enforces it; nothing reaches outside the sandbox without the operator approving it. Click any installed module to see exactly which permissions it asked for.', + 'text.extract pulls plain text from PDFs and DOCX. text.summarize turns long material into a short briefing via your configured System AI. text.translate moves text between languages. All three install in one click from the grid below — search for "text" to find them.', bodyDe: - 'F∆I-Module bringen eine explizite Berechtigungsliste mit — Netzwerk-Endpunkte, Dateien, Umgebungsvariablen. Der Hub setzt sie durch; ohne Operator-Freigabe verlässt nichts die Sandbox. Klicke ein installiertes Modul, um seine genauen Berechtigungen zu sehen.', + 'text.extract holt Plaintext aus PDFs und DOCX. text.summarize verdichtet lange Texte über die konfigurierte System-AI zu einem Briefing. text.translate übersetzt zwischen Sprachen. Alle drei lassen sich aus dem Grid unten mit einem Klick installieren — suche nach „text".', ctaLabelEn: '', ctaLabelDe: '', - icon: Icons.shield_outlined, + icon: Icons.menu_book_outlined, cta: TodayCta.none, ), TodayStoryData( - badgeEn: 'TAMPER-EVIDENT AUDIT', - badgeDe: 'MANIPULATIONSSICHERES AUDIT', - titleEn: 'Every event leaves a hash-chained trace', - titleDe: 'Jedes Ereignis hinterlässt eine hash-verkettete Spur', + badgeEn: 'SAMPLE FLOW', + badgeDe: 'BEISPIEL-FLOW', + titleEn: 'Try the extract → summarize flow', + titleDe: 'Probier den Extract → Summarize-Flow', bodyEn: - 'Flow runs, install / uninstall actions, approval decisions — all of them write into a hash-chained audit log. The Doctor page verifies the chain end-to-end on every load; if a single byte gets touched, you see it. Audit-grade by construction, not by policy.', + 'The bundled `flows/extract-summarize.yaml` chains text.extract into text.summarize so a PDF in one end produces a short briefing at the other. Open the Flows tab, hit Run, point it at any document — the result lands as plain text plus an audit-log trail of which modules ran with which inputs.', bodyDe: - 'Flow-Läufe, Installationen und Deinstallationen, Freigabe-Entscheidungen — alles wird in ein hash-verkettetes Audit-Log geschrieben. Die Diagnose-Seite verifiziert die Kette bei jedem Laden komplett; ein verändertes Byte ist sofort sichtbar. Audit-tauglich qua Konstruktion, nicht qua Policy.', + 'Der mitgelieferte Flow `flows/extract-summarize.yaml` reicht text.extract an text.summarize weiter — vorne kommt ein PDF rein, hinten kommt ein kurzes Briefing raus. Flows-Tab öffnen, Run klicken, beliebiges Dokument auswählen; das Ergebnis ist Klartext plus Audit-Spur welcher Modul-Schritt mit welchen Eingaben gelaufen ist.', ctaLabelEn: '', ctaLabelDe: '', - icon: Icons.shield_outlined, - cta: TodayCta.none, - ), - TodayStoryData( - badgeEn: 'AIR-GAP READY', - badgeDe: 'AIR-GAP-TAUGLICH', - titleEn: 'One binary, no Docker, no external services', - titleDe: 'Ein Binary, kein Docker, keine externen Dienste', - bodyEn: - 'The whole hub fits in a single binary that runs on Linux, macOS and Windows. SQLite for state, no message broker, no background services to babysit. Once a module is installed, the flow it powers runs without further network access — perfect for regulated environments where outbound calls have to be justified.', - bodyDe: - 'Der gesamte Hub steckt in einem einzigen Binary für Linux, macOS und Windows. SQLite für den State, kein Message-Broker, keine Hintergrunddienste, die gepflegt werden müssen. Sobald ein Modul installiert ist, läuft der Flow, der es nutzt, ohne weiteren Netzwerkzugriff — ideal für regulierte Umgebungen, in denen jeder ausgehende Call begründet werden muss.', - ctaLabelEn: '', - ctaLabelDe: '', - icon: Icons.rocket_launch, + icon: Icons.account_tree_outlined, cta: TodayCta.none, ), ]; @@ -2917,6 +2909,12 @@ class _StoreTodayHero extends StatelessWidget { final ctaLabel = isDe ? story.ctaLabelDe : story.ctaLabelEn; return Container( width: double.infinity, + // Min-height keeps the carousel slides at a consistent + // size so the surrounding layout doesn't jump when the + // operator clicks the prev/next chevrons. Sized to fit + // the longest story (≈ 6 body lines + title + CTA + chip + // row) without leaving empty space on shorter slides. + constraints: const BoxConstraints(minHeight: 240), padding: const EdgeInsets.fromLTRB( FaiSpace.xl, FaiSpace.lg, diff --git a/lib/widgets/fai_settings_dialog.dart b/lib/widgets/fai_settings_dialog.dart index 2a77262..8b659e4 100644 --- a/lib/widgets/fai_settings_dialog.dart +++ b/lib/widgets/fai_settings_dialog.dart @@ -1403,6 +1403,27 @@ class _McpSuggestion { } const _kMcpSuggestions = <_McpSuggestion>[ + // ── HTTPS / streamable-HTTP servers (no Node, no API key) ── + // Same set Studio promotes as one-click cards in the Today + // hero — listed here so the operator finds them even after + // the hero gets dismissed. + _McpSuggestion( + name: 'deepwiki', + icon: Icons.menu_book_outlined, + endpoint: 'https://mcp.deepwiki.com/mcp', + apiKeyEnv: '', + description: + 'Public HTTPS — GitHub repo documentation search (AI-powered). No Node, no API key.', + ), + _McpSuggestion( + name: 'semgrep', + icon: Icons.security, + endpoint: 'https://mcp.semgrep.ai/mcp', + apiKeyEnv: '', + description: + 'Public HTTPS — security scanning for code vulnerabilities. No Node, no API key.', + ), + // ── stdio servers (require Node + npx) ─────────────────── _McpSuggestion( name: 'filesystem', icon: Icons.folder_outlined, diff --git a/pubspec.yaml b/pubspec.yaml index 8e4496b..a23b5a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: fai_studio description: "F∆I Studio — desktop GUI for the F∆I hub" publish_to: 'none' -version: 0.35.0 +version: 0.36.0 environment: sdk: ^3.11.0-200.1.beta