Commit graph

25 commits

Author SHA1 Message Date
flemming-it
36a8252f67 test(studio): lock sidebar destination Y-stability on rail expansion
Adds a widget test asserting each destination icon keeps its Y position
whether the rail is collapsed or expanded — a recurring regression. To
drive expansion without a hover gesture (which trips RenderFlex overflow
mid-transition), a test-only startSidebarExpanded flag threads
StudioApp -> StudioShell -> _Sidebar (controller starts at 1.0, hover
disabled); _SidebarItems get stable ValueKeys. analyze clean; new test
and the existing smoke test pass.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-19 21:30:02 +02:00
flemming-it
891acd2ba2 refactor: rename internal Fai* design system + fai_ helpers to chain
Some checks failed
Security / Security check (push) Failing after 2s
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>
2026-06-16 17:53:17 +02:00
flemming-it
68d23ab7dd refactor: rename app fai_studio -> chain_studio
Some checks are pending
Security / Security check (push) Waiting to run
Studio is the Ch∆In product's GUI, not a F∆I-vendor app. Rename the
Flutter package, all package: imports, and the build identity across
platforms: linux/windows CMake BINARY_NAME + project, Windows Runner.rc
fields, macOS PRODUCT_NAME / bundle id (ai.flemming.chain.chainStudio) /
.app + scheme BuildableName. Update the client-SDK + flow-editor deps to
their renamed chain_* packages (path + git URL). Company/copyright fields
now read Flemming.AI. flutter analyze: clean.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-16 17:48:11 +02:00
flemming-it
7ff4fda2a3 refactor(brand): rename F∆I -> Ch∆In + hub binary fai -> chain
Some checks failed
Security / Security check (push) Failing after 2s
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>
2026-06-15 16:22:22 +02:00
flemming-it
971196518c feat(studio): friendlyError catches hub-specific patterns
Some checks failed
Security / Security check (push) Failing after 1s
Generic gRPC-code mapping was right but not specific enough.
A flow failing with an approval-timeout used to land on
'Deadline exceeded — try again later'; now it reads 'Freigabe-
Timeout abgelaufen — entweder timeout_seconds erhöhen oder
den Reviewer informieren.'

New pattern matchers in _matchHubPattern, runs before the
gRPC-code switch. Six FlowExecutionError shapes covered:

  - approval rejected ("rejected by")
  - approval timeout
  - output too large ("exceeding the X MB cap")
  - host service not declared
  - missing value reference
  - MCP endpoint unreachable
  - capability not installed (NotFound fallback)

Every match comes with a localised hint pointing at the
concrete fix path (audit log / timeout config / Integrations
panel / Text-tab Fix button).

Five new tests pin the matchers — would catch a silent
regression when the hub renames a variant Display string.
All 11 friendly_error tests + 19 Studio tests green.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-09 02:01:36 +02:00
flemming-it
d9dca60cfb test(studio): seven FaiLog cases + visibleForTesting path override
Some checks failed
Security / Security check (push) Failing after 1s
FaiLog used to compute its destination directly from HOME, which
made it impossible to test without scribbling on the operator's
real ~/.fai/logs/. Adds a static testPathOverride seam tagged
@visibleForTesting so the singleton can be redirected at a per-
test temp file.

The new test/fai_log_test.dart covers:

  - append() writes one JSON-shaped line per event
  - context field round-trips when supplied
  - tail() returns oldest-first
  - tail(maxLines:) caps and keeps the newest entries
  - rotation moves the previous log to .log.1 past 256 KiB
  - path getter honours the override
  - writes to an impossible path do not throw — best-effort
    contract that protects the UI from log-write failures

Production behaviour is unchanged: when the override is null
the path getter still computes from HOME/USERPROFILE.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-06-04 02:43:06 +02:00
flemming-it
0183771689 feat(studio): swap built-in flow editor for fai_studio_flow_editor package (v0.51.0)
Some checks failed
Security / Security check (push) Failing after 2s
The flow editor was internal to Studio (lib/pages/flow_editor.dart).
Per Stefan's review feedback ("austauschbar wäre schöner"),
extract it into its own Forgejo repo so the host can swap
the implementation without touching Studio.

New repo: https://git.flemming.ai/fai/studio-flow-editor

Studio's pubspec.yaml now references the package by git URL:

  dependencies:
    fai_studio_flow_editor:
      git:
        url: https://git.flemming.ai/fai/studio-flow-editor
        ref: main

To swap the editor:
  1. Fork (or write a new) fai/studio-flow-editor.
  2. Keep the FlowEditorPage(initialFlowName, locale, onRun)
     constructor signature — the stable host contract.
  3. Point the pubspec at your fork.
  4. Rebuild Studio.

Adapter pattern: _FlowEditorAdapter in main.dart resolves the
package's runtime dependencies (locale via Localizations,
onRun via HubService) from the BuildContext, then constructs
the package's FlowEditorPage. Same pattern in flows.dart for
the pencil → editor route push, so a future operator-side
locale switch propagates correctly.

The package brings its own copies of FaiSpace tokens, minimal
FaiEmptyState/FaiErrorBox widgets, and an inline EN+DE l10n
table — accepting a small amount of visual drift in exchange
for true package independence. flutter_code_editor +
highlight move from Studio's pubspec to the package's.

Deleted:
  lib/pages/flow_editor.dart  → package's lib/src/flow_editor_page.dart
  test/flow_editor_test.dart  → package's test/ (next commit there)

Bumped:
  pubspec.yaml version 0.50.0 → 0.51.0
  main.dart kStudioVersion 0.50.0 → 0.51.0
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 14:36:54 +02:00
flemming-it
e522267ec1 feat(studio): pencil-into-editor + back button + collapsible rail
Some checks failed
Security / Security check (push) Failing after 2s
Three operator-visible improvements per Stefan's review of
the v0.50.0 editor.

1. Pencil icon on Flows page now opens Studio's own flow
   editor (route push) preloaded with the selected flow,
   replacing the previous "open in OS default editor"
   (SystemActions.openInOs) behaviour. The editor's AppBar
   gains a back arrow when reached via route push; reaching
   the editor via the nav rail leaves it bare. Tooltip
   string updated in EN + DE.

2. New FlowEditorPage `initialFlowName` parameter. When set,
   the page loads that flow on first frame (via post-frame
   callback so the BuildContext is mounted before
   _openByName runs). When null (the nav-rail path), the
   editor opens to its empty state as before.

3. Sidebar (_Sidebar) is now collapsed-by-default: shows
   just icons in a 72px-wide rail with per-destination
   tooltips. Hovering the rail expands it to 220px (the
   old width) with brand-mark + labels + the full footer
   row (theme toggle, language toggle, clock, settings).
   Collapsed footer shows the settings icon only. Brand-
   mark (FaiDeltaMark) stays visible in both states so the
   live/idle status dot is always glanceable.

Side-effect: SystemActions import in flows.dart is no
longer needed (the pencil no longer shells out) — removed.

widget_test.dart: dropped the per-destination Text-presence
asserts since labels are now Tooltips when collapsed.
Hover-expand testing triggers RenderFlex-overflow mid-
animation in widget tests (the AnimatedContainer's width
transitions through a constraint slimmer than the Row's
intrinsic min). The booting-without-throwing assertion
remains; per-destination presence stays in the per-page
test suites.

Both arb files updated with the new strings (navFlowEditor
already existed; added flowEditorBackTooltip + retouched
flowsOpenInEditorTooltip).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 12:04:05 +02:00
flemming-it
bb793e64a9 feat(studio): inline flow editor — YAML highlighting + Run (v0.50.0)
Some checks failed
Security / Security check (push) Failing after 1s
New top-level destination "Editor" (Cmd+5) ships as Studio's
fifth surface. The editor reads + writes flow YAML directly
under ~/.fai/data/flows/ via dart:io — the hub picks up the
changes on the next listFlows / runSavedFlow call.

Layout: two-pane shell. Left (240 px) is the file list; right
flexes to the code pane and an optional results column when
Run produces output. Top toolbar exposes:
  * filename + dirty-mark
  * New flow (scaffolds from a debug.echo template)
  * Save (writes the active file to disk)
  * Run (saves first if dirty, calls
    HubService.runSavedFlow, surfaces typed FlowOutputs in
    a side panel)
  * Refresh

YAML highlighting via flutter_code_editor + the highlight
package's yaml language. Lightweight style map mapping the
five token classes that actually appear in flow YAML
(attr / string / number / comment / subst for the
${{ ... }} template syntax) to FaiTheme colors — keeps the
editor visually consistent with the rest of Studio.

New-flow naming uses a FilteringTextInputFormatter that
restricts the name to [a-z0-9_-]. A "name already exists"
SnackBar surfaces the conflict instead of silently
overwriting.

Bilingual strings shipped (en.arb + de.arb) for every
operator-facing string: toolbar buttons, dialogs, empty
states, file-exists error, run-output header.

New deps:
  * flutter_code_editor ^0.3.5
  * highlight (transitive — pinned as direct so the
    yaml-language import has its declared dependency).

Smoke-test (test/flow_editor_test.dart) pumps the page and
asserts the empty-state + toolbar render without throwing
on hosts that don't have ~/.fai/data/flows yet. The full
file-list + open-on-tap flow needs a writable HOME override
which dart:io's read-only Platform.environment doesn't allow
inside a test isolate — that path lives in the integration
suite as a follow-up.

Version bumps:
  * pubspec.yaml: 0.49.1 → 0.50.0
  * main.dart kStudioVersion: 0.42.0 → 0.50.0 (had drifted
    behind pubspec; brought back into sync as part of this
    bump)

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 01:10:05 +02:00
flemming-it
570c38b238 fix(welcome): structured doc-load error + Forgejo fallback link
Some checks failed
Security / Security check (push) Failing after 2s
The doc-reader bottom-sheet in welcome.dart hits rootBundle
to load assets/docs/<slug>[_de].md. When the asset isn't in
the bundle (e.g. the binary on disk predates the
flutter_markdown_plus migration in 69b54629 or an in-progress
asset reorganisation), the FutureBuilder's error branch dumps
the raw PlatformException text into FaiErrorBox with no
hint at what the operator should do.

Switch the throw to a structured `_DocReaderError` that
carries:
  - the slug, locale, and both attempted asset paths
  - the underlying error message
  - a precomputed `forgejoUrl` pointing at the same doc on
    the platform repo's main branch

The error UI gains a TextButton.icon below FaiErrorBox that
opens the Forgejo copy in the OS browser via the existing
SystemActions.openInOs path — operators always have a
working out, even when the local bundle is broken.

The error toString() also surfaces "rebuild Studio" as the
top suggestion, since the most common cause for asset
mismatches is a stale binary running against a newer source
tree.

New regression test (test/load_docs_test.dart) pumps the
exact code path the sheet uses (rootBundle.loadString +
Markdown with FaiTheme.markdownStyle) against all eight asset
paths (architecture/security/audit/flows × en/de). Catches a
future bundle drift before an operator notices.

No behavioural change on the happy path. Both the rendered
markdown widget and the closed-sheet animation are unchanged.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-30 00:54:46 +02:00
flemming-it
a5bd51de33 chore(studio): switch to fai_client_sdk (renamed from fai_dart_sdk)
Some checks failed
Security / Security check (push) Failing after 1s
The Dart SDK package was renamed package: fai_dart_sdk →
fai_client_sdk and the dir + Forgejo repo got a -dart language
suffix per the three SDK families convention in
fai/platform/docs/architecture/sdks.md. Updates the path
dep, dep name, every `package:fai_dart_sdk/...` import, and
the one comment that named the SDK.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-26 13:46:32 +02:00
flemming-it
4a9bc93d15 chore(studio): point plugin test at fai_plugins/ instead of fai_modules/
Studio plugins moved to a dedicated fai_plugins/ workspace
directory; integration test path updated to match.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-26 01:00:36 +02:00
flemming-it
a5bcde6446 feat(studio): HubService.invokePluginTheme + integration test
Wraps the new InvokePluginTheme RPC and ships an end-to-end
test that loads studio-theme-solarized into a fresh
HubFixture and asserts the light/dark schemes round-trip.

Signed-off-by: flemming-it <sf@flemming.it>
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-25 21:49:40 +02:00
flemming-it
21c2a0f411 chore(studio): RadioGroup migration + second integration test
Two clean-ups:

- `fai_system_ai_editor.dart` uses the new `RadioGroup<T>`
  ancestor pattern Material 3 introduced after Flutter 3.32.
  Same UX (three radios for off/redacted/full), no
  deprecation warnings, `flutter analyze` is now zero-issue
  for the whole project.
- New `install_install_planned_test.dart` asserts that
  installing a `planned` seed entry surfaces a clear,
  human-readable error class (no panic, no silent success).
  Locks in the failed-precondition path Studio's
  friendly-error mapper depends on.

Integration test suite is now 3 scenarios:
  - capabilities_test.dart: system.approval+kind tag
  - install_install_planned_test.dart: planned-install error

All run against a fresh `fai serve` subprocess via
HubFixture, ~2 s total.

Signed-off-by: flemming-it <sf@flemming.it>
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-25 14:05:43 +02:00
flemming-it
69b54629d3 chore(studio): flutter_markdown_plus migration + integration-test scaffold
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>
2026-05-25 13:22:04 +02:00
flemming-it
c11461b0f9 feat(studio): trust pass — friendly errors + store clarity + MCP i18n
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>
2026-05-25 12:36:14 +02:00
flemming-it
3a7d0e74ad feat(studio): Welcome page — Phase A scaffolding (v0.37.0)
First slice of the new sidebar destination outlined in
docs/landing-page-design.md. Static content only — embedded
docs (Phase B) and the live getting-started checklist
(Phase C) follow.

Phase A:

- New `WelcomePage` at `lib/pages/welcome.dart`, registered
  as sidebar slot 0 above Doctor. Default selectedIndex stays
  0, so a fresh launch lands on Welcome.
- Hero card: gradient backdrop matching the Today-Hero in the
  store, F∆I Platform headline, subtitle taken from CLAUDE.md
  ("deterministic workflow engine for AI-assisted document
  processing in regulated environments").
- Three-pillar row "Hub / Module / Flow" — operator-readable
  prose, not architecture-doc dense. Stacks to a column under
  640 dp window width so card text never gets cropped.
- Trust-posture deck — "Sandbox by default", "Tamper-evident
  audit log", "Air-gap ready". Same content that used to
  rotate as carousel slides in the store; reading them as a
  single deck with full prose works better than rotating
  through fragments.
- AppBar follows the same `titleSpacing: FaiSpace.xl`
  alignment as the Store so the title sits flush with the
  body padding.
- 13 new ARB keys for the page content (EN + DE).

Smoke test now expects "Welcome" in the navigation rail; the
existing `Doctor / Store / Flows / Audit / Approvals`
expectations stay unchanged.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-09 00:17:54 +02:00
flemming-it
a7e2eb101a feat(studio): drop Modules tab, fold its content into Store detail (v0.35.0)
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>
2026-05-08 14:57:51 +02:00
flemming-it
ee83eb851a feat(studio): app-wide i18n via flutter_localizations (v0.24.0)
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>
2026-05-08 01:19:32 +02:00
flemming-it
060c8003d5 feat(studio): operator-mode pass — Store + audit detail + channel switcher (v0.10.0)
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>
2026-05-07 11:54:31 +02:00
flemming-it
753db4fc60 feat(ui): Flows page (5th destination) + keyboard shortcuts
Two operator-DX additions:

1. **Flows** is the new third destination in the sidebar.
   Lists saved flows from the hub via the new HubAdmin
   ListFlows RPC. Each row has a Run button that opens an
   input dialog (key=value pairs, one per line, all values
   sent as text payloads — the binary-input case stays in
   `fai run` CLI). Flow runs in a non-dismissable progress
   dialog; output is shown per-key with monospace
   selectable text. Errors render with the exception detail
   for diagnosis.

2. **Keyboard shortcuts** at the shell level:
   - Cmd+1 / Cmd+2 / Cmd+3 / Cmd+4 / Cmd+5 jump to the
     matching destination (Doctor / Modules / Flows / Audit
     / Approvals).
   - Cmd+, opens the Settings dialog (macOS convention).

   Implemented via Flutter's Shortcuts/Actions/Intent triple
   so the bindings are discoverable in IDE devtools and
   composable with platform-specific overrides later.

Bumps fai_studio 0.7.3 -> 0.8.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-05 23:35:48 +02:00
flemming-it
671194c105 feat(ui): theme-mode toggle (system / light / dark) with persistence
Three-way switch in the sidebar footer next to the settings
gear. Cycles system → light → dark → system. Choice persists
via shared_preferences across app launches; restored before the
first frame so there's no theme flicker on startup.

The icon changes per state (auto / sun / moon) and the tooltip
spells it out so the cycling behaviour is discoverable.

- StudioApp is now stateful; exposes StudioApp.of(context) to
  let descendants flip the theme without prop-drilling.
- ThemeModeValue enum lives in data/hub.dart so the persistence
  layer stays free of flutter/material imports.

Bumps fai_studio 0.6.0 -> 0.7.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-05 22:19:13 +02:00
flemming-it
4352ecc28c feat(ui): add Doctor page (4th destination) — full health snapshot
Composes the platform's health picture in one view:
- Summary strip: 4 stat tiles for modules / approvals / audit
  chain / services with semantic accent colour.
- Event-log panel: WORM-1 badge + chain status, points to the
  first tampered event id when integrity fails.
- Modules & approvals panel: counts plus an "attention" pill
  when approvals are pending.
- Services panel: declared services from operator config or a
  helpful empty-state pointing at ~/.fai/config.yaml.

Backed by the new HubAdmin RPCs (VerifyEventChain, ListServices)
plus the existing ListCapabilities and ListApprovals. One
HubService.doctor() call fans out to all four in parallel.

Doctor is the new first destination in the sidebar — that's the
page an operator wants to land on after starting the app.

Bumps fai_studio 0.3.0 -> 0.4.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-05 22:09:49 +02:00
flemming-it
127c497f73 feat: live gRPC pages — drop mock data, talk to the real hub
Studio's three MVP pages now pull live data from a running hub
via fai_dart_sdk. Mock fixture removed.

- data/hub.dart: HubService singleton wraps fai_dart_sdk's
  HubClient and exposes UI-friendly types (ModuleSummary,
  AuditEvent, PendingApproval) so pages don't import protobuf.
- pages/modules.dart: FutureBuilder against listModules,
  groups CapabilityEntry rows by module, retry-on-error UI.
- pages/audit.dart: 2s polling Timer, status bar shows
  "live (polling 2s)" or "disconnected — <error>".
- pages/approvals.dart: live listApprovals, Approve sends
  DecideApproval(APPROVE), Reject opens a reason dialog and
  sends DecideApproval(REJECT). Both refresh after success.
- Connection-error states across all pages: cloud_off icon +
  hint to start `fai serve` + Retry button.

When no hub is running the pages render their disconnected
state instead of crashing. When a hub is running, the data is
real — no more mock fixture.

Bumps fai_studio 0.1.0 -> 0.2.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-05 16:27:54 +02:00
flemming-it
a173eec1d0 feat: F∆I Studio MVP scaffold (Tier-2 desktop GUI)
Initial scaffold for the F∆I Platform Tier-2 generic GUI client.
Flutter Desktop (macOS, Linux, Windows). Three MVP pages with
mock data, sharing one navigation shell:

- Modules — installed modules with capabilities, declared
  permissions and required services.
- Audit — event-stream view with type filter and tone-coded
  rows (started / completed / failed).
- Approvals — pending system.approval@^0 reviews with prompt,
  payload preview, and approve/reject buttons.

Live gRPC connection arrives in the next iteration via
fai_dart_sdk (sibling repo, currently a typed stub).

Future Forgejo path: fai/studio. Local layout matches existing
fai_platform/ convention.

Background: see docs/architecture/client.md in the platform
repo. The tier-2 client was previously called "Stage" — renamed
to "Studio" on 2026-05-05 to avoid confusion with
"staging environment".

flutter analyze: clean. flutter test: 2/2.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-05 14:19:39 +02:00