feat(studio): first-run UX, recovery affordances, l10n, bundled fonts
- Connection-aware Welcome: when the hub is down, show a hero with a primary "Start hub" CTA + install fallback instead of a dead, all-unchecked onboarding checklist (the first-run cliff). - Actionable binary-not-found (file picker + install link, not a "set FAI_BIN" dead end) and a connect-failure banner after repeated failed health polls. - Localize six hardcoded English error/toast clusters (DE+EN ARB). - Bundle Inter + JetBrains Mono as assets; drop the runtime google_fonts fetch (air-gap / KRITIS safe, no font-swap flash). Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
7511867774
commit
5313266cc4
25 changed files with 1231 additions and 234 deletions
|
|
@ -18,9 +18,61 @@
|
|||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
/// Sentinel returned in `_runFai(...).stderr` when no `fai`
|
||||
/// binary could be located. UI layers should not display this
|
||||
/// verbatim — they check [SystemActions.faiBinaryExists] first
|
||||
/// and render a localized, actionable message instead.
|
||||
const String kFaiBinaryNotFound = 'fai-binary-not-found';
|
||||
|
||||
/// Getting-started / install guide for the F∆I platform. Opened
|
||||
/// in the OS browser as the fallback affordance when no `fai`
|
||||
/// binary can be located on the machine.
|
||||
const String kFaiInstallDocsUrl =
|
||||
'https://git.flemming.ai/fai/platform#installation';
|
||||
|
||||
class SystemActions {
|
||||
SystemActions._();
|
||||
|
||||
/// SharedPreferences key for an operator-chosen `fai` binary
|
||||
/// path (set via the file picker when auto-detection fails).
|
||||
static const String _kFaiBinaryPrefKey = 'system.fai_binary_path';
|
||||
|
||||
/// In-memory cache of the operator override, loaded once at
|
||||
/// startup by [loadFaiBinaryOverride]. Null = no override.
|
||||
static String? _faiBinaryOverride;
|
||||
|
||||
/// Restore the persisted `fai` binary override (if any) so it
|
||||
/// is available before the first daemon action. Call once at
|
||||
/// app start, alongside the other persisted-state loaders.
|
||||
static Future<void> loadFaiBinaryOverride() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final p = prefs.getString(_kFaiBinaryPrefKey);
|
||||
if (p != null && p.isNotEmpty && File(p).existsSync()) {
|
||||
_faiBinaryOverride = p;
|
||||
}
|
||||
}
|
||||
|
||||
/// Persist an operator-chosen `fai` binary path. Used by the
|
||||
/// "Locate `fai` binary…" affordance when auto-detection on
|
||||
/// PATH and the canonical install dir both come up empty.
|
||||
/// Returns false (and persists nothing) when [path] is empty
|
||||
/// or does not point at an existing file.
|
||||
static Future<bool> setFaiBinaryPath(String path) async {
|
||||
if (path.isEmpty || !File(path).existsSync()) return false;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_kFaiBinaryPrefKey, path);
|
||||
_faiBinaryOverride = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// True when a `fai` binary can be located (override, PATH, or
|
||||
/// the canonical install dir). UI uses this to decide between
|
||||
/// the normal daemon-control affordances and the "locate the
|
||||
/// binary / read the install guide" recovery path.
|
||||
static bool faiBinaryExists() => _faiExecutable() != null;
|
||||
|
||||
/// Ask the OS to open [path] in the default handler. On macOS
|
||||
/// this opens text files in TextEdit, configs in the registered
|
||||
/// editor, etc. Returns true on a clean spawn (process exited
|
||||
|
|
@ -149,13 +201,11 @@ class SystemActions {
|
|||
) async {
|
||||
final exe = _faiExecutable();
|
||||
if (exe == null) {
|
||||
return (
|
||||
ok: false,
|
||||
stdout: '',
|
||||
stderr:
|
||||
'Could not locate the `fai` binary. Install via the '
|
||||
'platform installer or set FAI_BIN to its full path.',
|
||||
);
|
||||
// Sentinel, not a user-facing string. Callers detect this
|
||||
// via `faiBinaryExists()` and render a localized,
|
||||
// actionable recovery message (locate binary / install
|
||||
// guide) instead of CLI jargon like "set FAI_BIN".
|
||||
return (ok: false, stdout: '', stderr: kFaiBinaryNotFound);
|
||||
}
|
||||
try {
|
||||
final r = await Process.run(exe, args);
|
||||
|
|
@ -175,9 +225,17 @@ class SystemActions {
|
|||
return (executable: 'xdg-open', args: const []);
|
||||
}
|
||||
|
||||
/// Locate the `fai` binary. Order: $FAI_BIN, PATH, fallback to
|
||||
/// the canonical install location under the user's home dir.
|
||||
/// Locate the `fai` binary. Order: operator override (set via
|
||||
/// the file picker), $FAI_BIN, PATH, fallback to the canonical
|
||||
/// install location under the user's home dir.
|
||||
static String? _faiExecutable() {
|
||||
final override = _faiBinaryOverride;
|
||||
if (override != null &&
|
||||
override.isNotEmpty &&
|
||||
File(override).existsSync()) {
|
||||
return override;
|
||||
}
|
||||
|
||||
final fromEnv = Platform.environment['FAI_BIN'];
|
||||
if (fromEnv != null && fromEnv.isNotEmpty && File(fromEnv).existsSync()) {
|
||||
return fromEnv;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue