The detail sheet always answers 'who maintains this?': one
selectable line per maintainer from the store index's new
maintainers list, or an honest 'not specified' when the index
names nobody (StoreMaintainersSection, public for the widget
tests). The install trust gate carries the same fact when
present and stays terse otherwise. DE+EN; dialog-harness proof
captured with the maintainer row.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Even with the honest install badge the hub error stays reachable
(stale store snapshot, race with a store refresh, older hub). The
friendly-error mapper now gives it its own headline plus a hint
naming the three acquisition paths — local module install, adding
the providing store, configuring the MCP/n8n integration — in EN
and DE, with the verbatim hub message kept copyable. Matcher unit
tests EN+DE guard the classification.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The capability set behind the flow editor's Install quick-fix and
the flow list's install badge ingested every store entry's
requiresCapabilities (dependencies, not provided capabilities) and
ignored entry status/kind. Clicking Install on such a capability
ended in the hub's "no store entry for '<name>'" error.
The set now mirrors the hub's install resolver (entry names only,
no planned or federated entries) via installableStoreCapabilities()
with unit tests for both classification states. Unresolvable
capabilities render the editor's 'not in store' state, which
explains the three recovery paths before any click; the editor pin
moves to 0.25.0 (commit-pinned until its tag exists) and the dialog
harness captures the badge states light+dark. Studio 0.78.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The doc-verifier pass over the new trust/exposure surfaces came back
PASS with five improvements, all applied:
- the store policy notice gains a 'Learn more' into the security doc
(the notice named security.require_signatures but not where it
lives)
- 'blocked' disables the trust gate's install button — an active
button contradicted the 'install would be refused' statement right
above it (guard test added)
- the unknown-exposure tooltip now says what the operator can do
(check where the name resolves)
- dead l10n key verifPillUnverified removed (unverified is the
page-level notice, never a card pill)
- stale header comment in install_verification.dart corrected
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The trust-gate dialog replaces its generic 'no per-entry status yet
(alpha)' note with the hub's classified verification statement —
pinned store key / trusted publishers (green), installs without
signature checking (amber), install would be refused (red), bridge
entry (neutral) — via one shared describeInstallVerification mapping.
Against a pre-0.23 hub the field is empty and the old honest wording
stays (pinned by test).
Information architecture: policy-off is a GLOBAL fact, so it appears
as ONE ChainInlineHelp notice above the store grid instead of a
warning pill on every card (card noise); only 'blocked' — a genuine
per-source anomaly — earns a card pill. Doctor's host services show
the hub-classified network reach per endpoint (local only / private
network / publicly reachable with a protect-it hint / reach unknown).
Verified end-to-end against the live dev hub (guide harness): the
wire field arrives, the store page shows exactly one policy notice
and quiet cards; trust-gate variants captured via the dialog
harness. Suite 123 green.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
New ChainInlineHelp (intro strip: what this is + what will happen, with
an optional 'Learn more' into the doc sheet) and ChainFieldHelp /
ChainFieldLabel (a '?' affordance per field). First applied to the
add-satellite dialog, which asked for a bare 'name' with no hint of
what a satellite is or does (usertest): it now leads with a plain
explanation + a federation 'Learn more', and the name field carries a
'?'. Both widgets are quiet by design.
Verified: field_help_test covers the widgets + that the dialog explains
itself; dialog_shots_test.dart (a reusable headed dialog-capture
harness) proved the layout in light + dark. Studio 0.76.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The Federation and Runs help buttons opened the architecture doc:
federation.md/runs.md existed as assets but had no _DocEntry, so
showFaiDoc resolved the unknown slug to _kDocs.first. Register both
(onWelcome: false, so they don't clutter the newcomer grid but are
reachable), split the Welcome grid onto the curated subset, and make
the unknown-slug fallback assert in debug instead of silently opening
the wrong topic.
Guard (no-bugfix-without-a-guard): doc_help_wiring_test.dart scans
lib/ for every showFaiDoc('slug') call and asserts each has a
registered entry AND both assets/docs/<slug>[_de].md files. Exposes
kKnownDocSlugs for the test.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The switcher listed sealed areas by name ('lbs', 'stromnetz') on
any glance or screenshot — but the names themselves often carry
client/mandate identity (usertest security finding). The sealed
section now renders one aggregated row ('2 sealed areas') with a
deliberate 'Show names' reveal per menu opening; selection still
pops the regular s:<slug> value. Settings -> Security gains 'list
sealed areas with their names right away' (WorkspacePrefs,
SidebarPrefs pattern, default off).
The aggregate row wraps to two lines — popup menus cap their
width and action texts must never be truncated (the first cut
showed '1 abgeschotte…' in the proof shot). Guard: switcher tests
cover aggregated-until-reveal and the Settings toggle; the old
direct-listing test now asserts the reveal contract. DE+EN.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The same single-select choice pattern appeared as four widgets:
audit's hover pills, the store's SegmentedButton, the store filter
dialog's ChoiceChips, and the approvals TabBar (usertest finding
#14 / night-log decision 'pill segment as canon'). The audit
pattern is promoted to a shared ChainSegments widget (optional
icons, hover, selected border, button+selected semantics) and all
four sites use it; approvals switches lists via IndexedStack so
both stay alive and switching does not refetch.
Guard per the no-bugfix-without-a-guard rule: widget tests for
selection + semantics, plus a canon sweep that bans
TabBar/TabBarView/TabController/SegmentedButton/ChoiceChip from
lib/ (comments exempt). Deliberately out of scope: the flow
editor's Graph/Text/Run tabs live in the separate editor package.
Studio 0.75.0; guide images regenerated, dark + light verified.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The widget suites used to talk to whatever listens on the real
endpoint — results depended on the operator's machine (a running
hub fed real data into a11y/responsive runs and its gRPC channel
timers caused the historic flake). Hardening round:
- HubService.instance is now injectable (debugSetInstance);
FakeHubService (test/support/fake_hub.dart) answers every member
the pages touch with healthy-empty defaults and scripts per-RPC
failures (UNIMPLEMENTED / UNAVAILABLE / detached gate) through a
GrpcError-shaped fake. Unimplemented members are recorded and
fail the sweep with the exact list.
- state_matrix_test.dart pins the app-wide invariants for every
sidebar page x hub condition: healthy => no unreachable claims
and no raw error text; hub gone => honest unreachable states;
UNIMPLEMENTED => never 'not reachable' while the sidebar shows
connected; detached gate => plain-language feature-off state.
- a11y + responsive sweeps now inject the fake (hermetic); the
6-minute idle-timer drain workaround is gone with the cause.
Real bugs the new sweep caught immediately:
- every data page (store, doctor, audit, approvals, federation)
folded ANY load failure into 'hub not reachable' — the runs-page
bug class; they now share HubLoadErrorView, which classifies
into unreachable / needs-newer-hub / load-failed-with-copyable-
detail (new generic DE+EN strings)
- the approvals page's hidden tab had no future listener: a load
failure there surfaced as an uncaught async error
- the audit status bar rendered the raw gRPC error wall verbatim;
it now shows the classified friendly headline (still selectable)
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Doctor findings used to be dead-end statements — 'approvals
waiting for review' left the operator to find the approvals inbox
on their own. Every finding with a dedicated surface is now one
tap away from it:
- summary tiles: modules -> store, approvals -> approvals inbox,
audit chain -> audit log (chevron affordance, tooltip + semantics
button; the services tile stays plain — no dedicated page)
- modules/approvals panel rows link the same way
- the event-log headline opens the audit page next to the
existing verify button
- host-services empty state gains a 'view the configuration'
button opening the in-Studio config viewer the hint refers to
- the update banner's release-notes URL is now an underlined,
clickable link instead of dead text
Tiles and the panel are public callback-driven widgets so the
widget tests pump them without a live hub. New DE+EN link labels.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Finally caught with a creation stack trace: when the last gRPC
stream closes, Http2ClientConnection arms the channel's 5-minute
idleTimeout timer — even on a shut-down connection — so the
test's 1-minute drain never covered it and the framework's
pending-timer invariant tripped whenever the arm landed inside
the test window (frequent while a real hub listens on 50051).
The suite now closes the channel in real-async space at the end
of the body (new @visibleForTesting HubService.debugResetChannel;
shutdown is deliberately not awaited — it wedges on a mid-connect
socket, but cancels its timers synchronously) and pumps past the
idle timeout so the timer fires inside the test. 6 consecutive
full-suite runs + 3 isolated runs green; before, roughly 1 in 3
full runs failed.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The runs monitor folded every load failure into 'hub not reachable',
contradicting the sidebar's green connected dot whenever the hub
answered but the RPC failed — most visibly against a pre-0.22 hub
whose version predates the ListInvocations RPC (UNIMPLEMENTED).
Classify the failure instead (top-level, unit-tested):
- UNIMPLEMENTED -> 'this view needs a newer hub version' with a
doctor-page link (the update banner lives there)
- FAILED_PRECONDITION from the detached gate -> the regular
feature-off empty state with the guide button
- UNAVAILABLE / DEADLINE_EXCEEDED / socket-level failures -> the
honest 'hub not reachable' state (unchanged)
- everything else -> a load-failed state with the friendly error
and a copyable detail box
The error view is a public callback-driven widget so the tests pump
each variant without a live hub. New DE+EN strings for the too-old
and load-failed states; grpcCodeOf/grpcMessageOf exposed from the
friendly-error mapper instead of duplicating the duck-typing.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The menu so far only exported the current view (type + search
filter over the page 100-event window). A second action now fetches
the complete event history of the active project scope in one
EventLog call (the RPC has no cursor and no server-side cap) and
writes it as JSONL. Serialisation extracted to a top-level function
with unit tests; a stale comment advertising the never-shipped
"chain audit export" command now names "chain admin events --json".
Studio 0.73.0.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
- Store hero: 'TODAY' badge only for an operator-accepted story; the
rotating compiled-in fallback deck now says 'FEATURED' (no false
freshness claim). Policy as a top-level function with unit tests.
- Audit filter chips: standard label typography instead of mono —
mono stays reserved for paths and identifiers.
- DE chain wording: 'Hash-Kette geprüft' as the one confirmation term
(audit header now matches the doctor pill); 'intakt' stays the
state headline. EN was already consistent.
- Workspace switcher: contrast bump for the sealed-area 'stopped'
label.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Three doctor-page findings from the usertest panel:
- Module tile no longer counts the hub's built-in 'system'
pseudo-module — a fresh hub shows 0 modules, matching the
welcome checklist's definition of an install. Counting is a
top-level function with unit tests.
- When the audit DB lives in an OS-cleanable temp directory
(/var/folders, /tmp, Windows Temp), the daemon-files panel
says so instead of presenting the state as healthy. The
classifier is a top-level function with unit tests.
- The daemon card states who-talks-to-whom-how in one line:
endpoint, transport security (TLS / unencrypted-local /
unencrypted), and whether a bearer token is attached (length
only) — the auditor's baseline the green dot cannot answer.
Also syncs pubspec.yaml (0.70.0 -> 0.72.0) with kStudioVersion,
which had drifted to 0.71.0 while pubspec stayed behind.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Text widgets render markdown backticks verbatim (usertest: reads
as typos / raw dev output) — stripped across both catalogs; code
terms stay inline in plain type. 'Zeig dem Hub' / 'Nimm das CLI'
now use the formal address; the guard test learns both stems.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
- search field over flow/step/module/error/detail/project/id backs
the list and the export ('current view' semantics); match logic
is a top-level function with unit tests
- export writes one JSON object per line via the save dialog; the
CLI stays the canonical WORM-grade export
- the bare trash icon on the audit toolbar read as 'delete
evidence' (security-auditor finding) — the dev-only reset now
sits in a labeled overflow menu next to the export action
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
- 'Fähigkeiten' is the German lead term everywhere (welcome step 2,
integrations panel, federation) — English jargon only as a
parenthesized technical term on first mention
- 'Enrollment-Token' -> 'Registrierungs-Token', bootstrap label in
plain words, CA spelled out, enrollment hint now formal address
- formality guard also rejects reader-addressed imperatives
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
One-click installs showed no trust signal at all (top security
finding of the usertest panel). Every install path — store card,
detail sheet, and the flow list's quick fix — now routes through
one confirmation dialog showing what the hub actually knows
before download: origin store, version, license, maturity, and
required services/capabilities, plus the sandbox model and an
honest note that per-entry signature status is not in the store
index yet (verification happens hub-side at install). Widget
tests cover content and confirm/cancel semantics.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The icon-only rail forced first-time users to guess (usertest:
Senior, a11y, UX personas). Three changes:
- Nav tooltips appear instantly and carry the page shortcut
(Cmd+1..9, Ctrl on non-mac — Ctrl activators added); expanded
labels show the same hint. Explicit button semantics for
screen readers on every destination.
- A visible 'Search & commands' row above the footer opens the
existing Cmd+K palette, which nothing in the UI advertised.
- Settings -> Appearance gains 'Keep the navigation expanded':
pins the rail with permanent labels (persisted preference).
Footer strip and pillar toggle made overflow-safe for the
animating rail; responsive test scrolls the by-design scrollable
destinations list.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Store search, security panel, System-AI fix hint and the
approvals payload hint addressed the operator informally while
the rest of the app uses Sie — the panel flagged the break on
every second screen. All DE strings now use the formal address,
and a guard test rejects any future informal form in app_de.arb.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Stefan's live findings, all four addressed at the root:
- 'In 3 Fragen loslegen' read like ad copy → the entry is now plainly
'Einrichtung starten' / 'Start setup'.
- The setup button sat permanently on the Welcome page of a running
app ('setup after the app runs is backwards' — reported twice). A
fresh install now starts INSIDE the setup: SetupGateScreen hosts
the wizard embedded as the page (new embedded/onFinished modes on
GuidedSetupDialog), with an explicit 'Später einrichten' skip.
Welcome loses the setup button entirely and stays a calm intro.
- Re-running the setup later lives in Settings → General ('Run setup
again…'), the single post-first-run home.
- 'You must grant access first and only then see what will be done':
the preview used to spawn the chain CLI, whose first run could pop
the macOS permission prompt BEFORE the plan was ever shown. The
preview now calls the new PlanSetup RPC over the live hub
connection (no subprocess, nothing granted); the CLI remains only
a fallback when no hub is reachable — and applying stays the
explicit, separate step.
Widget tests: gate hosts the wizard + skip/cancel leave it; CLI-path
tests drive the fallback through the new hub-preview test seam.
Suite 76 green, analyze clean.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
A black window on launch, no error anywhere: main() awaited
loadPersistedEndpoint before the first frame, and
SharedPreferences.getBool threw 'int is not a subtype of bool?' —
the store is writable from outside the app and hub.secure had been
written as int 0. Pref reads now go through defensive typed helpers
(int coerces to bool, wrong types fall back to defaults), and every
pre-frame restore step is failure-isolated: a broken store can cost
a preference, never the first frame. Regression tests stage the
corrupt store (the exact observed value and worse).
Also removes the CocoaPods leftovers from the macOS project
(Podfile, [CP] script phases, Pods framework references, xcconfig
includes): the project builds via Swift Package Manager, and the
dual wiring ran both dependency managers on every build — Flutter's
persistent 'removing CocoaPods will improve build time' warning.
Verified: clean profile build produces a launchable bundle (plugins
statically linked via SwiftPM), suite green, analyze clean.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Review follow-ups on the auth-status work:
- Both daemon-start paths classified an auth-rejected hub as "daemon
dead" via healthy() and showed a start-failure dialog while the
shell banner above correctly blamed the token. They now share
daemonAnswers(): only an unreachable probe counts as down.
- An auth-rejected poll now re-reads ~/.chain/hub-auth-token and
reconnects when the file changed, so a token fixed outside Studio
(CLI, editor) heals the connection without a restart — previously
the client kept the stale in-memory token forever and the banner's
own advice could not work.
- An endpoint switch resets the failure streak, so a stale in-flight
probe can no longer let the unreachable banner blame the new
endpoint for the old one's misses.
- The auth-policy panel re-queries when the hub token is saved or
cleared in the panel above (reloadTick), instead of keeping a
stale admin-denied hint; it also renders the hub's new
reload_required flag as a pending-reload warning (DE+EN).
- today-pipeline.md still documented ~/.fai/today after the rename;
the FAB theme comment now states the both-themes intent.
flutter analyze clean; 71 tests green including four new ones.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The wizard's widget tests all ran in German, leaving the English
variant unverified (open follow-up from the setup redesign). One
EN walk now pins the localized step counter, scenario cards with
explanations, environment step and Next/Back navigation, and
asserts no German strings leak through.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The sustained-failure banner treated every failed health poll as
'can't reach the hub'. With token auth active, a wrong or rotated
token gets UNAUTHENTICATED from a perfectly reachable hub — the
old wording sent the operator to fix the endpoint. The shell now
uses the SDK's probe() and, on auth rejection, switches the banner
to 'rejected the sign-in — check the access token' (key-off icon,
DE+EN). Two widget tests pin the wording per failure kind and the
banner clearing once the probe turns serving.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Accessibility/responsive audit pass with two new permanent test
gates (test/a11y_test.dart: WCAG text contrast + labeled tap
targets on every page in both themes; test/responsive_test.dart:
no layout overflow at 800/960/1280/1920 px). Findings fixed:
- Light theme primary/tertiary sky-500 → sky-700: white text on
the lighter accent only reached 2.8:1 (welcome CTA, active
sidebar label); sky-700 clears WCAG AA at ~5.9:1. Dark theme
unchanged (already compliant). FABs now follow the same accent
instead of Material 3's washed-out tonal default.
- Audit page: filter chips collapse into a checkmark popup menu
below 900 px window width (app bar overflowed); the live-status
bar's left text is now Expanded with ellipsis so the row can
shrink, and the disconnected state's copyable error gets the
full remaining width.
- German strings now use formal address consistently (~20 strings
still used du-forms next to Sie-forms on welcome/setup), the
audit event-type chip "Step" is "Schritt", and the doctor
page's event count pluralises correctly in both languages.
flutter analyze clean, 64 tests green. Screenshot pass light+dark
via the guide-shots harness (verified parity, no overflows).
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Settings → Security now shows the hub's effective auth policy via the
new read-only AuthStatus RPC: active token validator (static / jwt-rs256
with issuer, audience, JWKS source), anonymous-access warning, per-token
cards with scope grants, env-var presence and rate limits, plus a
localized admin-denied story for non-admin tokens. Live-reloads on
endpoint change.
Also fixes a batch of fai→chain rename leftovers this panel's
verification uncovered: hub_auth_token.dart and registry_token.dart
read/wrote ~/.fai/ while the hub reads ~/.chain/ (stored registry
tokens never reached the hub), today_story_loader + tools/today used
~/.fai/today, chain_log legacy ~/.fai/logs migration removed per the
no-legacy-recognisers decision, and UI strings still advertised the
retired .fai bundle extension.
Includes 5 widget tests for the panel, an integration-test screenshot
harness (auth_policy_shots_test.dart, guide-shots style), and DE+EN
l10n. flutter analyze clean, 58 tests green.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Field test of the setup wizard surfaced three trust breaks in one run:
an unexplained macOS Documents permission prompt, a perceived crash,
and an error message whose copy button could not be reached.
Root causes and fixes:
- chain init failures were shown as a SnackBar, which lands BEHIND the
wizard's modal barrier: dimmed, clipped, copy unreachable — and the
click aimed at it hit the barrier, dismissing the whole wizard with
all answers (the perceived crash). Errors now open a modal dialog
ABOVE the wizard via showChainErrorDialog with a copyable detail
block, and the wizard is no longer barrier-dismissible.
- When the resolved chain binary is older than Studio and rejects
--plan-json, the wizard now explains the version skew in plain
language (binary path + update path) instead of leaking a raw clap
usage error. A missing binary gets its own localized story.
- Step 3 announces which chain binary the preview will execute; when
that binary physically lives (symlinks resolved) in a TCC-protected
folder, the wizard pre-explains the macOS folder prompt.
Supporting changes: FriendlyError passes through friendlyError()
unchanged so call sites can ship precise localized stories through the
shared presentation; SystemActions gains resolvedChainBinary() plus
run/resolve test seams; ChainErrorBox hugs its content instead of
filling an unbounded dialog; the wizard's answers file is written
synchronously (the async dart:io variants never complete under the
widget-test fake-async zone).
Verified: flutter analyze clean, 53 tests green (6 new wizard error-
path tests incl. clipboard round-trip), plus a live GUI walk on macOS
in dark + light with a stale binary (skew dialog, copy verified via
clipboard) and with the real binary (TCC pre-explanation with the
resolved path, full plan preview).
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
- Regulated path finishes without a terminal: the signed-source
state offers 'Add a signed source…' (stores dialog with pin-a-key)
plus the per-module install buttons and a plain-language hint why
pinning the publisher's key matters — instead of a hint with no
affordance.
- Apply warnings (e.g. the empty-trusted-publishers caveat) surface
selectable in the done state instead of being swallowed.
- Truthful preview: new lines state which machine is being set up
(server/container targets configure THIS machine), that regulated
profiles always get the hash-chained audit log (even with WORM
off), and that the curated reading list is stored with the setup
record.
- Language pass: onboarding checklist in Sie-form + 'System-KI'
(was du-form + 'System-AI'), 'Audit-Sperre' jargon replaced,
answers file moved to a private per-dialog temp dir.
- Screenshot harness: GUIDE_SHOTS_THEME=light for light-parity
proof runs.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
integration_test/guide_shots_test.dart boots a hermetic hub
(HubFixture), seeds demo projects (open Bürgeramt; sealed
Ratsinformation and a setup.applied audit event only when the
runner confirms an isolated $HOME), launches the app in German +
dark mode, walks every sidebar page in Cmd order with
content-aware waits, opens the workspace switcher and the setup
wizard, and writes the guide PNGs via a driverless RepaintBoundary
capture. Driven by the platform repo's scripts/regen-studio-guide.sh.
Also fixes the hub fixture's binary resolution, dead since the
rename (it looked for 'fai' and ../chain_platform/): now $CHAIN_BIN,
'chain' on PATH, then ../fai_chain/target/{release,debug}/chain —
the integration tests actually run again instead of silently
skipping.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
test/nav_manifest_test.dart derives docs/nav.generated.json from
the _pages list (order = Cmd numbers), the labelOf switch, and both
.arb files. On any nav change it regenerates the manifest and fails
once with instructions to commit + mirror it to
fai_chain/docs/studio/, where the platform repo's docs_consistency
gate checks the operator guide against it — cross-repo nav drift
becomes a red gate instead of quietly rotting docs.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The wizard's first step now offers 'or just describe what you want
to do': the goal goes to the configured system AI, which maps it
onto the menu answers (validated against strict enum whitelists —
a hallucinated value can never reach the engine). The suggestion
comes back as an editable plain-language reflection ('this is how I
read your task') the operator can adjust step-by-step or take to
the same preview/apply the menu path uses. Trust rules per
guided-setup.md: suggestion only (never auto-apply), a privacy line
states whether the description is processed locally or sent to a
provider, and without a configured system AI the section explains
that the menu always works — no dead end.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Post-apply the wizard now renders real Studio actions instead of CLI
text: a start-hub button that polls until the daemon answers,
per-module install buttons (capability-name install via the hub's
store index) with done/progress states, and an open-the-starter-flow
button that navigates to the Flows page. Regulated plans explain in
plain language that modules come from a signed source; the preview
offers 'allow installing from the public store' as one deliberate,
reversible switch that re-assembles the plan (allow_unsigned_modules).
Fresh installs (no config, no setup-plan.yaml) auto-open the wizard
once per run — the wizard IS the onboarding — and it steps back once
a setup exists. The welcome CTA is framed honestly ('get started in
3 questions'), and after the wizard closes the onboarding checklist
remounts, re-probes, and says what the assistant already covered
(profile line from setup-plan.yaml) instead of acting as a second,
disconnected onboarding surface.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Reworks the Setup-Assistent toward the zero-learning-curve bar
(docs/architecture/guided-setup.md, phase 1.1):
- Every scenario/intent/target choice is now a localized option CARD
with a one-line plain-language explanation of what it configures
(DE+EN, Sie-form) — replacing the bare dropdowns whose labels were
English enum humanizations ('Regulated Production', 'This Laptop').
- Three explained steps with a 'Schritt n von 3' progress line
(stakes → task → environment); the two adaptive toggles move to the
last step in plain language (no 'air-gapped' jargon).
- The review step renders a localized PLAIN-LANGUAGE summary built
from 'chain init --answers --plan-json' (the structured SetupPlan) —
'Ch∆In richtet einen regulierten Betrieb ein: signierte Module
verlangt · … · geändert wird nur ~/.chain/config.yaml' — instead of
echoing the CLI's English prose. Warns when an existing config will
be overwritten. After apply: a plain 'Fertig' + next steps.
flutter analyze clean; widget tests for the step flow + German option
labels. Remaining per plan: clickable follow-up actions, signature
dead-end fix, placement/auto-open, and the LLM free-text path.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
The workspace switcher now lists the operator's sealed areas (read from
~/.chain/sealed/ manifests, the same source the CLI uses) below the
shared projects, each with a lock icon and a running/stopped status.
Selecting one is a real connection switch: Studio reconnects its hub
client to the area's own port with a full state reload — one window,
one truth. A stopped area is started first (chain project start) with a
visible notice; a failure surfaces as a copyable error and rolls back to
the shared hub.
While in a sealed area an identity bar under the AppBar is painted in
the area's accent colour and names it, with a one-click Leave back to
the shared hub. The area colour is marking, not theming — Studio's blue
stays the app accent. Selecting a shared project from inside an area
switches the connection back first. The sealed connection is never
persisted across restarts.
New: SealedAreaService (manifest + PID discovery), Workspace sealed
switch logic, ChainSealedIdentityBar, SystemActions.chainProjectStart.
l10n DE+EN. flutter analyze clean; 33 tests green (switcher lists sealed
with lock+status, pill shows active area, identity bar renders in the
area colour). Runtime plumbing (discovery, start, endpoint, reach)
verified headlessly against real sealed instances under a redirected
HOME; the identity-bar screenshot is deferred (display click-automation
failed after sleep on the shared desktop — an environment issue, not a
code gap; the visible components are widget-tested).
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
New Runs sidebar destination listing detached invocations (detach:true)
with phase, current step, project and a Cancel button while
pending/running. Workspace-scoped like Audit/Approvals, polls every 2s.
Detached runs are opt-in (detached.enabled) — the empty state explains
how to enable them. Inline help doc DE+EN. DetachedRun model +
listDetachedRuns/cancelDetachedRun in HubService, backed by the SDK's
listInvocations()/cancelInvocation(). flutter analyze clean; 29 tests
green (sidebar Y-stability updated for the new destination, model
mapping unit-tested).
Screenshot verification (light+dark) deferred — shared desktop in use.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Multi-project stage 1 against the shared hub (platform design
docs/architecture/projects.md, § Studio):
- ChainWorkspaceSwitcher in the Audit + Approvals AppBars: lists the
registry (colour dot per project, shield for protected, honesty
tooltip), 'All projects' stays reachable — a filter, not a jail.
Selection is persisted and shared via the Workspace notifier.
- Audit page: list query AND live stream re-scoped hub-side on switch.
- Approvals page: pending + history scoped; the sidebar badge counts
the active workspace's pending approvals.
- Flow runs are stamped with the active workspace; a flow file
carrying its own project: keeps it (file wins, CLI semantics).
- Data layer: listProjects/ProjectRef; project fields on AuditEvent,
PendingApproval(+Record), SavedFlow; project params through
HubService. l10n DE+EN. Widget tests for the switcher contract.
Visual verification (light+dark screenshots) still pending — the
shared desktop was in active use; code paths are covered by
flutter test (26 green).
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
A Welcome 'Setup assistant' button opens a wizard that collects
scenario / intent / target (+ approval & data-local toggles), then calls
`chain init --answers` to preview the assembled plan and `--apply --force`
to write the config — reusing the Rust deterministic engine, no logic
duplicated. New SystemActions.chainInit; copyable errors via
showFaiProcessError; EN+DE l10n. analyze clean; smoke test + existing
welcome/sidebar tests pass.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
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>
The Studio design system, widgets and helpers carried a Fai* / fai_
prefix (FaiSpace, FaiColors, FaiTheme, FaiLog, 17 fai_*.dart files, the
faiBinary* l10n keys). Studio is the Ch∆In product, so rename them to
Chain* / chain_ — carefully preserving English fail/failure/failed.
Also fix stale references: the 'fai' binary in l10n strings -> 'chain',
FAI_* env vars (FAI_BIN/DATA_DIR/MODULES_DIR/TODAY/BOOTSTRAP_TOKEN) ->
CHAIN_*, fai_platform -> fai_chain, fai_hub -> chain_hub. Vendor
security-hook tooling (FAI_BANNED_TERMS_FILE) + the .fai bundle ext left.
flutter analyze + test: clean (20 passed).
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
Studio 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>
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>
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>
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>
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>
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>
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>
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>