The hub copies the reviewer string a client sends straight into decided_by (DecideApproval, ClearEventLog); nothing on the wire ties it to the authenticated caller. Studio filled it from the OS account, so an export read like non-repudiation while being an arbitrary client claim — the legal finding of the 2026-07-26 usertest panel. The real fix is hub-side (derive decided_by from CALLER_IDENTITY); that contract is written down in docs/reviewer-identity.md and needs a hub release. Until then Studio does the one thing it can do honestly and marks its own claim as a claim, inside the record: - data/reviewer_identity.dart is the single place that produces and reads the value; wire() is idempotent, so page and HubService may both normalise. Every write path funnels through HubService, so no surface can send a bare handle. - The inbox states before the decision who will be recorded, what that attribution is worth on this hub (from AuthStatus), and the literal string that lands in decided_by. An unreadable auth policy stays unreadable — never optimistic. - Reading back: a marked value shows its plain name plus an unchecked flag; an unmarked one (legacy row, CLI decision, or a future hub-derived identity) is not classified either way. - The audit wipe seeds the same kind of marked attribution into its chain.reset marker. When the hub starts deriving the value it overwrites the field and the prefix disappears by itself — no Studio release needed. Guards: reviewer_identity_test (the value) and approvals_reviewer_identity_test (every surface that writes or renders it, against the hermetic fake hub). Visual proof for both themes via the dialog-shot harness. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
718 lines
36 KiB
Markdown
718 lines
36 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to `chain_studio` recorded here. Pubspec
|
||
version + `kStudioVersion` in `lib/data/about_info.dart` stay in
|
||
lockstep.
|
||
|
||
## Unreleased
|
||
|
||
### Changed (0.81.0)
|
||
|
||
- **Approvals explain themselves.** The pending approval card now
|
||
leads with a one-line intro strip (what this inbox is; that
|
||
Approve resumes the flow and Reject stops it with a reason) and
|
||
carries a proper ORIGIN block: flow, step, **run id** (previously
|
||
dropped at the Dart mapping layer), project, and requested-at —
|
||
so a reviewer can see where a request comes from and why. The run
|
||
id is copyable; the same facts appear in the history dialog.
|
||
Approve/Reject buttons gained tooltips. Fixed the approvals doc
|
||
drift — the guide showed `title`/`details`/`reviewer`, which the
|
||
engine never accepted; it now documents `prompt`/`show`/
|
||
`timeout_seconds`. Guard: `approvals_origin_test` renders the card
|
||
through the hermetic fake hub and pins every origin fact.
|
||
- **Update the hub straight from the runs page.** The "this view
|
||
needs a newer hub version" state now leads with an in-place
|
||
*Update hub now* button (the same `chain update apply` path as the
|
||
Diagnose page) instead of only deep-linking to Diagnose; the
|
||
Diagnose link stays as the secondary escape hatch, and a missing
|
||
local CLI falls back to it. Guard: two new `RunsLoadErrorView`
|
||
widget tests.
|
||
|
||
### Fixed (0.81.0)
|
||
|
||
Approvals usertest-panel hardening:
|
||
|
||
- **Never fake the request time.** A missing/unparseable `created_at`
|
||
now leaves the "Angefordert" line off the card instead of showing
|
||
`now` (which even drifted on every refresh). `ApprovalRecord.createdAt`
|
||
is nullable; guard in `approvals_origin_test`.
|
||
- **Copyable errors on every path.** Approve/Reject and both batch
|
||
actions now route failures through `showChainErrorSnack` instead of
|
||
a bare `SnackBar(Text(e))` — the batch path surfaces the first real
|
||
cause, not just a count.
|
||
- **Reject needs a reason.** The reject dialog leads with a
|
||
`ChainInlineHelp` strip and disables the confirm button while the
|
||
reason is empty, instead of closing silently with nothing happening.
|
||
- **Batch approve respects the no-data guard.** A bulk approve that
|
||
includes requests without `show:` data now asks for the same
|
||
conscious confirmation the single approve already required, naming
|
||
how many are data-less.
|
||
- **Plainer language.** The intro and origin glossary "Flow" as
|
||
"Vorgang (Flow)", the history detail labels the question "FRAGE"
|
||
(not "PROMPT"), and the no-data hint drops developer jargon.
|
||
- **Copy the run id in one click**, and the history payload renders
|
||
with the same JSON pretty-printing as the card.
|
||
- **A reviewer name no longer poses as a proven identity.** The hub
|
||
stores whatever `reviewer` string a client sends as `decided_by`,
|
||
so Studio's `$USER@studio` read like non-repudiation while being
|
||
an unchecked claim (legal finding of the 2026-07-26 panel). Every
|
||
value Studio writes — approvals and the `chain.reset` marker of
|
||
the audit wipe — now carries an `unverified:` marker inside the
|
||
record, so an export, a CLI reader or a database dump sees the
|
||
trust level without knowing how the deciding Studio was set up.
|
||
The inbox states before the decision who will be recorded and
|
||
what that is worth on this hub (derived from the hub's auth
|
||
policy; "unreadable" stays unreadable, never optimistic). Reading
|
||
back, a marked value shows its plain name with an "unchecked"
|
||
flag, an unmarked one (legacy row, CLI decision) is not
|
||
classified either way. The real fix is hub-side — derive
|
||
`decided_by` from the authenticated caller; the contract is in
|
||
`docs/reviewer-identity.md`, and once the hub does it the marker
|
||
disappears without a Studio release. Guards:
|
||
`reviewer_identity_test` + `approvals_reviewer_identity_test`.
|
||
- **Audit overflow-menu entries no longer run off the menu.** A
|
||
popup menu is width-capped, so the full-sentence German labels
|
||
were clipped — on the reset entry exactly the "(nur local/dev)"
|
||
that keeps it from reading as "delete evidence". Labels wrap now;
|
||
guard in `responsive_test` (the page sweep cannot see popup menus,
|
||
which is why it went unnoticed).
|
||
|
||
### Added (0.80.0)
|
||
|
||
- **Ollama joins the host services.** Ollama-backed store entries
|
||
now declare `requires_services: [ollama]` hub-side, and the
|
||
doctor's services panel notices when the system AI uses an Ollama
|
||
endpoint that is not declared as a host service: one plain
|
||
sentence plus a one-click "declare as host service" (via the new
|
||
DeclareService RPC; states honestly that a daemon restart is
|
||
required).
|
||
- **The shell says when a hub update exists.** One slim, dismissible
|
||
banner after connecting when the release manifest offers a newer
|
||
hub version — dismissal is remembered per version, the doctor
|
||
page keeps the apply button. (Deliberately manifest-based:
|
||
Studio and hub version numbers are independent counters, so a
|
||
direct comparison would be wrong; unreleased dev skew stays with
|
||
the per-page "needs a newer hub" states.)
|
||
|
||
### Added (0.79.0)
|
||
|
||
- **Maintainers in the store.** The detail sheet always answers "who
|
||
maintains this?" — one selectable line per maintainer from the
|
||
store index, or an honest "not specified" when the index names
|
||
nobody. The install trust gate carries the same fact when present.
|
||
All bundled seed entries name their maintainer (guarded hub-side).
|
||
|
||
### Fixed — honest install badge in the flow editor (0.78.0)
|
||
|
||
- **Install is only offered when the store can deliver.** The
|
||
capability set behind the flow editor's Install quick-fix and the
|
||
flow list's install badge previously ingested every store entry's
|
||
`requiresCapabilities` (dependencies, not provided capabilities)
|
||
and ignored entry status/kind — so 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` entries, no federated entries
|
||
(`lib/data/store_caps.dart`). Capabilities the store cannot
|
||
resolve render the editor's "not in store" state, which explains
|
||
the three recovery paths (local install, add store, configure
|
||
integration) before any click.
|
||
- Flow editor pinned at 0.25.0 (store-resolvability split in the
|
||
flow list, three-path analyzer message).
|
||
|
||
### Fixed — disturbance hardening on the flows install path (0.78.0)
|
||
|
||
- **Unknown ≠ absent.** Until the first successful store search
|
||
(and again after a connection switch) the store snapshot is
|
||
"unknown": the editor then claims neither "installable" nor
|
||
"not in store" for missing capabilities. Previously an
|
||
unreachable store made every missing capability claim
|
||
"not in store".
|
||
- **Sealed switch resets capability offers.** Entering/leaving a
|
||
sealed area reloads both the installed-capability list and the
|
||
store snapshot on the mounted Flows page — the editor no longer
|
||
keeps offering the other hub's capabilities.
|
||
- **Store snapshot refreshes after installs**, closing the race
|
||
where a fresh install left the badge's truth stale. The
|
||
classified "no store entry" error (see above) stays as the net
|
||
for true races.
|
||
|
||
### Added — project separation for flows (0.78.0)
|
||
|
||
- **Workspace switcher on the Flows page.** The same project
|
||
selector as Audit/Approvals/Runs now sits in the flow editor's
|
||
toolbar: it filters the flow list by the active project (flows
|
||
without a `project:` key count as "general" — display semantics,
|
||
files are never rewritten) and is the project a new flow gets
|
||
stamped with. The file keeps winning for runs; the editor's
|
||
mismatch banner still offers the one-click align.
|
||
|
||
### Added — real trust + exposure data (0.77.0)
|
||
|
||
- **Per-entry install verification.** The hub now reports (since
|
||
0.23) how an install of each store entry would be verified under
|
||
the CURRENT policy — computed with the same resolvers the install
|
||
gate enforces, so display and enforcement cannot disagree. The
|
||
trust-gate dialog replaces its generic "no per-entry status yet
|
||
(alpha)" note with the classified statement: signature checked
|
||
via pinned store key / trusted publishers (green), installs
|
||
without signature checking (amber, policy off), install would be
|
||
refused (red), or bridge entry without a bundle signature. Store
|
||
cards stay quiet on the good path and wear an "unsigned"/
|
||
"blocked" warning pill only when installs are unverified —
|
||
same philosophy as the provenance pill. Against a pre-0.23 hub
|
||
everything keeps the old honest wording.
|
||
- **Service exposure on the doctor page.** Every declared host
|
||
service shows its network reach as classified by the hub from
|
||
the endpoint host (local only / private network / publicly
|
||
reachable / reach unknown — no DNS lookups). Publicly reachable
|
||
endpoints get the warning tone with a "make sure it is
|
||
protected" hint.
|
||
|
||
### Added — explain it where it happens (0.76.0)
|
||
|
||
- **In-place help pattern.** New `ChainInlineHelp` (a one-sentence
|
||
intro strip that says what a surface is and what will happen, with
|
||
an optional "Learn more" link into the doc sheet) and
|
||
`ChainFieldHelp` / `ChainFieldLabel` (a "?" affordance for a single
|
||
field). First applied to the add-satellite dialog, which used to
|
||
ask for a "name" with no hint of what a satellite even is
|
||
(usertest): it now leads with a plain explanation + a federation
|
||
"Learn more", and the name field carries a "?".
|
||
- **Page help opens the RIGHT topic.** The Federation and Runs help
|
||
buttons silently fell back to the architecture doc — `federation`
|
||
and `runs` had markdown assets but no catalog entry, so
|
||
`showFaiDoc` resolved them to the first doc. Both are registered
|
||
now; a new `doc_help_wiring_test.dart` asserts every
|
||
`showFaiDoc('slug')` call has a registered entry AND both locale
|
||
assets, so a help button can never open the wrong topic again.
|
||
|
||
### Security (0.75.0)
|
||
|
||
- **Sealed-area names are confidential by default.** The workspace
|
||
switcher no longer lists sealed areas by name on a casual glance
|
||
(names often carry client/mandate identity — usertest security
|
||
finding): it shows one aggregated row ("2 sealed areas") and
|
||
reveals the named rows only after a deliberate click, per menu
|
||
opening. Settings → Security offers "list sealed areas with
|
||
their names right away" for single-operator machines.
|
||
|
||
### Changed (0.75.0)
|
||
|
||
- **One segment control everywhere.** The same single-select
|
||
choice pattern used to appear as four different widgets: the
|
||
audit page's hover pills, the store's Material SegmentedButton
|
||
(Modules / Studio & Designs), the store filter dialog's
|
||
ChoiceChips and the approvals page's TabBar. All of them now use
|
||
the canonical `ChainSegments` pill control (the audit pattern
|
||
promoted to a shared widget, with icons and a11y semantics);
|
||
the approvals page keeps both lists alive in an IndexedStack so
|
||
switching does not refetch. A canon-guard test bans
|
||
TabBar/SegmentedButton/ChoiceChip from lib/ for good. Guide
|
||
images + wording updated (dark and light verified).
|
||
|
||
### Hardening round (test strategy, 2026-07-19)
|
||
|
||
- **Hermetic widget suites.** `HubService.instance` is injectable;
|
||
a scriptable fake (`test/support/fake_hub.dart`) answers every
|
||
member the pages touch and scripts per-RPC failures. The a11y and
|
||
responsive sweeps run against it — results no longer depend on
|
||
whether a hub happens to listen on the operator's machine (which
|
||
was the root of the historic a11y flake).
|
||
- **State-matrix sweep** (`state_matrix_test.dart`): every sidebar
|
||
page × hub condition (healthy / gone / too old / feature off) with
|
||
app-wide invariants — no "unreachable" claims while the hub
|
||
answers, no raw error text in the UI, classified states only.
|
||
- **Shared load-error view.** Store, Doctor, Audit, Approvals and
|
||
Federation no longer fold every load failure into "hub not
|
||
reachable"; `HubLoadErrorView` classifies into unreachable /
|
||
needs-newer-hub / load-failed-with-copyable-detail (new DE+EN
|
||
strings). Also fixed: the approvals page's hidden tab surfaced
|
||
load failures as uncaught async errors; the audit status bar
|
||
rendered the raw gRPC error wall instead of the friendly headline.
|
||
- **Version-skew gate.** `integration_test/skew_smoke_test.dart`
|
||
(run via the platform repo's `scripts/skew-smoke.sh`) boots the
|
||
released hub binary and walks every page with the same
|
||
invariants — the dev-Studio-against-old-hub combination is now a
|
||
release gate instead of a live-bug generator.
|
||
|
||
### Added (0.74.0)
|
||
|
||
- **Doctor findings deep-link to their page.** Summary tiles and
|
||
panel rows jump to where the finding is actioned: modules → store,
|
||
"approvals waiting for review" → approvals inbox, audit chain →
|
||
audit log (chevron affordance, tooltip + semantics button). The
|
||
host-services empty state gains a "view the configuration" button,
|
||
and the update banner's release-notes URL is a real link now.
|
||
|
||
### Fixed (0.74.0)
|
||
|
||
- **Runs page no longer contradicts the sidebar.** Load failures are
|
||
classified instead of all reading "hub not reachable": an older hub
|
||
without the ListInvocations RPC gets "this view needs a newer hub
|
||
version" with a doctor link, the detached-off refusal shows the
|
||
regular feature-off empty state, and unknown errors surface a
|
||
copyable detail box. "Not reachable" is reserved for genuine
|
||
connection failures.
|
||
- **Audit page timer leak.** The live-stream reconnect timer is held
|
||
and cancelled on dispose.
|
||
- **a11y suite flake killed at the root.** The pending-timer failures
|
||
came from the gRPC channel's 5-minute idle timer being armed when
|
||
the last stream closes; the suite now closes the channel and pumps
|
||
past the idle timeout (6 consecutive full runs green).
|
||
|
||
### Added
|
||
|
||
- **Full-history audit export.** The audit page's actions menu gains
|
||
"Export full log as JSONL" next to the current-view export: fetches
|
||
the complete event history of the active project scope (independent
|
||
of the type chip, the search field and the page's 100-event window)
|
||
and writes it as JSONL. The current-view export is unchanged; the
|
||
CLI `chain admin events --json` remains the canonical tool for
|
||
WORM-guaranteed exports (a stale code comment still advertised the
|
||
never-shipped `chain audit export` — fixed).
|
||
|
||
### Changed (usertest low-findings tail)
|
||
|
||
- **Honest hero badge.** The store hero wears "TODAY"/"HEUTE" only
|
||
for an operator-accepted story; the compiled-in fallback deck has
|
||
no date binding and now says "FEATURED"/"EMPFOHLEN".
|
||
- **Audit filter chips in UI type.** The Alle/Flow/Schritt/Modul
|
||
chips drop the monospace styling — mono stays reserved for paths
|
||
and identifiers.
|
||
- **One verb for the chain check (DE).** "Hash-Kette geprüft" is the
|
||
confirmation everywhere (audit header, doctor pill); "intakt"
|
||
remains the state headline. English was already consistent.
|
||
- **Readable sealed-area status.** The "stopped" label in the
|
||
workspace switcher gets a modest contrast bump.
|
||
|
||
### Changed (usertest panel 2026-07-17, 14 personas — all confirmed findings)
|
||
|
||
- **Install trust gate.** One confirmation dialog before every module
|
||
install (store card, detail sheet, flow quick-fix): source, version,
|
||
license, maturity, required services — plus an honest signature note
|
||
(the hub does not report per-entry signature status yet; no fake
|
||
badge).
|
||
- **Settings → About.** Product, Studio + hub version (live), vendor
|
||
Flemming.AI / Dr. Stefan Flemming, Apache 2.0, contact, docs link —
|
||
every value copyable.
|
||
- **Discoverable sidebar.** Instant tooltips with the ⌘1–9/^1–9
|
||
shortcut, semantics labels, a visible "Search & commands ⌘K" row,
|
||
and a setting to keep the rail always expanded.
|
||
- **Plain-language empty states.** Runs page explains background runs
|
||
in plain words and — via the hub's new `detached_enabled` flag —
|
||
says truthfully whether the feature is off or simply unused;
|
||
approvals inbox explains human approvals without raw capability
|
||
syntax; welcome cards lead with everyday language (tech detail
|
||
behind a toggle).
|
||
- **One term per concept.** "Fähigkeiten" is the German lead term
|
||
(welcome, integrations, federation); "Registrierungs-Token" instead
|
||
of "Enrollment-Token"; category and source-kind labels localized
|
||
(no lowercase wire tokens like `document`, `1 system`).
|
||
- **Audit page.** Free-text search over the event list; JSONL export
|
||
of the current view; the dev-only clear action moved from a bare
|
||
trash icon into a labeled overflow menu.
|
||
- **Doctor page.** Copy button on every daemon path row; "n of m
|
||
events verified" instead of "n/m chain"; hash-chain wording unified.
|
||
The module tile no longer counts the built-in `system`
|
||
pseudo-module (a fresh hub shows 0 modules, matching the welcome
|
||
checklist); the daemon-files panel warns when the audit DB lives in
|
||
an OS-cleanable temp directory; the daemon card states endpoint,
|
||
transport security, and token status (length only) in one line.
|
||
- **Federation page.** Primary action in the app bar and in the empty
|
||
state (FAB removed); plain-language empty text.
|
||
- **Store.** License pill on the module card; maturity pills explain
|
||
themselves on hover; carousel shows a textual position; German tab
|
||
says "Studio & Designs".
|
||
- **Formal address guard.** A test now rejects informal German forms
|
||
including reader-addressed imperatives; literal markdown backticks
|
||
are gone from both catalogs.
|
||
- **Setup.** The wizard replaces the Settings dialog instead of
|
||
stacking on it, with a stronger scrim; the try-out profile names
|
||
the upgrade path to a regulated profile.
|
||
- **Welcome checklist.** The "start a saved flow" tick is sticky
|
||
instead of silently unticking once the event leaves the
|
||
recent-events window.
|
||
|
||
### Changed (setup lives before the app)
|
||
|
||
- **First-run gate.** A fresh install starts inside the setup: a
|
||
dedicated screen hosts the wizard before the app appears (embedded
|
||
mode + explicit "Später einrichten" skip). The Welcome page loses
|
||
its setup button entirely; re-running the setup lives in Settings →
|
||
General ("Run setup again…"). Wording de-ad-ified: "Einrichtung
|
||
starten" instead of "In 3 Fragen loslegen".
|
||
- **Preview before permission.** The plan preview now calls the new
|
||
PlanSetup RPC over the live hub connection instead of spawning the
|
||
chain CLI — whose first run could pop the macOS file-access prompt
|
||
BEFORE the operator ever saw the plan. CLI remains the no-hub
|
||
fallback; applying stays the explicit separate step.
|
||
- **Native file dialog for flow file inputs** (with flow-editor
|
||
v0.23.0): the Run tab's file inputs open a real picker; typing an
|
||
absolute path is now only the no-host fallback.
|
||
|
||
|
||
### Fixed (startup + build time)
|
||
|
||
- **Black window on launch.** A corrupt preference value (`hub.secure`
|
||
stored as int `0` by an external `defaults write`) made
|
||
`SharedPreferences.getBool` throw inside `main()` before the first
|
||
frame — the app sat as a black window with no visible error. Pref
|
||
reads now tolerate wrong types (int coerces to bool, garbage falls
|
||
back to the default), and every pre-frame restore step is
|
||
failure-isolated so nothing can prevent `runApp` again. Regression
|
||
tests reproduce the corrupt store.
|
||
- **CocoaPods integration removed** (macOS). The project builds via
|
||
Swift Package Manager; the leftover Pods wiring (Podfile, `[CP]`
|
||
script phases, Pods framework refs, xcconfig includes) made every
|
||
build run both dependency managers — exactly what Flutter's
|
||
own "will improve the project's build time" warning was about.
|
||
|
||
|
||
### Added
|
||
|
||
- **Settings → Security shows the hub's auth policy.** New panel
|
||
backed by the hub's read-only `AuthStatus` RPC: active token
|
||
validator (static / JWT-RS256 with issuer, audience and JWKS
|
||
source), anonymous-access warning, and per-token cards with
|
||
scope grants, env-var presence and rate limits — T4/T5 security
|
||
administration finally visible outside YAML. Non-admin tokens
|
||
get a localized denied story instead of an error.
|
||
- **Permanent accessibility + responsive test gates.** Every page
|
||
must pass WCAG text contrast and labeled-tap-target guidelines
|
||
in both themes (`test/a11y_test.dart`) and lay out without
|
||
overflow from 800 to 1920 px (`test/responsive_test.dart`).
|
||
|
||
### Fixed
|
||
|
||
- **Daemon start with a wrong token no longer misreports.** Both
|
||
start paths (welcome CTA, sidebar) treated an auth-rejected hub
|
||
as "daemon dead" and showed a start-failure dialog while the
|
||
banner above correctly blamed the token; an auth rejection now
|
||
counts as "daemon alive".
|
||
- **Token fixed outside Studio heals the connection.** On an
|
||
auth-rejected poll the shell re-reads `~/.chain/hub-auth-token`
|
||
and reconnects when the file changed — following the banner's
|
||
own advice no longer requires re-saving in Settings or a
|
||
restart. An endpoint switch also resets the failure streak, so
|
||
a stale in-flight probe can't blame the new endpoint.
|
||
- **Security panel stays fresh.** The auth-policy view re-queries
|
||
after saving or clearing the hub token in the panel above, and
|
||
renders the hub's new `reload_required` flag as a plain-language
|
||
pending-reload warning (DE+EN).
|
||
- **Auth-rejected hub no longer reported as "unreachable".** With
|
||
token auth active, a wrong or rotated token got UNAUTHENTICATED
|
||
from a perfectly reachable hub, but the sustained-failure banner
|
||
said "can't reach the hub" — pointing the operator at the
|
||
endpoint instead of the token. The shell now probes with the
|
||
SDK's failure-kind-aware `probe()` and switches the banner to
|
||
"rejected the sign-in — check the access token" (key-off icon).
|
||
- **Light theme accent was below WCAG contrast.** Filled buttons
|
||
and the active sidebar label rendered white-on-sky-500 (2.8:1);
|
||
the light scheme now uses sky-700 (~5.9:1). FABs follow the
|
||
same accent instead of Material 3's tonal default.
|
||
- **Audit page at narrow window widths.** The app-bar filter chips
|
||
collapse into a checkmark menu below 900 px, and the live-status
|
||
bar shrinks gracefully (copyable disconnect error gets the full
|
||
row width).
|
||
- **Consistent formal address in German.** ~20 strings still used
|
||
du-forms next to the Sie-forms on welcome/setup; the audit
|
||
event-type chip "Step" is now "Schritt"; the doctor page's event
|
||
count pluralises correctly ("1 Ereignis") in both languages.
|
||
- **Stored tokens reached the wrong directory.** `~/.fai/` rename
|
||
leftovers: the hub-auth token and registry token were read from /
|
||
written to `~/.fai/` while the hub reads `~/.chain/` — a stored
|
||
registry token never reached the hub. Today-stories and UI texts
|
||
still advertising the retired `.fai` bundle extension fixed too.
|
||
|
||
### Fixed (wizard live-run findings 2026-07-14)
|
||
|
||
Field test of the setup wizard surfaced three trust breaks in one
|
||
run; all were reproduced against a stale CLI and fixed:
|
||
|
||
- **Errors surfaced behind the wizard.** `chain init` failures were
|
||
shown as a SnackBar, which lands BEHIND the wizard's modal barrier:
|
||
dimmed, clipped, its copy button unreachable — and the operator's
|
||
click at it hit the barrier, which (dismissible by default) closed
|
||
the whole wizard with all answers. Now: failures open a modal error
|
||
dialog ABOVE the wizard (copy button works, verbatim CLI output
|
||
behind "Details"), and the wizard is no longer barrier-dismissible —
|
||
leaving it is explicit via Abbrechen/Zurück.
|
||
- **CLI version skew explained.** When the resolved `chain` binary is
|
||
older than Studio and rejects `--plan-json`, the wizard now names
|
||
the skew in plain language — which binary was executed, that it
|
||
predates the assistant, and the update path — instead of leaking a
|
||
raw clap usage error. A missing binary gets the same treatment plus
|
||
a hint on step 3 before anything runs.
|
||
- **macOS folder prompt pre-explained.** Step 3 now states which
|
||
`chain` binary the preview will execute; when that binary physically
|
||
lives (symlinks resolved) in a TCC-protected folder (Documents/
|
||
Desktop/Downloads), the wizard says up front that macOS may ask for
|
||
folder access — instead of a bare permission prompt appearing in
|
||
the middle of setup.
|
||
- `FriendlyError` values now pass through `friendlyError()` unchanged
|
||
so call sites can route precise, localized stories through the
|
||
shared error presentation; `SystemActions` gained a public
|
||
`resolvedChainBinary()` and test seams for the run/resolve paths.
|
||
|
||
### Added (guided setup on grade-1 — steps A4/A5/B1 + doc automation)
|
||
|
||
- **Clickable next steps.** After apply, the wizard renders real
|
||
Studio actions instead of CLI text: start-hub button (polls until
|
||
the daemon answers), per-module install buttons with progress/done
|
||
states (capability-name install via the hub's store index), and an
|
||
open-the-starter-flow button navigating to Flows.
|
||
- **Signature dead end resolved.** 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 (`allow_unsigned_modules`) that re-assembles the
|
||
plan. Air-gapped plans point to the offline-bundle path instead.
|
||
- **Fresh-install auto-open + honest framing.** On a fresh hub (no
|
||
config, no `setup-plan.yaml`) the wizard opens by itself, once per
|
||
run; the welcome CTA is now "In 3 Fragen loslegen" / "Get started
|
||
in 3 questions". After the wizard closes, the onboarding checklist
|
||
re-probes and states what the assistant already covered (applied
|
||
profile from `setup-plan.yaml`).
|
||
- **Free-text AI path (phase 1.2).** "Or just describe what you want
|
||
to do": the goal goes to the configured system AI, the reply is
|
||
validated against strict enum whitelists and comes back as an
|
||
editable "this is how I read your task" reflection feeding the same
|
||
preview/apply. Privacy line states local vs. provider processing;
|
||
without a system AI the menu path stands alone.
|
||
- **Nav manifest guard.** `test/nav_manifest_test.dart` generates
|
||
`docs/nav.generated.json` from the sidebar truth (ids, order = Cmd
|
||
numbers, DE+EN labels); the platform repo checks the operator guide
|
||
against the mirrored copy.
|
||
- **Guide screenshot harness.** `integration_test/guide_shots_test.dart`
|
||
boots a hermetic hub, seeds demo projects, walks every nav page,
|
||
the workspace switcher and the setup wizard, and writes the guide
|
||
PNGs — driven by the platform repo's `scripts/regen-studio-guide.sh`.
|
||
- **Fixed:** the integration-test hub fixture still looked for the
|
||
pre-rename `fai` binary and `chain_platform/` path, so its tests
|
||
silently skipped since the rename; it now resolves `$CHAIN_BIN`,
|
||
`chain` on PATH, and `../fai_chain/target/{release,debug}/chain`.
|
||
|
||
### Added (guided setup — persona re-audit fixes, grade-1 round)
|
||
|
||
- **Regulated path finishes without a terminal.** The post-apply
|
||
signed-source state now offers "Add a signed source…" (the stores
|
||
dialog with its pin-a-key field) plus the per-module install
|
||
buttons, instead of a hint with no affordance; a plain-language
|
||
trust hint explains why pinning the publisher's key matters.
|
||
- **Apply warnings surface.** Warning lines from `chain init --apply`
|
||
(e.g. the empty-trusted-publishers caveat) are shown selectable in
|
||
the done state instead of being swallowed on success.
|
||
- **Truthful preview.** New plan lines state which machine is being
|
||
set up (server/container targets configure THIS machine — said
|
||
explicitly), that regulated profiles get the hash-chained audit
|
||
log even when WORM is off, and that a curated reading list is
|
||
stored with the setup record.
|
||
- **Language pass.** Onboarding checklist switched to Sie-form and
|
||
"System-KI" (was du-form + "System-AI" next to the formal wizard);
|
||
"Audit-Sperre" jargon replaced with "Schreibschutz für das
|
||
Prüfprotokoll"; answers file now lives in a private per-dialog
|
||
temp dir instead of a fixed world-readable name.
|
||
|
||
### Added (multi-project, stage ③ — sealed areas)
|
||
|
||
- **Sealed-area connection switch.** 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.
|
||
- **Identity bar.** While connected to a sealed area, a strip 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. (The window-title
|
||
tint is a small follow-up; the identity bar is the primary signal.)
|
||
- Selecting a shared project or "All projects" from inside a sealed area
|
||
switches the connection back to the shared hub first. The sealed
|
||
connection is never persisted across restarts — Studio always launches
|
||
on the shared hub and the operator re-enters an area deliberately.
|
||
|
||
### Added (detached-runs monitor — T3 parity)
|
||
|
||
- **Runs page.** A new sidebar destination lists detached invocations
|
||
(submitted with `detach: true`) with their phase, current step,
|
||
project and a **Cancel** button while pending/running. Workspace-
|
||
scoped like Audit and Approvals; polls every 2 s. Detached runs are
|
||
opt-in (`detached.enabled`), so the empty state explains how to turn
|
||
them on. Inline help doc (DE+EN). Backed by the SDK's
|
||
`listInvocations()` + `cancelInvocation()`.
|
||
|
||
### Added (multi-project, stages ① + ②)
|
||
|
||
- **Workspace switcher.** The Audit and Approvals AppBars carry a
|
||
workspace control listing the hub's project registry (colour dot per
|
||
project, a shield for `protected`, an honesty tooltip). "All projects"
|
||
stays reachable — a filter, not a jail. The selection is persisted and
|
||
shared across pages; it re-scopes the audit list AND live stream
|
||
hub-side, scopes approvals (pending + history), and drives the sidebar
|
||
approval badge. Runs launched from the editor are stamped with the
|
||
active workspace — unless the flow file declares its own `project:`,
|
||
in which case the file wins (CLI semantics).
|
||
- **Editor project chip + file-wins guard.** A flow file's own
|
||
`project:` shows as a chip; on mismatch with the active workspace an
|
||
amber pill offers a one-click switch (the file still wins for the run).
|
||
Backed by editor package 0.22.0.
|
||
|
||
### Fixed (usertest 2026-07-10 findings)
|
||
|
||
- **Approval prompts localize.** An approval whose flow step gave no
|
||
`prompt:` (and legacy rows carrying the hub's old baked-in English
|
||
sentence) now renders the localized fallback "Freigabe für diesen
|
||
Schritt erforderlich" / "Approval required for this step" — no more
|
||
English inside the German approvals UI.
|
||
- **Approving without review data asks first.** When the approval step
|
||
attached no `show:` payload, "Freigeben" opens a calm confirmation
|
||
("Ohne Prüfdaten freigeben?") explaining that the flow deliberately
|
||
attached no data, with an explicit "Trotzdem freigeben".
|
||
- **Studio error log moved to `~/.chain/logs/`.** Writes went to the
|
||
pre-rename `~/.fai/logs/studio-errors.log` while the Doctor page and
|
||
`chain doctor` read `~/.chain/…`. Studio now writes to `.chain` and
|
||
migrates the old file (+ rotation sibling) over once.
|
||
- **Honest audit wording (legal review).** DE strings no longer claim
|
||
"manipulationssicher" — the audit log is *manipulationserkennend*
|
||
(tamper-evident); "warum WORM-1 für KRITIS reicht" became a neutral
|
||
what-it-does-and-does-not sentence (EN too); the Doctor chain pill
|
||
says "Integritätskette v1" instead of "WORM-1"; the federation
|
||
enrollment hint says the CA *authenticates* the first connect.
|
||
- **WCAG-AA secondary text on dark.** De-emphasised text was 3.7:1 on
|
||
cards; the muted token is now ≥ 4.5:1 against canvas, cards and
|
||
elevated surfaces.
|
||
|
||
### Added
|
||
|
||
- **Live audit feed.** The Audit page subscribes to `streamEvents` and
|
||
refreshes instantly on each new event (debounced); the 2 s poll stays
|
||
as the safety net and re-subscribes after a clean stream close.
|
||
- **Channel switcher in the sidebar.** The active-channel pill (and the
|
||
collapsed chip) is now click-to-switch: a menu of every channel with
|
||
its running state + a check on the active one; switching writes
|
||
`~/.chain/current-channel`, restarts that channel's daemon, and Studio
|
||
repoints to it.
|
||
- **Module-store manager is prominent + bilingual.** The store page
|
||
gains a labelled "Add store" button (was a bare icon) opening a fully
|
||
localized dialog: configured stores, a curated **Suggested stores**
|
||
shelf with one-click add/remove (probed for reachability — shows "not
|
||
available yet" until a store's index is published), and an optional
|
||
per-store **pinned public key** field.
|
||
- **Approval payload clarity.** The approval card always shows the
|
||
payload section: a present payload scrolls inside a height-capped
|
||
copyable box; an absent one explains that the flow's approval step
|
||
chooses what to surface via its `show:` field.
|
||
|
||
### Fixed
|
||
|
||
- **Errors are copyable everywhere.** System-AI test-connection, the
|
||
Settings dialog, and the Audit status bar rendered failures as
|
||
non-copyable text; all now route through `ChainErrorBox` /
|
||
`SelectableText`.
|
||
- **Filter dialog crash** on the store page — a `Spacer` inside
|
||
`AlertDialog.actions` (an `OverflowBar`, not a Flex) threw; the
|
||
buttons now sit in a `Row`.
|
||
- **Welcome docs grid** tidied into equal-height paired rows.
|
||
- **Approval card** leads with the human prompt; the `flow › step` id is
|
||
demoted to a metadata line.
|
||
- **Pulsing sidebar connection dot** restored when connected.
|
||
|
||
## 0.70.0 — 2026-06-13
|
||
|
||
### Added
|
||
|
||
- **Federation panel.** A new "Föderation" destination shows the
|
||
satellites connected to this hub (name, region, version, wire
|
||
version, advertised capabilities) and adds them in one step: press
|
||
**Add satellite**, name it, and the hub issues a single-use
|
||
bootstrap token bundled with its CA as a ready-to-paste satellite
|
||
config — the bundled CA makes the satellite's first connect
|
||
tamper-proof. Localized (EN + DE) with an in-app help doc. Backed
|
||
by new `HubClient.listSatellites` / `issueBootstrapToken` in the
|
||
Dart SDK.
|
||
|
||
## 0.68.0 — 2026-06-09
|
||
|
||
### Added
|
||
|
||
- **Inline approval driver**. `StudioFlowRunDriver` implements
|
||
the three new methods the editor 0.21.0 introduced
|
||
(`pendingApprovalIdForStep`, `approveApproval`,
|
||
`rejectApproval`) — all delegate to the existing
|
||
`HubService` approvals RPCs. End result: the Run tab now
|
||
hosts a complete Approve / Reject form inline, removing the
|
||
tab-switch detour to the standalone Approvals page.
|
||
|
||
## 0.67.0 — 2026-06-09
|
||
|
||
### Added
|
||
|
||
- **Inline help icons** in the Approvals, Audit, and Doctor
|
||
app-bars open the matching bundled doc in the existing
|
||
bottom-sheet reader via the new public helper
|
||
`showFaiDoc(BuildContext, slug)` exported from `welcome.dart`.
|
||
- **New `assets/docs/approvals.md`** (+ `_de.md`) explains the
|
||
approval concept end-to-end with a YAML example.
|
||
- **friendlyError hub-pattern matching**. Six hub-specific
|
||
failure shapes (approval rejected / timed out, output too
|
||
large, host service not declared, missing value reference,
|
||
MCP unreachable) get specific localised headlines + hints
|
||
before falling through to the gRPC-code default. Five new
|
||
test cases pin the matchers.
|
||
|
||
### Changed
|
||
|
||
- **Settings dialog sidebar fully localised** (Allgemein /
|
||
Darstellung / System-KI / Integrationen / Sicherheit /
|
||
Wartung). Per-panel titles + descriptions also.
|
||
- **Daemon action labels localised**: `enable autostart`,
|
||
`disable autostart`, restart / start / stop / status + the
|
||
OK / Failed result line.
|
||
|
||
## 0.66.0 — 2026-06-08
|
||
|
||
### Added
|
||
|
||
- **Add-module-source dialog** for private modules that
|
||
aren't in the public store. Asks for URL or local `.fai`
|
||
bundle path; carries an explainer block + `chain install --link`
|
||
example.
|
||
- **Store catalogue** passed to the flow editor so the
|
||
analyzer can decide between Install (in store) and Add
|
||
source (not in store).
|
||
|
||
## 0.65.x
|
||
|
||
- Editor 0.18.0 pickup; analyzer-error block on Run;
|
||
diagnostic strip persistence across tabs.
|
||
|
||
## 0.64.0 — 2026-06-08
|
||
|
||
### Added
|
||
|
||
- **Flow editor quick-fix install handler**.
|
||
`_onInstallCapability` wires the editor's "Install <cap>"
|
||
button to `HubService.installModule` and refreshes the
|
||
capability list on success.
|
||
|
||
## 0.63.0 — 2026-06-04
|
||
|
||
### Changed
|
||
|
||
- **Settings dialog refactored** from one 800-line vertical
|
||
scroll into a macOS-style sidebar with six categories.
|
||
|
||
## 0.62.x — 2026-06-03
|
||
|
||
### Added
|
||
|
||
- **`FaiLog` + central error helpers** (`showFaiErrorSnack`,
|
||
`showFaiErrorDialog`).
|
||
- **Inline `FaiLogViewer`** modal for tail-viewing log files.
|
||
- **Today CTAs** in the Store carousel actually re-run search
|
||
after a `filterCategory` / `runQuery` story.
|
||
|
||
## 0.61.x and earlier
|
||
|
||
See git log.
|