feat(studio): carousel size lock, store-actionable today stories, HTTPS MCP suggestions in settings (v0.36.0)
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>
This commit is contained in:
parent
a7e2eb101a
commit
711436b71d
5 changed files with 227 additions and 30 deletions
178
docs/landing-page-design.md
Normal file
178
docs/landing-page-design.md
Normal file
|
|
@ -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.<server>.<tool>` 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.
|
||||||
|
|
@ -25,7 +25,7 @@ import 'widgets/widgets.dart';
|
||||||
/// Studio's own build version. Bump on every UI commit so the
|
/// Studio's own build version. Bump on every UI commit so the
|
||||||
/// running app self-identifies — visible in the sidebar header
|
/// running app self-identifies — visible in the sidebar header
|
||||||
/// and quick-glance proof that you're seeing the current build.
|
/// and quick-glance proof that you're seeing the current build.
|
||||||
const String kStudioVersion = '0.35.0';
|
const String kStudioVersion = '0.36.0';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
|
||||||
|
|
@ -2802,6 +2802,12 @@ class _ScreenshotPlaceholder extends StatelessWidget {
|
||||||
/// recompiling Studio — see docs/today-pipeline.md. When the
|
/// recompiling Studio — see docs/today-pipeline.md. When the
|
||||||
/// operator accepts a story, that single story becomes the
|
/// operator accepts a story, that single story becomes the
|
||||||
/// active item and the carousel collapses to a single slide.
|
/// 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<TodayStoryData> _kFallbackTodayStories = <TodayStoryData>[
|
const List<TodayStoryData> _kFallbackTodayStories = <TodayStoryData>[
|
||||||
TodayStoryData(
|
TodayStoryData(
|
||||||
badgeEn: 'PUBLIC SOURCES',
|
badgeEn: 'PUBLIC SOURCES',
|
||||||
|
|
@ -2818,45 +2824,31 @@ const List<TodayStoryData> _kFallbackTodayStories = <TodayStoryData>[
|
||||||
cta: TodayCta.openSettings,
|
cta: TodayCta.openSettings,
|
||||||
),
|
),
|
||||||
TodayStoryData(
|
TodayStoryData(
|
||||||
badgeEn: 'SANDBOX BY DEFAULT',
|
badgeEn: 'TEXT MODULES',
|
||||||
badgeDe: 'SANDBOX VON ANFANG AN',
|
badgeDe: 'TEXT-MODULE',
|
||||||
titleEn: 'Every module declares what it can touch',
|
titleEn: 'Three text modules already in the store',
|
||||||
titleDe: 'Jedes Modul deklariert, worauf es zugreifen darf',
|
titleDe: 'Drei Text-Module sind schon im Store',
|
||||||
bodyEn:
|
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:
|
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: '',
|
ctaLabelEn: '',
|
||||||
ctaLabelDe: '',
|
ctaLabelDe: '',
|
||||||
icon: Icons.shield_outlined,
|
icon: Icons.menu_book_outlined,
|
||||||
cta: TodayCta.none,
|
cta: TodayCta.none,
|
||||||
),
|
),
|
||||||
TodayStoryData(
|
TodayStoryData(
|
||||||
badgeEn: 'TAMPER-EVIDENT AUDIT',
|
badgeEn: 'SAMPLE FLOW',
|
||||||
badgeDe: 'MANIPULATIONSSICHERES AUDIT',
|
badgeDe: 'BEISPIEL-FLOW',
|
||||||
titleEn: 'Every event leaves a hash-chained trace',
|
titleEn: 'Try the extract → summarize flow',
|
||||||
titleDe: 'Jedes Ereignis hinterlässt eine hash-verkettete Spur',
|
titleDe: 'Probier den Extract → Summarize-Flow',
|
||||||
bodyEn:
|
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:
|
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: '',
|
ctaLabelEn: '',
|
||||||
ctaLabelDe: '',
|
ctaLabelDe: '',
|
||||||
icon: Icons.shield_outlined,
|
icon: Icons.account_tree_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,
|
|
||||||
cta: TodayCta.none,
|
cta: TodayCta.none,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
@ -2917,6 +2909,12 @@ class _StoreTodayHero extends StatelessWidget {
|
||||||
final ctaLabel = isDe ? story.ctaLabelDe : story.ctaLabelEn;
|
final ctaLabel = isDe ? story.ctaLabelDe : story.ctaLabelEn;
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
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(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
FaiSpace.xl,
|
FaiSpace.xl,
|
||||||
FaiSpace.lg,
|
FaiSpace.lg,
|
||||||
|
|
|
||||||
|
|
@ -1403,6 +1403,27 @@ class _McpSuggestion {
|
||||||
}
|
}
|
||||||
|
|
||||||
const _kMcpSuggestions = <_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(
|
_McpSuggestion(
|
||||||
name: 'filesystem',
|
name: 'filesystem',
|
||||||
icon: Icons.folder_outlined,
|
icon: Icons.folder_outlined,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
name: fai_studio
|
name: fai_studio
|
||||||
description: "F∆I Studio — desktop GUI for the F∆I hub"
|
description: "F∆I Studio — desktop GUI for the F∆I hub"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.35.0
|
version: 0.36.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.0-200.1.beta
|
sdk: ^3.11.0-200.1.beta
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue