refactor: rename internal Fai* design system + fai_ helpers to chain
Some checks failed
Security / Security check (push) Failing after 2s
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>
This commit is contained in:
parent
68d23ab7dd
commit
891acd2ba2
52 changed files with 1225 additions and 1225 deletions
|
|
@ -38,7 +38,7 @@ flutter run -d macos
|
||||||
|
|
||||||
Will be published as `fai/studio` on Forgejo (`git.flemming.ws`).
|
Will be published as `fai/studio` on Forgejo (`git.flemming.ws`).
|
||||||
The local directory `chain_studio/` follows the established
|
The local directory `chain_studio/` follows the established
|
||||||
`fai_platform/` layout convention.
|
`fai_chain/` layout convention.
|
||||||
|
|
||||||
## Why "Studio" and not "Stage"
|
## Why "Studio" and not "Stage"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ connected and lets you enrol new ones.
|
||||||
2. The hub issues a single-use **bootstrap token** and bundles its
|
2. The hub issues a single-use **bootstrap token** and bundles its
|
||||||
**CA certificate**. Copy the shown config into the satellite's
|
**CA certificate**. Copy the shown config into the satellite's
|
||||||
`config.yaml` (one paste) and export the token as
|
`config.yaml` (one paste) and export the token as
|
||||||
`FAI_BOOTSTRAP_TOKEN`.
|
`CHAIN_BOOTSTRAP_TOKEN`.
|
||||||
3. Start the satellite. It dials the primary, the CA pin makes the
|
3. Start the satellite. It dials the primary, the CA pin makes the
|
||||||
first connect tamper-proof, and the primary signs it a short-lived
|
first connect tamper-proof, and the primary signs it a short-lived
|
||||||
certificate. It then appears in this list.
|
certificate. It then appears in this list.
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ verbundenen Satelliten und richtet neue ein.
|
||||||
2. Der Hub stellt ein einmaliges **Bootstrap-Token** aus und legt sein
|
2. Der Hub stellt ein einmaliges **Bootstrap-Token** aus und legt sein
|
||||||
**CA-Zertifikat** bei. Kopiere die gezeigte Konfiguration in die
|
**CA-Zertifikat** bei. Kopiere die gezeigte Konfiguration in die
|
||||||
`config.yaml` des Satelliten (ein Einfügen) und exportiere das
|
`config.yaml` des Satelliten (ein Einfügen) und exportiere das
|
||||||
Token als `FAI_BOOTSTRAP_TOKEN`.
|
Token als `CHAIN_BOOTSTRAP_TOKEN`.
|
||||||
3. Starte den Satelliten. Er wählt sich beim Primary ein, der CA-Pin
|
3. Starte den Satelliten. Er wählt sich beim Primary ein, der CA-Pin
|
||||||
macht den ersten Connect manipulationssicher, und der Primary
|
macht den ersten Connect manipulationssicher, und der Primary
|
||||||
signiert ihm ein kurzlebiges Zertifikat. Danach erscheint er in
|
signiert ihm ein kurzlebiges Zertifikat. Danach erscheint er in
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ Instead a `_DocReaderSheet` (modal bottom-sheet, same shape as
|
||||||
existing `flutter_markdown` package — same one the Store uses
|
existing `flutter_markdown` package — same one the Store uses
|
||||||
for module READMEs. The doc source is bundled with Studio as
|
for module READMEs. The doc source is bundled with Studio as
|
||||||
`assets/docs/*.md`, kept in sync via a build step that copies
|
`assets/docs/*.md`, kept in sync via a build step that copies
|
||||||
the relevant files from `fai_platform/docs/`.
|
the relevant files from `fai_chain/docs/`.
|
||||||
|
|
||||||
Pros:
|
Pros:
|
||||||
- Air-gap-tauglich (no network call to read docs)
|
- Air-gap-tauglich (no network call to read docs)
|
||||||
|
|
@ -158,7 +158,7 @@ phased PRs:
|
||||||
content with hard-coded strings (with i18n). Lands
|
content with hard-coded strings (with i18n). Lands
|
||||||
without the embedded-doc reader.
|
without the embedded-doc reader.
|
||||||
2. **Phase B** — bundled markdown docs + `_DocReaderSheet`.
|
2. **Phase B** — bundled markdown docs + `_DocReaderSheet`.
|
||||||
Pick 4-5 source docs from `fai_platform/docs/architecture/`
|
Pick 4-5 source docs from `fai_chain/docs/architecture/`
|
||||||
that map to operator concerns (security, host-services,
|
that map to operator concerns (security, host-services,
|
||||||
flow-experience, compliance-gaps). Add a build script that
|
flow-experience, compliance-gaps). Add a build script that
|
||||||
syncs them into `assets/docs/`.
|
syncs them into `assets/docs/`.
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,10 @@ import 'dart:io';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
class FaiLog {
|
class ChainLog {
|
||||||
FaiLog._();
|
ChainLog._();
|
||||||
|
|
||||||
static final FaiLog instance = FaiLog._();
|
static final ChainLog instance = ChainLog._();
|
||||||
|
|
||||||
// Serialise concurrent writes — Future-based mutex so a burst
|
// Serialise concurrent writes — Future-based mutex so a burst
|
||||||
// of errors (rare but happens during failed install retries)
|
// of errors (rare but happens during failed install retries)
|
||||||
|
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../widgets/fai_error_box.dart';
|
import '../widgets/chain_error_box.dart';
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
import 'fai_log.dart';
|
import 'chain_log.dart';
|
||||||
|
|
||||||
/// Show [error] as a SnackBar with a copy affordance.
|
/// Show [error] as a SnackBar with a copy affordance.
|
||||||
///
|
///
|
||||||
|
|
@ -41,7 +41,7 @@ void showFaiErrorSnack(
|
||||||
}) {
|
}) {
|
||||||
// Fire-and-forget log write. If the disk's full or the dir
|
// Fire-and-forget log write. If the disk's full or the dir
|
||||||
// is read-only the UI still surfaces the error.
|
// is read-only the UI still surfaces the error.
|
||||||
FaiLog.instance.error(source, error, context: title);
|
ChainLog.instance.error(source, error, context: title);
|
||||||
final messenger = ScaffoldMessenger.maybeOf(context);
|
final messenger = ScaffoldMessenger.maybeOf(context);
|
||||||
if (messenger == null) {
|
if (messenger == null) {
|
||||||
// No Scaffold above — the caller is likely showing the
|
// No Scaffold above — the caller is likely showing the
|
||||||
|
|
@ -60,8 +60,8 @@ void showFaiErrorSnack(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
content: Padding(
|
content: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.sm,
|
horizontal: ChainSpace.sm,
|
||||||
vertical: FaiSpace.xs,
|
vertical: ChainSpace.xs,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
@ -75,9 +75,9 @@ void showFaiErrorSnack(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.xs),
|
const SizedBox(height: ChainSpace.xs),
|
||||||
],
|
],
|
||||||
FaiErrorBox(error: error, isError: true),
|
ChainErrorBox(error: error, isError: true),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -94,14 +94,14 @@ Future<void> showFaiErrorDialog(
|
||||||
Object error, {
|
Object error, {
|
||||||
String? title,
|
String? title,
|
||||||
}) async {
|
}) async {
|
||||||
FaiLog.instance.error(source, error);
|
ChainLog.instance.error(source, error);
|
||||||
return showDialog<void>(
|
return showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => AlertDialog(
|
builder: (ctx) => AlertDialog(
|
||||||
title: title == null ? null : Text(title),
|
title: title == null ? null : Text(title),
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
width: 480,
|
width: 480,
|
||||||
child: FaiErrorBox(error: error, isError: true, maxHeight: 240),
|
child: ChainErrorBox(error: error, isError: true, maxHeight: 240),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ class StudioFlowRunDriver implements editor.FlowRunDriver {
|
||||||
/// (text / json / bytes only). File outputs degrade to
|
/// (text / json / bytes only). File outputs degrade to
|
||||||
/// JSON containing the URI string — the editor's run-tab
|
/// JSON containing the URI string — the editor's run-tab
|
||||||
/// renderer is read-only so the operator can still see
|
/// renderer is read-only so the operator can still see
|
||||||
/// where the file went; Studio's richer FaiFlowOutput
|
/// where the file went; Studio's richer ChainFlowOutput
|
||||||
/// widget keeps file-open affordances available elsewhere.
|
/// widget keeps file-open affordances available elsewhere.
|
||||||
editor.FlowOutputValue _convertOutput(FlowOutput out) {
|
editor.FlowOutputValue _convertOutput(FlowOutput out) {
|
||||||
return switch (out) {
|
return switch (out) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// HubService — singleton wrapper around fai_dart_sdk's HubClient.
|
// HubService — singleton wrapper around chain_dart_sdk's HubClient.
|
||||||
//
|
//
|
||||||
// Centralises the gRPC connection so each page just imports
|
// Centralises the gRPC connection so each page just imports
|
||||||
// `HubService.instance` instead of constructing its own client.
|
// `HubService.instance` instead of constructing its own client.
|
||||||
|
|
@ -1282,7 +1282,7 @@ class SatelliteEnrollment {
|
||||||
return 'federation:\n'
|
return 'federation:\n'
|
||||||
' upstream: https://<this-primary-host>:<federation-port>\n'
|
' upstream: https://<this-primary-host>:<federation-port>\n'
|
||||||
' hub_name: $name\n'
|
' hub_name: $name\n'
|
||||||
' bootstrap_token_env: FAI_BOOTSTRAP_TOKEN$ca';
|
' bootstrap_token_env: CHAIN_BOOTSTRAP_TOKEN$ca';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1553,7 +1553,7 @@ class HardwareSnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One curated model with editorial metadata. Wire-faithful
|
/// One curated model with editorial metadata. Wire-faithful
|
||||||
/// with `crates/fai_hub/system-ai/models.yaml`.
|
/// with `crates/chain_hub/system-ai/models.yaml`.
|
||||||
class CuratedModelInfo {
|
class CuratedModelInfo {
|
||||||
final String id;
|
final String id;
|
||||||
final String family;
|
final String family;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import 'package:path/path.dart' as p;
|
||||||
/// Operator-managed registry auth token, kept at
|
/// Operator-managed registry auth token, kept at
|
||||||
/// `~/.chain/registry-token` (mode 0600 on Unix). The hub reads
|
/// `~/.chain/registry-token` (mode 0600 on Unix). The hub reads
|
||||||
/// this file at install time when `CHAIN_REGISTRY_TOKEN` is unset
|
/// this file at install time when `CHAIN_REGISTRY_TOKEN` is unset
|
||||||
/// — see `download_to_temp` in `crates/fai_hub/src/lib.rs`.
|
/// — see `download_to_temp` in `crates/chain_hub/src/lib.rs`.
|
||||||
///
|
///
|
||||||
/// Studio writes the file directly so a fresh install never
|
/// Studio writes the file directly so a fresh install never
|
||||||
/// requires the operator to fiddle with shell env vars.
|
/// requires the operator to fiddle with shell env vars.
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
/// Sentinel returned in `_runFai(...).stderr` when no `fai`
|
/// Sentinel returned in `_runFai(...).stderr` when no `fai`
|
||||||
/// binary could be located. UI layers should not display this
|
/// binary could be located. UI layers should not display this
|
||||||
/// verbatim — they check [SystemActions.faiBinaryExists] first
|
/// verbatim — they check [SystemActions.chainBinaryExists] first
|
||||||
/// and render a localized, actionable message instead.
|
/// and render a localized, actionable message instead.
|
||||||
const String kFaiBinaryNotFound = 'fai-binary-not-found';
|
const String kFaiBinaryNotFound = 'fai-binary-not-found';
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ class SystemActions {
|
||||||
|
|
||||||
/// SharedPreferences key for an operator-chosen `fai` binary
|
/// SharedPreferences key for an operator-chosen `fai` binary
|
||||||
/// path (set via the file picker when auto-detection fails).
|
/// path (set via the file picker when auto-detection fails).
|
||||||
static const String _kFaiBinaryPrefKey = 'system.fai_binary_path';
|
static const String _kFaiBinaryPrefKey = 'system.chain_binary_path';
|
||||||
|
|
||||||
/// In-memory cache of the operator override, loaded once at
|
/// In-memory cache of the operator override, loaded once at
|
||||||
/// startup by [loadFaiBinaryOverride]. Null = no override.
|
/// startup by [loadFaiBinaryOverride]. Null = no override.
|
||||||
|
|
@ -71,7 +71,7 @@ class SystemActions {
|
||||||
/// the canonical install dir). UI uses this to decide between
|
/// the canonical install dir). UI uses this to decide between
|
||||||
/// the normal daemon-control affordances and the "locate the
|
/// the normal daemon-control affordances and the "locate the
|
||||||
/// binary / read the install guide" recovery path.
|
/// binary / read the install guide" recovery path.
|
||||||
static bool faiBinaryExists() => _faiExecutable() != null;
|
static bool chainBinaryExists() => _faiExecutable() != null;
|
||||||
|
|
||||||
/// Ask the OS to open [path] in the default handler. On macOS
|
/// Ask the OS to open [path] in the default handler. On macOS
|
||||||
/// this opens text files in TextEdit, configs in the registered
|
/// this opens text files in TextEdit, configs in the registered
|
||||||
|
|
@ -137,7 +137,7 @@ class SystemActions {
|
||||||
|
|
||||||
/// Run a `chain daemon ...` subcommand and surface the captured
|
/// Run a `chain daemon ...` subcommand and surface the captured
|
||||||
/// output. Used by Doctor's "Restart daemon" button.
|
/// output. Used by Doctor's "Restart daemon" button.
|
||||||
static Future<({bool ok, String stdout, String stderr})> faiDaemon(
|
static Future<({bool ok, String stdout, String stderr})> chainDaemon(
|
||||||
List<String> args,
|
List<String> args,
|
||||||
) async {
|
) async {
|
||||||
return _runFai(['daemon', ...args]);
|
return _runFai(['daemon', ...args]);
|
||||||
|
|
@ -145,7 +145,7 @@ class SystemActions {
|
||||||
|
|
||||||
/// Run `chain update apply --channel <c>`. Long-running on a slow
|
/// Run `chain update apply --channel <c>`. Long-running on a slow
|
||||||
/// network — caller should show a spinner.
|
/// network — caller should show a spinner.
|
||||||
static Future<({bool ok, String stdout, String stderr})> faiUpdateApply(
|
static Future<({bool ok, String stdout, String stderr})> chainUpdateApply(
|
||||||
String channel,
|
String channel,
|
||||||
) async {
|
) async {
|
||||||
return _runFai(['update', 'apply', '--channel', channel]);
|
return _runFai(['update', 'apply', '--channel', channel]);
|
||||||
|
|
@ -154,7 +154,7 @@ class SystemActions {
|
||||||
/// Switch the active channel pointer at `~/.chain/current-channel`.
|
/// Switch the active channel pointer at `~/.chain/current-channel`.
|
||||||
/// The CLI also restarts the daemon for the new channel, so the
|
/// The CLI also restarts the daemon for the new channel, so the
|
||||||
/// caller does not need a follow-up restart.
|
/// caller does not need a follow-up restart.
|
||||||
static Future<({bool ok, String stdout, String stderr})> faiChannelSwitch(
|
static Future<({bool ok, String stdout, String stderr})> chainChannelSwitch(
|
||||||
String channel,
|
String channel,
|
||||||
) async {
|
) async {
|
||||||
return _runFai(['channel', 'switch', channel]);
|
return _runFai(['channel', 'switch', channel]);
|
||||||
|
|
@ -164,14 +164,14 @@ class SystemActions {
|
||||||
/// (launchd plist on macOS, systemd-user unit on Linux). On
|
/// (launchd plist on macOS, systemd-user unit on Linux). On
|
||||||
/// Windows the platform CLI returns a "not supported" exit
|
/// Windows the platform CLI returns a "not supported" exit
|
||||||
/// status that the caller surfaces as an error message.
|
/// status that the caller surfaces as an error message.
|
||||||
static Future<({bool ok, String stdout, String stderr})> faiDaemonEnable(
|
static Future<({bool ok, String stdout, String stderr})> chainDaemonEnable(
|
||||||
String channel,
|
String channel,
|
||||||
) async {
|
) async {
|
||||||
return _runFai(['daemon', 'enable', '--channel', channel]);
|
return _runFai(['daemon', 'enable', '--channel', channel]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the autostart unit installed by [faiDaemonEnable].
|
/// Remove the autostart unit installed by [chainDaemonEnable].
|
||||||
static Future<({bool ok, String stdout, String stderr})> faiDaemonDisable(
|
static Future<({bool ok, String stdout, String stderr})> chainDaemonDisable(
|
||||||
String channel,
|
String channel,
|
||||||
) async {
|
) async {
|
||||||
return _runFai(['daemon', 'disable', '--channel', channel]);
|
return _runFai(['daemon', 'disable', '--channel', channel]);
|
||||||
|
|
@ -186,7 +186,7 @@ class SystemActions {
|
||||||
///
|
///
|
||||||
/// Studio's gRPC channel goes down for ~1s while the daemon
|
/// Studio's gRPC channel goes down for ~1s while the daemon
|
||||||
/// restarts — caller should follow up with a reconnect.
|
/// restarts — caller should follow up with a reconnect.
|
||||||
static Future<({bool ok, String stdout, String stderr})> faiReset({
|
static Future<({bool ok, String stdout, String stderr})> chainReset({
|
||||||
bool keepModules = false,
|
bool keepModules = false,
|
||||||
bool keepData = false,
|
bool keepData = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
|
@ -202,9 +202,9 @@ class SystemActions {
|
||||||
final exe = _faiExecutable();
|
final exe = _faiExecutable();
|
||||||
if (exe == null) {
|
if (exe == null) {
|
||||||
// Sentinel, not a user-facing string. Callers detect this
|
// Sentinel, not a user-facing string. Callers detect this
|
||||||
// via `faiBinaryExists()` and render a localized,
|
// via `chainBinaryExists()` and render a localized,
|
||||||
// actionable recovery message (locate binary / install
|
// actionable recovery message (locate binary / install
|
||||||
// guide) instead of CLI jargon like "set FAI_BIN".
|
// guide) instead of CLI jargon like "set CHAIN_BIN".
|
||||||
return (ok: false, stdout: '', stderr: kFaiBinaryNotFound);
|
return (ok: false, stdout: '', stderr: kFaiBinaryNotFound);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
@ -226,7 +226,7 @@ class SystemActions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Locate the `fai` binary. Order: operator override (set via
|
/// Locate the `fai` binary. Order: operator override (set via
|
||||||
/// the file picker), $FAI_BIN, PATH, fallback to the canonical
|
/// the file picker), $CHAIN_BIN, PATH, fallback to the canonical
|
||||||
/// install location under the user's home dir.
|
/// install location under the user's home dir.
|
||||||
static String? _faiExecutable() {
|
static String? _faiExecutable() {
|
||||||
final override = _faiBinaryOverride;
|
final override = _faiBinaryOverride;
|
||||||
|
|
@ -236,7 +236,7 @@ class SystemActions {
|
||||||
return override;
|
return override;
|
||||||
}
|
}
|
||||||
|
|
||||||
final fromEnv = Platform.environment['FAI_BIN'];
|
final fromEnv = Platform.environment['CHAIN_BIN'];
|
||||||
if (fromEnv != null && fromEnv.isNotEmpty && File(fromEnv).existsSync()) {
|
if (fromEnv != null && fromEnv.isNotEmpty && File(fromEnv).existsSync()) {
|
||||||
return fromEnv;
|
return fromEnv;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import 'hub.dart';
|
||||||
|
|
||||||
/// SharedPreferences key for the operator's preferred theme
|
/// SharedPreferences key for the operator's preferred theme
|
||||||
/// plugin. `null` (or absent) means "use Studio's built-in
|
/// plugin. `null` (or absent) means "use Studio's built-in
|
||||||
/// default" (FaiTheme.light / .dark).
|
/// default" (ChainTheme.light / .dark).
|
||||||
const _kThemePluginKey = 'theme_plugin_capability';
|
const _kThemePluginKey = 'theme_plugin_capability';
|
||||||
|
|
||||||
/// One ColorScheme pair as built from a theme-plugin's
|
/// One ColorScheme pair as built from a theme-plugin's
|
||||||
|
|
@ -96,14 +96,14 @@ Future<ThemePluginSchemes> loadThemePluginSchemes(String capability) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a `ThemeData` from a plugin-supplied `ColorScheme`.
|
/// Build a `ThemeData` from a plugin-supplied `ColorScheme`.
|
||||||
/// Delegates to [FaiTheme.fromColorScheme] so plugin-themed
|
/// Delegates to [ChainTheme.fromColorScheme] so plugin-themed
|
||||||
/// builds and built-in themes share one structural shape — same
|
/// builds and built-in themes share one structural shape — same
|
||||||
/// textTheme cascade, same component themes, same `inherit`
|
/// textTheme cascade, same component themes, same `inherit`
|
||||||
/// values on every TextStyle. Without that, Flutter's
|
/// values on every TextStyle. Without that, Flutter's
|
||||||
/// AnimatedDefaultTextStyle lerp blows up when swapping between
|
/// AnimatedDefaultTextStyle lerp blows up when swapping between
|
||||||
/// the two paths.
|
/// the two paths.
|
||||||
ThemeData themeDataFromScheme(ColorScheme scheme) =>
|
ThemeData themeDataFromScheme(ColorScheme scheme) =>
|
||||||
FaiTheme.fromColorScheme(scheme);
|
ChainTheme.fromColorScheme(scheme);
|
||||||
|
|
||||||
/// Translate the plugin's 14 ARGB tokens into a `ColorScheme`.
|
/// Translate the plugin's 14 ARGB tokens into a `ColorScheme`.
|
||||||
/// Order matches the plugin contract:
|
/// Order matches the plugin contract:
|
||||||
|
|
|
||||||
|
|
@ -1539,13 +1539,13 @@
|
||||||
"detail": {"type": "String"}
|
"detail": {"type": "String"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"faiBinaryNotFound": "Das Programm `fai` wurde auf diesem Rechner nicht gefunden.",
|
"chainBinaryNotFound": "Das Programm `chain` wurde auf diesem Rechner nicht gefunden.",
|
||||||
"faiBinaryNotFoundHint": "Installiere die Ch∆In-Plattform oder verweise Studio auf eine vorhandene `fai`-Datei.",
|
"chainBinaryNotFoundHint": "Installiere die Ch∆In-Plattform oder verweise Studio auf eine vorhandene `chain`-Datei.",
|
||||||
"faiBinaryLocateButton": "`fai`-Programm suchen …",
|
"chainBinaryLocateButton": "`chain`-Programm suchen …",
|
||||||
"faiBinaryInstallDocsButton": "Installationsanleitung öffnen",
|
"chainBinaryInstallDocsButton": "Installationsanleitung öffnen",
|
||||||
"faiBinaryPickerTitle": "`fai`-Programm auswählen",
|
"chainBinaryPickerTitle": "`chain`-Programm auswählen",
|
||||||
"faiBinarySetOk": "Verwende `fai` unter {path}",
|
"chainBinarySetOk": "Verwende `chain` unter {path}",
|
||||||
"@faiBinarySetOk": {
|
"@chainBinarySetOk": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"path": {"type": "String"}
|
"path": {"type": "String"}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1542,13 +1542,13 @@
|
||||||
"detail": {"type": "String"}
|
"detail": {"type": "String"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"faiBinaryNotFound": "Could not find the `fai` program on this machine.",
|
"chainBinaryNotFound": "Could not find the `chain` program on this machine.",
|
||||||
"faiBinaryNotFoundHint": "Install the Ch∆In platform, or point Studio at an existing `fai` binary.",
|
"chainBinaryNotFoundHint": "Install the Ch∆In platform, or point Studio at an existing `chain` binary.",
|
||||||
"faiBinaryLocateButton": "Locate `fai` binary…",
|
"chainBinaryLocateButton": "Locate `chain` binary…",
|
||||||
"faiBinaryInstallDocsButton": "Open install guide",
|
"chainBinaryInstallDocsButton": "Open install guide",
|
||||||
"faiBinaryPickerTitle": "Select the `fai` binary",
|
"chainBinaryPickerTitle": "Select the `chain` binary",
|
||||||
"faiBinarySetOk": "Using `fai` binary at {path}",
|
"chainBinarySetOk": "Using `chain` binary at {path}",
|
||||||
"@faiBinarySetOk": {
|
"@chainBinarySetOk": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"path": {"type": "String"}
|
"path": {"type": "String"}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4328,41 +4328,41 @@ abstract class AppLocalizations {
|
||||||
/// **'Could not start daemon: {detail}'**
|
/// **'Could not start daemon: {detail}'**
|
||||||
String daemonStartFailed(String detail);
|
String daemonStartFailed(String detail);
|
||||||
|
|
||||||
/// No description provided for @faiBinaryNotFound.
|
/// No description provided for @chainBinaryNotFound.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Could not find the `fai` program on this machine.'**
|
/// **'Could not find the `chain` program on this machine.'**
|
||||||
String get faiBinaryNotFound;
|
String get chainBinaryNotFound;
|
||||||
|
|
||||||
/// No description provided for @faiBinaryNotFoundHint.
|
/// No description provided for @chainBinaryNotFoundHint.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Install the Ch∆In platform, or point Studio at an existing `fai` binary.'**
|
/// **'Install the Ch∆In platform, or point Studio at an existing `chain` binary.'**
|
||||||
String get faiBinaryNotFoundHint;
|
String get chainBinaryNotFoundHint;
|
||||||
|
|
||||||
/// No description provided for @faiBinaryLocateButton.
|
/// No description provided for @chainBinaryLocateButton.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Locate `fai` binary…'**
|
/// **'Locate `chain` binary…'**
|
||||||
String get faiBinaryLocateButton;
|
String get chainBinaryLocateButton;
|
||||||
|
|
||||||
/// No description provided for @faiBinaryInstallDocsButton.
|
/// No description provided for @chainBinaryInstallDocsButton.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Open install guide'**
|
/// **'Open install guide'**
|
||||||
String get faiBinaryInstallDocsButton;
|
String get chainBinaryInstallDocsButton;
|
||||||
|
|
||||||
/// No description provided for @faiBinaryPickerTitle.
|
/// No description provided for @chainBinaryPickerTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Select the `fai` binary'**
|
/// **'Select the `chain` binary'**
|
||||||
String get faiBinaryPickerTitle;
|
String get chainBinaryPickerTitle;
|
||||||
|
|
||||||
/// No description provided for @faiBinarySetOk.
|
/// No description provided for @chainBinarySetOk.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Using `fai` binary at {path}'**
|
/// **'Using `chain` binary at {path}'**
|
||||||
String faiBinarySetOk(String path);
|
String chainBinarySetOk(String path);
|
||||||
|
|
||||||
/// No description provided for @sysAiFixParse.
|
/// No description provided for @sysAiFixParse.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -2535,25 +2535,25 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get faiBinaryNotFound =>
|
String get chainBinaryNotFound =>
|
||||||
'Das Programm `fai` wurde auf diesem Rechner nicht gefunden.';
|
'Das Programm `chain` wurde auf diesem Rechner nicht gefunden.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get faiBinaryNotFoundHint =>
|
String get chainBinaryNotFoundHint =>
|
||||||
'Installiere die Ch∆In-Plattform oder verweise Studio auf eine vorhandene `fai`-Datei.';
|
'Installiere die Ch∆In-Plattform oder verweise Studio auf eine vorhandene `chain`-Datei.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get faiBinaryLocateButton => '`fai`-Programm suchen …';
|
String get chainBinaryLocateButton => '`chain`-Programm suchen …';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get faiBinaryInstallDocsButton => 'Installationsanleitung öffnen';
|
String get chainBinaryInstallDocsButton => 'Installationsanleitung öffnen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get faiBinaryPickerTitle => '`fai`-Programm auswählen';
|
String get chainBinaryPickerTitle => '`chain`-Programm auswählen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String faiBinarySetOk(String path) {
|
String chainBinarySetOk(String path) {
|
||||||
return 'Verwende `fai` unter $path';
|
return 'Verwende `chain` unter $path';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -2538,25 +2538,25 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get faiBinaryNotFound =>
|
String get chainBinaryNotFound =>
|
||||||
'Could not find the `fai` program on this machine.';
|
'Could not find the `chain` program on this machine.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get faiBinaryNotFoundHint =>
|
String get chainBinaryNotFoundHint =>
|
||||||
'Install the Ch∆In platform, or point Studio at an existing `fai` binary.';
|
'Install the Ch∆In platform, or point Studio at an existing `chain` binary.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get faiBinaryLocateButton => 'Locate `fai` binary…';
|
String get chainBinaryLocateButton => 'Locate `chain` binary…';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get faiBinaryInstallDocsButton => 'Open install guide';
|
String get chainBinaryInstallDocsButton => 'Open install guide';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get faiBinaryPickerTitle => 'Select the `fai` binary';
|
String get chainBinaryPickerTitle => 'Select the `chain` binary';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String faiBinarySetOk(String path) {
|
String chainBinarySetOk(String path) {
|
||||||
return 'Using `fai` binary at $path';
|
return 'Using `chain` binary at $path';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
110
lib/main.dart
110
lib/main.dart
|
|
@ -8,7 +8,7 @@ import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'data/fai_log.dart';
|
import 'data/chain_log.dart';
|
||||||
import 'data/hub.dart';
|
import 'data/hub.dart';
|
||||||
import 'data/system_actions.dart';
|
import 'data/system_actions.dart';
|
||||||
import 'data/theme_plugin.dart';
|
import 'data/theme_plugin.dart';
|
||||||
|
|
@ -22,7 +22,7 @@ import 'pages/store.dart';
|
||||||
import 'pages/welcome.dart';
|
import 'pages/welcome.dart';
|
||||||
import 'theme/theme.dart';
|
import 'theme/theme.dart';
|
||||||
import 'theme/tokens.dart';
|
import 'theme/tokens.dart';
|
||||||
import 'widgets/fai_search_palette.dart';
|
import 'widgets/chain_search_palette.dart';
|
||||||
import 'widgets/widgets.dart';
|
import 'widgets/widgets.dart';
|
||||||
|
|
||||||
/// Studio's own build version. Bump on every UI commit so the
|
/// Studio's own build version. Bump on every UI commit so the
|
||||||
|
|
@ -55,7 +55,7 @@ class StudioApp extends StatefulWidget {
|
||||||
|
|
||||||
/// Capability name of the active theme plugin (e.g.
|
/// Capability name of the active theme plugin (e.g.
|
||||||
/// `studio.theme.solarized`) or `null` for the built-in
|
/// `studio.theme.solarized`) or `null` for the built-in
|
||||||
/// FaiTheme.
|
/// ChainTheme.
|
||||||
final String? initialThemePlugin;
|
final String? initialThemePlugin;
|
||||||
|
|
||||||
const StudioApp({
|
const StudioApp({
|
||||||
|
|
@ -89,7 +89,7 @@ class StudioAppState extends State<StudioApp> {
|
||||||
|
|
||||||
/// Capability name of the active `studio.theme.*` plugin
|
/// Capability name of the active `studio.theme.*` plugin
|
||||||
/// (e.g. `studio.theme.solarized`) or `null` for
|
/// (e.g. `studio.theme.solarized`) or `null` for
|
||||||
/// FaiTheme's built-in palette. Flipping this triggers a
|
/// ChainTheme's built-in palette. Flipping this triggers a
|
||||||
/// re-fetch of the plugin's ColorSchemes; the MaterialApp
|
/// re-fetch of the plugin's ColorSchemes; the MaterialApp
|
||||||
/// rebuilds with the new themes.
|
/// rebuilds with the new themes.
|
||||||
late final ValueNotifier<String?> themePluginNotifier;
|
late final ValueNotifier<String?> themePluginNotifier;
|
||||||
|
|
@ -188,7 +188,7 @@ class StudioAppState extends State<StudioApp> {
|
||||||
// own "applied" snackbar is replaced upstream by the
|
// own "applied" snackbar is replaced upstream by the
|
||||||
// friendlier failure message via [themePluginFailed].
|
// friendlier failure message via [themePluginFailed].
|
||||||
// ignore: discarded_futures
|
// ignore: discarded_futures
|
||||||
FaiLog.instance.error(
|
ChainLog.instance.error(
|
||||||
'theme.plugin.load',
|
'theme.plugin.load',
|
||||||
e,
|
e,
|
||||||
context: 'capability=$capability',
|
context: 'capability=$capability',
|
||||||
|
|
@ -217,8 +217,8 @@ class StudioAppState extends State<StudioApp> {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Ch∆In Studio',
|
title: 'Ch∆In Studio',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: p.light ?? FaiTheme.light(),
|
theme: p.light ?? ChainTheme.light(),
|
||||||
darkTheme: p.dark ?? FaiTheme.dark(),
|
darkTheme: p.dark ?? ChainTheme.dark(),
|
||||||
themeMode: _flutterMode(mode),
|
themeMode: _flutterMode(mode),
|
||||||
locale: locale,
|
locale: locale,
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
|
@ -268,7 +268,7 @@ class StudioShellState extends State<StudioShell> {
|
||||||
/// row and the WelcomePage hero CTA.
|
/// row and the WelcomePage hero CTA.
|
||||||
Future<void> startDaemon() async {
|
Future<void> startDaemon() async {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
final r = await SystemActions.faiDaemon(['start']);
|
final r = await SystemActions.chainDaemon(['start']);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final detail = r.stderr.trim().isEmpty ? r.stdout.trim() : r.stderr.trim();
|
final detail = r.stderr.trim().isEmpty ? r.stdout.trim() : r.stderr.trim();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
|
@ -325,7 +325,7 @@ class StudioShellState extends State<StudioShell> {
|
||||||
// (permissions, on-disk path, uninstall) now lives inside
|
// (permissions, on-disk path, uninstall) now lives inside
|
||||||
// the Store's detail sheet, and the Store with the
|
// the Store's detail sheet, and the Store with the
|
||||||
// "Installed" filter covers the listing role. Cmd+K still
|
// "Installed" filter covers the listing role. Cmd+K still
|
||||||
// opens FaiModuleSheet for quick info.
|
// opens ChainModuleSheet for quick info.
|
||||||
// Flows destination IS the editor as of 0.52.0 — the
|
// Flows destination IS the editor as of 0.52.0 — the
|
||||||
// graph / text / run tabs all live behind one icon. The
|
// graph / text / run tabs all live behind one icon. The
|
||||||
// separate "flow-editor" destination is gone; the Cmd+K
|
// separate "flow-editor" destination is gone; the Cmd+K
|
||||||
|
|
@ -413,9 +413,9 @@ class StudioShellState extends State<StudioShell> {
|
||||||
void _openSearchPalette() {
|
void _openSearchPalette() {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
final pagesGroup = l.searchGroupPages;
|
final pagesGroup = l.searchGroupPages;
|
||||||
final hits = <FaiSearchHit>[
|
final hits = <ChainSearchHit>[
|
||||||
for (var i = 0; i < _pages.length; i++)
|
for (var i = 0; i < _pages.length; i++)
|
||||||
FaiSearchHit(
|
ChainSearchHit(
|
||||||
label: _pages[i].labelOf(context),
|
label: _pages[i].labelOf(context),
|
||||||
hint: l.searchPageHint(i + 1),
|
hint: l.searchPageHint(i + 1),
|
||||||
icon: _pages[i].icon,
|
icon: _pages[i].icon,
|
||||||
|
|
@ -425,18 +425,18 @@ class StudioShellState extends State<StudioShell> {
|
||||||
setState(() => _selectedIndex = i);
|
setState(() => _selectedIndex = i);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
FaiSearchHit(
|
ChainSearchHit(
|
||||||
label: l.searchSettingsLabel,
|
label: l.searchSettingsLabel,
|
||||||
hint: l.searchSettingsHint,
|
hint: l.searchSettingsHint,
|
||||||
icon: Icons.settings_outlined,
|
icon: Icons.settings_outlined,
|
||||||
group: pagesGroup,
|
group: pagesGroup,
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
FaiSettingsDialog.show(context);
|
ChainSettingsDialog.show(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
FaiSearchPalette.show(context, staticHits: hits);
|
ChainSearchPalette.show(context, staticHits: hits);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -481,7 +481,7 @@ class StudioShellState extends State<StudioShell> {
|
||||||
),
|
),
|
||||||
_OpenSettingsIntent: CallbackAction<_OpenSettingsIntent>(
|
_OpenSettingsIntent: CallbackAction<_OpenSettingsIntent>(
|
||||||
onInvoke: (_) {
|
onInvoke: (_) {
|
||||||
FaiSettingsDialog.show(context);
|
ChainSettingsDialog.show(context);
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -513,13 +513,13 @@ class StudioShellState extends State<StudioShell> {
|
||||||
if (_hubUnreachable)
|
if (_hubUnreachable)
|
||||||
_HubUnreachableBanner(
|
_HubUnreachableBanner(
|
||||||
endpoint: HubService.instance.endpointLabel,
|
endpoint: HubService.instance.endpointLabel,
|
||||||
onOpenSettings: () => FaiSettingsDialog.show(context),
|
onOpenSettings: () => ChainSettingsDialog.show(context),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
duration: FaiMotion.base,
|
duration: ChainMotion.base,
|
||||||
switchInCurve: FaiMotion.easing,
|
switchInCurve: ChainMotion.easing,
|
||||||
switchOutCurve: FaiMotion.easing,
|
switchOutCurve: ChainMotion.easing,
|
||||||
transitionBuilder: (child, anim) => FadeTransition(
|
transitionBuilder: (child, anim) => FadeTransition(
|
||||||
opacity: anim,
|
opacity: anim,
|
||||||
child: SlideTransition(
|
child: SlideTransition(
|
||||||
|
|
@ -569,8 +569,8 @@ class _HubUnreachableBanner extends StatelessWidget {
|
||||||
color: theme.colorScheme.errorContainer,
|
color: theme.colorScheme.errorContainer,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.lg,
|
horizontal: ChainSpace.lg,
|
||||||
vertical: FaiSpace.sm,
|
vertical: ChainSpace.sm,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -579,7 +579,7 @@ class _HubUnreachableBanner extends StatelessWidget {
|
||||||
size: 18,
|
size: 18,
|
||||||
color: theme.colorScheme.onErrorContainer,
|
color: theme.colorScheme.onErrorContainer,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
l.hubUnreachableBanner(endpoint),
|
l.hubUnreachableBanner(endpoint),
|
||||||
|
|
@ -588,7 +588,7 @@ class _HubUnreachableBanner extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: onOpenSettings,
|
onPressed: onOpenSettings,
|
||||||
child: Text(l.hubUnreachableOpenSettings),
|
child: Text(l.hubUnreachableOpenSettings),
|
||||||
|
|
@ -674,8 +674,8 @@ class _SidebarState extends State<_Sidebar>
|
||||||
// reveal is a deliberate, eased gesture, not a flicker.
|
// reveal is a deliberate, eased gesture, not a flicker.
|
||||||
// The collapse runs faster (reverseDuration) so dismissing
|
// The collapse runs faster (reverseDuration) so dismissing
|
||||||
// the rail feels crisp rather than sluggish.
|
// the rail feels crisp rather than sluggish.
|
||||||
duration: FaiMotion.slow,
|
duration: ChainMotion.slow,
|
||||||
reverseDuration: FaiMotion.base,
|
reverseDuration: ChainMotion.base,
|
||||||
value: 0,
|
value: 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -688,7 +688,7 @@ class _SidebarState extends State<_Sidebar>
|
||||||
|
|
||||||
Future<void> _startDaemon(BuildContext context) async {
|
Future<void> _startDaemon(BuildContext context) async {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
final r = await SystemActions.faiDaemon(['start']);
|
final r = await SystemActions.chainDaemon(['start']);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
final detail = r.stderr.trim().isEmpty ? r.stdout.trim() : r.stderr.trim();
|
final detail = r.stderr.trim().isEmpty ? r.stdout.trim() : r.stderr.trim();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
|
@ -704,8 +704,8 @@ class _SidebarState extends State<_Sidebar>
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final mode = widget.connected == true
|
final mode = widget.connected == true
|
||||||
? FaiDeltaMode.live
|
? ChainDeltaMode.live
|
||||||
: FaiDeltaMode.idle;
|
: ChainDeltaMode.idle;
|
||||||
final hasChannel =
|
final hasChannel =
|
||||||
widget.activeChannel != null && widget.activeChannel!.isNotEmpty;
|
widget.activeChannel != null && widget.activeChannel!.isNotEmpty;
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
|
|
@ -721,7 +721,7 @@ class _SidebarState extends State<_Sidebar>
|
||||||
return Container(
|
return Container(
|
||||||
width: width,
|
width: width,
|
||||||
color: theme.colorScheme.surfaceContainerLow,
|
color: theme.colorScheme.surfaceContainerLow,
|
||||||
padding: const EdgeInsets.symmetric(vertical: FaiSpace.xl),
|
padding: const EdgeInsets.symmetric(vertical: ChainSpace.xl),
|
||||||
child: ClipRect(
|
child: ClipRect(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -730,7 +730,7 @@ class _SidebarState extends State<_Sidebar>
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: _brandRowH,
|
height: _brandRowH,
|
||||||
child: _sidebarRow(
|
child: _sidebarRow(
|
||||||
leading: FaiDeltaMark(
|
leading: ChainDeltaMark(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
mode: mode,
|
mode: mode,
|
||||||
),
|
),
|
||||||
|
|
@ -796,7 +796,7 @@ class _SidebarState extends State<_Sidebar>
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
|
|
@ -868,7 +868,7 @@ class _SidebarState extends State<_Sidebar>
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: t,
|
opacity: t,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: FaiSpace.md),
|
padding: const EdgeInsets.only(right: ChainSpace.md),
|
||||||
child: label,
|
child: label,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -889,7 +889,7 @@ class _SidebarState extends State<_Sidebar>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openSettings(BuildContext context) async {
|
Future<void> _openSettings(BuildContext context) async {
|
||||||
final saved = await FaiSettingsDialog.show(context);
|
final saved = await ChainSettingsDialog.show(context);
|
||||||
if (saved && context.mounted) {
|
if (saved && context.mounted) {
|
||||||
// Force a sidebar rebuild so the new endpoint shows in
|
// Force a sidebar rebuild so the new endpoint shows in
|
||||||
// the connection pill.
|
// the connection pill.
|
||||||
|
|
@ -911,7 +911,7 @@ class _ChannelPill extends StatelessWidget {
|
||||||
case 'production':
|
case 'production':
|
||||||
return cs.error;
|
return cs.error;
|
||||||
case 'beta':
|
case 'beta':
|
||||||
return FaiColors.warning;
|
return ChainColors.warning;
|
||||||
case 'dev':
|
case 'dev':
|
||||||
return cs.primary;
|
return cs.primary;
|
||||||
case 'local':
|
case 'local':
|
||||||
|
|
@ -943,17 +943,17 @@ class _ChannelPill extends StatelessWidget {
|
||||||
message: l.sidebarChannelTooltip,
|
message: l.sidebarChannelTooltip,
|
||||||
child: Material(
|
child: Material(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
onTap: () => FaiSettingsDialog.show(context),
|
onTap: () => ChainSettingsDialog.show(context),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.sm,
|
horizontal: ChainSpace.sm,
|
||||||
vertical: 4,
|
vertical: 4,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: accent.withValues(alpha: 0.4)),
|
border: Border.all(color: accent.withValues(alpha: 0.4)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -963,7 +963,7 @@ class _ChannelPill extends StatelessWidget {
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
channel,
|
channel,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 10,
|
size: 10,
|
||||||
weight: FontWeight.w600,
|
weight: FontWeight.w600,
|
||||||
color: accent,
|
color: accent,
|
||||||
|
|
@ -980,7 +980,7 @@ class _ChannelPill extends StatelessWidget {
|
||||||
|
|
||||||
/// Mini-version of [_ConnectionLabel] used when the sidebar is
|
/// Mini-version of [_ConnectionLabel] used when the sidebar is
|
||||||
/// collapsed. A 10px dot in the same green/red/amber tonality
|
/// collapsed. A 10px dot in the same green/red/amber tonality
|
||||||
/// the FaiDeltaMark uses for live / idle / unknown.
|
/// the ChainDeltaMark uses for live / idle / unknown.
|
||||||
class _CollapsedConnectionDot extends StatelessWidget {
|
class _CollapsedConnectionDot extends StatelessWidget {
|
||||||
final bool? connected;
|
final bool? connected;
|
||||||
const _CollapsedConnectionDot({required this.connected});
|
const _CollapsedConnectionDot({required this.connected});
|
||||||
|
|
@ -989,10 +989,10 @@ class _CollapsedConnectionDot extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final color = connected == true
|
final color = connected == true
|
||||||
? FaiColors.success
|
? ChainColors.success
|
||||||
: connected == false
|
: connected == false
|
||||||
? theme.colorScheme.error
|
? theme.colorScheme.error
|
||||||
: FaiColors.warning;
|
: ChainColors.warning;
|
||||||
return Container(
|
return Container(
|
||||||
width: 10,
|
width: 10,
|
||||||
height: 10,
|
height: 10,
|
||||||
|
|
@ -1025,7 +1025,7 @@ class _CollapsedChannelChip extends StatelessWidget {
|
||||||
case 'production':
|
case 'production':
|
||||||
return cs.error;
|
return cs.error;
|
||||||
case 'beta':
|
case 'beta':
|
||||||
return FaiColors.warning;
|
return ChainColors.warning;
|
||||||
case 'dev':
|
case 'dev':
|
||||||
return cs.primary;
|
return cs.primary;
|
||||||
case 'local':
|
case 'local':
|
||||||
|
|
@ -1053,7 +1053,7 @@ class _CollapsedChannelChip extends StatelessWidget {
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(
|
child: Text(
|
||||||
_letter,
|
_letter,
|
||||||
style: FaiTheme.mono(size: 10, weight: FontWeight.w700, color: accent),
|
style: ChainTheme.mono(size: 10, weight: FontWeight.w700, color: accent),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1080,7 +1080,7 @@ class _BrandLabel extends StatelessWidget {
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
'v$kStudioVersion',
|
'v$kStudioVersion',
|
||||||
style: FaiTheme.mono(size: 10, color: theme.colorScheme.primary),
|
style: ChainTheme.mono(size: 10, color: theme.colorScheme.primary),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
|
@ -1133,7 +1133,7 @@ class _ConnectionLabel extends StatelessWidget {
|
||||||
endpoint,
|
endpoint,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 10,
|
size: 10,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -1251,7 +1251,7 @@ class _SidebarItemState extends State<_SidebarItem> {
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: widget.t,
|
opacity: widget.t,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: FaiSpace.md),
|
padding: const EdgeInsets.only(right: ChainSpace.md),
|
||||||
child: Text(
|
child: Text(
|
||||||
label,
|
label,
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
|
|
@ -1278,19 +1278,19 @@ class _SidebarItemState extends State<_SidebarItem> {
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: widget.onTap,
|
onTap: widget.onTap,
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: FaiMotion.fast,
|
duration: ChainMotion.fast,
|
||||||
// No horizontal padding — the icon column anchors
|
// No horizontal padding — the icon column anchors
|
||||||
// the left. The bg highlight spans the rail's
|
// the left. The bg highlight spans the rail's
|
||||||
// current width (typical nav-rail behaviour).
|
// current width (typical nav-rail behaviour).
|
||||||
padding: const EdgeInsets.symmetric(vertical: FaiSpace.md),
|
padding: const EdgeInsets.symmetric(vertical: ChainSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: bg,
|
color: bg,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
// Lift only the selected item — a whisper of depth
|
// Lift only the selected item — a whisper of depth
|
||||||
// marks "you are here" without competing with the
|
// marks "you are here" without competing with the
|
||||||
// accent fill.
|
// accent fill.
|
||||||
boxShadow: widget.selected
|
boxShadow: widget.selected
|
||||||
? FaiElevation.low(theme.brightness)
|
? ChainElevation.low(theme.brightness)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
child: content,
|
child: content,
|
||||||
|
|
@ -1348,7 +1348,7 @@ class _Footer extends StatelessWidget {
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: t,
|
opacity: t,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: FaiSpace.sm),
|
padding: const EdgeInsets.only(right: ChainSpace.sm),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
_ThemeToggle(),
|
_ThemeToggle(),
|
||||||
|
|
@ -1515,7 +1515,7 @@ class _SidebarClockState extends State<_SidebarClock> {
|
||||||
message: tooltip,
|
message: tooltip,
|
||||||
child: Text(
|
child: Text(
|
||||||
'$hh:$mm:$ss',
|
'$hh:$mm:$ss',
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.85),
|
color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.85),
|
||||||
),
|
),
|
||||||
|
|
@ -1553,7 +1553,7 @@ class _LanguageToggle extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
isDe ? 'DE' : 'EN',
|
isDe ? 'DE' : 'EN',
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
weight: FontWeight.w600,
|
weight: FontWeight.w600,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ class _ApprovalsPageState extends State<ApprovalsPage>
|
||||||
tooltip: AppLocalizations.of(context)!.approvalsReloadTooltip,
|
tooltip: AppLocalizations.of(context)!.approvalsReloadTooltip,
|
||||||
onPressed: _refresh,
|
onPressed: _refresh,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: TabBarView(
|
body: TabBarView(
|
||||||
|
|
@ -288,7 +288,7 @@ class _PendingList extends StatelessWidget {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return FaiEmptyState(
|
return ChainEmptyState(
|
||||||
icon: Icons.cloud_off_outlined,
|
icon: Icons.cloud_off_outlined,
|
||||||
iconColor: theme.colorScheme.error,
|
iconColor: theme.colorScheme.error,
|
||||||
title: l.hubUnreachable,
|
title: l.hubUnreachable,
|
||||||
|
|
@ -301,7 +301,7 @@ class _PendingList extends StatelessWidget {
|
||||||
}
|
}
|
||||||
final pending = snapshot.data ?? [];
|
final pending = snapshot.data ?? [];
|
||||||
if (pending.isEmpty) {
|
if (pending.isEmpty) {
|
||||||
return FaiEmptyState(
|
return ChainEmptyState(
|
||||||
icon: Icons.task_alt_outlined,
|
icon: Icons.task_alt_outlined,
|
||||||
title: l.approvalsInboxZero,
|
title: l.approvalsInboxZero,
|
||||||
hint: l.approvalsInboxHint,
|
hint: l.approvalsInboxHint,
|
||||||
|
|
@ -314,15 +314,15 @@ class _PendingList extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
ListView.separated(
|
ListView.separated(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
FaiSpace.xl,
|
ChainSpace.xl,
|
||||||
FaiSpace.xl,
|
ChainSpace.xl,
|
||||||
FaiSpace.xl,
|
ChainSpace.xl,
|
||||||
// Keep the last card clear of the floating
|
// Keep the last card clear of the floating
|
||||||
// batch action bar.
|
// batch action bar.
|
||||||
selected.isEmpty ? FaiSpace.xl : 96.0,
|
selected.isEmpty ? ChainSpace.xl : 96.0,
|
||||||
),
|
),
|
||||||
itemCount: pending.length,
|
itemCount: pending.length,
|
||||||
separatorBuilder: (_, _) => const SizedBox(height: FaiSpace.md),
|
separatorBuilder: (_, _) => const SizedBox(height: ChainSpace.md),
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
final a = pending[i];
|
final a = pending[i];
|
||||||
final isSelected = selectedIds.contains(a.id);
|
final isSelected = selectedIds.contains(a.id);
|
||||||
|
|
@ -337,9 +337,9 @@ class _PendingList extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (selected.isNotEmpty)
|
if (selected.isNotEmpty)
|
||||||
Positioned(
|
Positioned(
|
||||||
left: FaiSpace.xl,
|
left: ChainSpace.xl,
|
||||||
right: FaiSpace.xl,
|
right: ChainSpace.xl,
|
||||||
bottom: FaiSpace.lg,
|
bottom: ChainSpace.lg,
|
||||||
child: _BatchActionBar(
|
child: _BatchActionBar(
|
||||||
selectedCount: selected.length,
|
selectedCount: selected.length,
|
||||||
totalCount: pending.length,
|
totalCount: pending.length,
|
||||||
|
|
@ -387,15 +387,15 @@ class _BatchActionBar extends StatelessWidget {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return Material(
|
return Material(
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.lg,
|
horizontal: ChainSpace.lg,
|
||||||
vertical: FaiSpace.md,
|
vertical: ChainSpace.md,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -405,14 +405,14 @@ class _BatchActionBar extends StatelessWidget {
|
||||||
size: 18,
|
size: 18,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
l.approvalsBatchSelected(selectedCount),
|
l.approvalsBatchSelected(selectedCount),
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
if (selectedCount < totalCount)
|
if (selectedCount < totalCount)
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: inFlight ? null : onSelectAll,
|
onPressed: inFlight ? null : onSelectAll,
|
||||||
|
|
@ -434,7 +434,7 @@ class _BatchActionBar extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: inFlight ? null : onApprove,
|
onPressed: inFlight ? null : onApprove,
|
||||||
icon: inFlight
|
icon: inFlight
|
||||||
|
|
@ -470,7 +470,7 @@ class _HistoryList extends StatelessWidget {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return FaiEmptyState(
|
return ChainEmptyState(
|
||||||
icon: Icons.cloud_off_outlined,
|
icon: Icons.cloud_off_outlined,
|
||||||
iconColor: theme.colorScheme.error,
|
iconColor: theme.colorScheme.error,
|
||||||
title: l.hubUnreachable,
|
title: l.hubUnreachable,
|
||||||
|
|
@ -483,16 +483,16 @@ class _HistoryList extends StatelessWidget {
|
||||||
}
|
}
|
||||||
final decided = snapshot.data ?? [];
|
final decided = snapshot.data ?? [];
|
||||||
if (decided.isEmpty) {
|
if (decided.isEmpty) {
|
||||||
return FaiEmptyState(
|
return ChainEmptyState(
|
||||||
icon: Icons.history,
|
icon: Icons.history,
|
||||||
title: l.approvalsHistoryEmpty,
|
title: l.approvalsHistoryEmpty,
|
||||||
hint: l.approvalsHistoryEmptyHint,
|
hint: l.approvalsHistoryEmptyHint,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
padding: const EdgeInsets.all(FaiSpace.xl),
|
padding: const EdgeInsets.all(ChainSpace.xl),
|
||||||
itemCount: decided.length,
|
itemCount: decided.length,
|
||||||
separatorBuilder: (_, _) => const SizedBox(height: FaiSpace.xs),
|
separatorBuilder: (_, _) => const SizedBox(height: ChainSpace.xs),
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
final a = decided[i];
|
final a = decided[i];
|
||||||
return _HistoryRow(record: a);
|
return _HistoryRow(record: a);
|
||||||
|
|
@ -522,7 +522,7 @@ class _ApprovalCard extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return FaiCard(
|
return ChainCard(
|
||||||
accentTop: true,
|
accentTop: true,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -534,13 +534,13 @@ class _ApprovalCard extends StatelessWidget {
|
||||||
onChanged: (_) => onToggleSelected(),
|
onChanged: (_) => onToggleSelected(),
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.xs),
|
const SizedBox(width: ChainSpace.xs),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: l.approvalsPillPending,
|
label: l.approvalsPillPending,
|
||||||
tone: FaiPillTone.warning,
|
tone: ChainPillTone.warning,
|
||||||
icon: Icons.pending_outlined,
|
icon: Icons.pending_outlined,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${approval.flowName} › ${approval.stepId}',
|
'${approval.flowName} › ${approval.stepId}',
|
||||||
|
|
@ -550,17 +550,17 @@ class _ApprovalCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (approval.expiresAt != null)
|
if (approval.expiresAt != null)
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: _expiresInLabel(context, approval.expiresAt!),
|
label: _expiresInLabel(context, approval.expiresAt!),
|
||||||
tone: FaiPillTone.neutral,
|
tone: ChainPillTone.neutral,
|
||||||
icon: Icons.schedule,
|
icon: Icons.schedule,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: ChainSpace.lg),
|
||||||
Text(approval.prompt, style: theme.textTheme.bodyLarge),
|
Text(approval.prompt, style: theme.textTheme.bodyLarge),
|
||||||
if (approval.payloadPreview != null) ...[
|
if (approval.payloadPreview != null) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 4),
|
padding: const EdgeInsets.only(bottom: 4),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
@ -574,27 +574,27 @@ class _ApprovalCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.md),
|
padding: const EdgeInsets.all(ChainSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
_prettyPreview(approval.payloadPreview!),
|
_prettyPreview(approval.payloadPreview!),
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: ChainSpace.lg),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
approval.id,
|
approval.id,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 10,
|
size: 10,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -611,7 +611,7 @@ class _ApprovalCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: onApprove,
|
onPressed: onApprove,
|
||||||
icon: const Icon(Icons.check, size: 16),
|
icon: const Icon(Icons.check, size: 16),
|
||||||
|
|
@ -663,21 +663,21 @@ class _HistoryRow extends StatelessWidget {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => _HistoryDialog(record: record),
|
builder: (_) => _HistoryDialog(record: record),
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.sm,
|
horizontal: ChainSpace.sm,
|
||||||
vertical: FaiSpace.sm,
|
vertical: ChainSpace.sm,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
FaiPill(label: label, tone: tone, icon: icon),
|
ChainPill(label: label, tone: tone, icon: icon),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 150,
|
width: 150,
|
||||||
child: Text(
|
child: Text(
|
||||||
timeStr,
|
timeStr,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -693,7 +693,7 @@ class _HistoryRow extends StatelessWidget {
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
if (record.decidedBy.isNotEmpty)
|
if (record.decidedBy.isNotEmpty)
|
||||||
Text(
|
Text(
|
||||||
record.decidedBy,
|
record.decidedBy,
|
||||||
|
|
@ -707,24 +707,24 @@ class _HistoryRow extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static (FaiPillTone, IconData, String) _statusPresentation(
|
static (ChainPillTone, IconData, String) _statusPresentation(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
String status,
|
String status,
|
||||||
) {
|
) {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'approved':
|
case 'approved':
|
||||||
return (FaiPillTone.success, Icons.check, l.approvalsPillApproved);
|
return (ChainPillTone.success, Icons.check, l.approvalsPillApproved);
|
||||||
case 'rejected':
|
case 'rejected':
|
||||||
return (FaiPillTone.danger, Icons.close, l.approvalsPillRejected);
|
return (ChainPillTone.danger, Icons.close, l.approvalsPillRejected);
|
||||||
case 'expired':
|
case 'expired':
|
||||||
return (
|
return (
|
||||||
FaiPillTone.neutral,
|
ChainPillTone.neutral,
|
||||||
Icons.timer_off_outlined,
|
Icons.timer_off_outlined,
|
||||||
l.approvalsPillExpired,
|
l.approvalsPillExpired,
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (FaiPillTone.neutral, Icons.help_outline, status);
|
return (ChainPillTone.neutral, Icons.help_outline, status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -743,8 +743,8 @@ class _HistoryDialog extends StatelessWidget {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
FaiPill(label: label, tone: tone, icon: icon),
|
ChainPill(label: label, tone: tone, icon: icon),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${record.flowName} › ${record.stepId}',
|
'${record.flowName} › ${record.stepId}',
|
||||||
|
|
@ -779,7 +779,7 @@ class _HistoryDialog extends StatelessWidget {
|
||||||
AppLocalizations.of(context)!.approvalsDialogReason,
|
AppLocalizations.of(context)!.approvalsDialogReason,
|
||||||
record.reason,
|
record.reason,
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.approvalsDialogPrompt,
|
AppLocalizations.of(context)!.approvalsDialogPrompt,
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
|
@ -790,7 +790,7 @@ class _HistoryDialog extends StatelessWidget {
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
SelectableText(record.prompt, style: theme.textTheme.bodyMedium),
|
SelectableText(record.prompt, style: theme.textTheme.bodyMedium),
|
||||||
if (record.payloadPreview != null) ...[
|
if (record.payloadPreview != null) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.approvalsPayloadPreview,
|
AppLocalizations.of(context)!.approvalsPayloadPreview,
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
|
@ -801,22 +801,22 @@ class _HistoryDialog extends StatelessWidget {
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.sm),
|
padding: const EdgeInsets.all(ChainSpace.sm),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
record.payloadPreview!,
|
record.payloadPreview!,
|
||||||
style: FaiTheme.mono(size: 11),
|
style: ChainTheme.mono(size: 11),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Text(
|
Text(
|
||||||
record.id,
|
record.id,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 10,
|
size: 10,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ class _AuditPageState extends State<AuditPage> {
|
||||||
title: Text(AppLocalizations.of(context)!.auditTitle),
|
title: Text(AppLocalizations.of(context)!.auditTitle),
|
||||||
actions: [
|
actions: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: FaiSpace.lg),
|
padding: const EdgeInsets.only(right: ChainSpace.lg),
|
||||||
child: _FilterChips(
|
child: _FilterChips(
|
||||||
value: _typeFilter,
|
value: _typeFilter,
|
||||||
onChanged: (v) => setState(() => _typeFilter = v),
|
onChanged: (v) => setState(() => _typeFilter = v),
|
||||||
|
|
@ -113,7 +113,7 @@ class _AuditPageState extends State<AuditPage> {
|
||||||
tooltip: AppLocalizations.of(context)!.auditClearLogTooltip,
|
tooltip: AppLocalizations.of(context)!.auditClearLogTooltip,
|
||||||
onPressed: _onClearPressed,
|
onPressed: _onClearPressed,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
|
|
@ -123,14 +123,14 @@ class _AuditPageState extends State<AuditPage> {
|
||||||
child: !_initialLoaded
|
child: !_initialLoaded
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: _error != null && _events.isEmpty
|
: _error != null && _events.isEmpty
|
||||||
? FaiEmptyState(
|
? ChainEmptyState(
|
||||||
icon: Icons.cloud_off_outlined,
|
icon: Icons.cloud_off_outlined,
|
||||||
iconColor: theme.colorScheme.error,
|
iconColor: theme.colorScheme.error,
|
||||||
title: AppLocalizations.of(context)!.hubUnreachable,
|
title: AppLocalizations.of(context)!.hubUnreachable,
|
||||||
hint: AppLocalizations.of(context)!.hubUnreachableHint,
|
hint: AppLocalizations.of(context)!.hubUnreachableHint,
|
||||||
)
|
)
|
||||||
: filtered.isEmpty
|
: filtered.isEmpty
|
||||||
? FaiEmptyState(
|
? ChainEmptyState(
|
||||||
icon: Icons.timeline_outlined,
|
icon: Icons.timeline_outlined,
|
||||||
title: AppLocalizations.of(context)!.auditNoEvents,
|
title: AppLocalizations.of(context)!.auditNoEvents,
|
||||||
hint: AppLocalizations.of(context)!.auditNoEventsHint,
|
hint: AppLocalizations.of(context)!.auditNoEventsHint,
|
||||||
|
|
@ -187,7 +187,7 @@ class _AuditPageState extends State<AuditPage> {
|
||||||
Color _toneFor(String type, ThemeData theme) {
|
Color _toneFor(String type, ThemeData theme) {
|
||||||
if (type.endsWith('.failed')) return theme.colorScheme.error;
|
if (type.endsWith('.failed')) return theme.colorScheme.error;
|
||||||
if (type.endsWith('.completed')) return theme.colorScheme.primary;
|
if (type.endsWith('.completed')) return theme.colorScheme.primary;
|
||||||
if (type.endsWith('.started')) return FaiColors.warning;
|
if (type.endsWith('.started')) return ChainColors.warning;
|
||||||
return theme.colorScheme.outline;
|
return theme.colorScheme.outline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -220,15 +220,15 @@ class _GroupedEventList extends StatelessWidget {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
final items = _itemsWithHeaders(events, l);
|
final items = _itemsWithHeaders(events, l);
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
padding: const EdgeInsets.all(FaiSpace.xl),
|
padding: const EdgeInsets.all(ChainSpace.xl),
|
||||||
itemCount: items.length,
|
itemCount: items.length,
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
final item = items[i];
|
final item = items[i];
|
||||||
if (item is _GroupHeader) {
|
if (item is _GroupHeader) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
top: i == 0 ? 0 : FaiSpace.lg,
|
top: i == 0 ? 0 : ChainSpace.lg,
|
||||||
bottom: FaiSpace.sm,
|
bottom: ChainSpace.sm,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
item.label,
|
item.label,
|
||||||
|
|
@ -242,8 +242,8 @@ class _GroupedEventList extends StatelessWidget {
|
||||||
}
|
}
|
||||||
final e = (item as _EventItem).event;
|
final e = (item as _EventItem).event;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: FaiSpace.xs),
|
padding: const EdgeInsets.only(bottom: ChainSpace.xs),
|
||||||
child: FaiDataRow(
|
child: ChainDataRow(
|
||||||
accent: toneFor(e.type),
|
accent: toneFor(e.type),
|
||||||
leading: formatTime(e.timestamp),
|
leading: formatTime(e.timestamp),
|
||||||
title: e.type,
|
title: e.type,
|
||||||
|
|
@ -328,7 +328,7 @@ class _FilterChips extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
for (final (v, label) in items)
|
for (final (v, label) in items)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: FaiSpace.xs),
|
padding: const EdgeInsets.only(left: ChainSpace.xs),
|
||||||
child: _ChipButton(
|
child: _ChipButton(
|
||||||
label: label,
|
label: label,
|
||||||
selected: value == v,
|
selected: value == v,
|
||||||
|
|
@ -378,14 +378,14 @@ class _ChipButtonState extends State<_ChipButton> {
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: widget.onTap,
|
onTap: widget.onTap,
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: FaiMotion.fast,
|
duration: ChainMotion.fast,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.md,
|
horizontal: ChainSpace.md,
|
||||||
vertical: 6,
|
vertical: 6,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: bg,
|
color: bg,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: widget.selected
|
color: widget.selected
|
||||||
? theme.colorScheme.primary.withValues(alpha: 0.3)
|
? theme.colorScheme.primary.withValues(alpha: 0.3)
|
||||||
|
|
@ -394,7 +394,7 @@ class _ChipButtonState extends State<_ChipButton> {
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.label,
|
widget.label,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
weight: widget.selected ? FontWeight.w500 : FontWeight.w400,
|
weight: widget.selected ? FontWeight.w500 : FontWeight.w400,
|
||||||
color: fg,
|
color: fg,
|
||||||
|
|
@ -420,8 +420,8 @@ class _LiveStatusBar extends StatelessWidget {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.xl,
|
horizontal: ChainSpace.xl,
|
||||||
vertical: FaiSpace.sm,
|
vertical: ChainSpace.sm,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
|
|
@ -431,11 +431,11 @@ class _LiveStatusBar extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
FaiStatusDot(
|
ChainStatusDot(
|
||||||
color: live ? FaiColors.success : FaiColors.danger,
|
color: live ? ChainColors.success : ChainColors.danger,
|
||||||
pulsing: live,
|
pulsing: live,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
live ? l.auditLiveStatus(eventCount) : l.auditDisconnected(error!),
|
live ? l.auditLiveStatus(eventCount) : l.auditDisconnected(error!),
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -569,7 +569,7 @@ class _EventDetailDialogState extends State<_EventDetailDialog> {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(event.type),
|
title: Text(event.type),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
),
|
),
|
||||||
content: ConstrainedBox(
|
content: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 640, maxHeight: 560),
|
constraints: const BoxConstraints(maxWidth: 640, maxHeight: 560),
|
||||||
|
|
@ -616,7 +616,7 @@ class _EventDetailDialogState extends State<_EventDetailDialog> {
|
||||||
valueColor: theme.colorScheme.error,
|
valueColor: theme.colorScheme.error,
|
||||||
),
|
),
|
||||||
if (detail.isNotEmpty) ...[
|
if (detail.isNotEmpty) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Text(
|
Text(
|
||||||
l.auditDetailHeader,
|
l.auditDetailHeader,
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
|
@ -627,17 +627,17 @@ class _EventDetailDialogState extends State<_EventDetailDialog> {
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.md),
|
padding: const EdgeInsets.all(ChainSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: SelectableText(detail, style: FaiTheme.mono(size: 11)),
|
child: SelectableText(detail, style: ChainTheme.mono(size: 11)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (_explanation != null || _explaining) ...[
|
if (_explanation != null || _explaining) ...[
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: ChainSpace.lg),
|
||||||
_ExplanationPanel(
|
_ExplanationPanel(
|
||||||
explaining: _explaining,
|
explaining: _explaining,
|
||||||
result: _explanation,
|
result: _explanation,
|
||||||
|
|
@ -729,7 +729,7 @@ class _FlowRunDialog extends StatelessWidget {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(l.auditFlowRunDialogTitle(flowName)),
|
title: Text(l.auditFlowRunDialogTitle(flowName)),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
),
|
),
|
||||||
content: ConstrainedBox(
|
content: ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: 640, maxHeight: maxHeight),
|
constraints: BoxConstraints(maxWidth: 640, maxHeight: maxHeight),
|
||||||
|
|
@ -743,15 +743,15 @@ class _FlowRunDialog extends StatelessWidget {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: related.length,
|
itemCount: related.length,
|
||||||
separatorBuilder: (_, _) => const SizedBox(height: FaiSpace.xs),
|
separatorBuilder: (_, _) => const SizedBox(height: ChainSpace.xs),
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
final e = related[i];
|
final e = related[i];
|
||||||
return FaiDataRow(
|
return ChainDataRow(
|
||||||
accent: _toneFor(e.type, theme),
|
accent: _toneFor(e.type, theme),
|
||||||
leading: _formatRelativeTime(e.timestamp),
|
leading: _formatRelativeTime(e.timestamp),
|
||||||
title: e.type,
|
title: e.type,
|
||||||
|
|
@ -792,7 +792,7 @@ class _FlowRunDialog extends StatelessWidget {
|
||||||
Color _toneFor(String type, ThemeData theme) {
|
Color _toneFor(String type, ThemeData theme) {
|
||||||
if (type.endsWith('.failed')) return theme.colorScheme.error;
|
if (type.endsWith('.failed')) return theme.colorScheme.error;
|
||||||
if (type.endsWith('.completed')) return theme.colorScheme.primary;
|
if (type.endsWith('.completed')) return theme.colorScheme.primary;
|
||||||
if (type.endsWith('.started')) return FaiColors.warning;
|
if (type.endsWith('.started')) return ChainColors.warning;
|
||||||
return theme.colorScheme.outline;
|
return theme.colorScheme.outline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -831,7 +831,7 @@ class _Field extends StatelessWidget {
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
value,
|
value,
|
||||||
style: mono
|
style: mono
|
||||||
? FaiTheme.mono(
|
? ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: valueColor ?? theme.colorScheme.onSurface,
|
color: valueColor ?? theme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
|
|
@ -875,10 +875,10 @@ class _ExplanationPanel extends StatelessWidget {
|
||||||
: theme.colorScheme.error;
|
: theme.colorScheme.error;
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.md),
|
padding: const EdgeInsets.all(ChainSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: color.withValues(alpha: 0.3)),
|
border: Border.all(color: color.withValues(alpha: 0.3)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -887,7 +887,7 @@ class _ExplanationPanel extends StatelessWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.auto_awesome, size: 14, color: color),
|
Icon(Icons.auto_awesome, size: 14, color: color),
|
||||||
const SizedBox(width: FaiSpace.xs),
|
const SizedBox(width: ChainSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
l.auditSystemAi,
|
l.auditSystemAi,
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
|
@ -895,15 +895,15 @@ class _ExplanationPanel extends StatelessWidget {
|
||||||
letterSpacing: 0.6,
|
letterSpacing: 0.6,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.xs),
|
const SizedBox(width: ChainSpace.xs),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: privacyMode,
|
label: privacyMode,
|
||||||
tone: privacyMode == 'full'
|
tone: privacyMode == 'full'
|
||||||
? FaiPillTone.warning
|
? ChainPillTone.warning
|
||||||
: FaiPillTone.neutral,
|
: ChainPillTone.neutral,
|
||||||
),
|
),
|
||||||
if (result != null && result!.isSuccess && result!.cached) ...[
|
if (result != null && result!.isSuccess && result!.cached) ...[
|
||||||
const SizedBox(width: FaiSpace.xs),
|
const SizedBox(width: ChainSpace.xs),
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: () {
|
message: () {
|
||||||
final hits = result!.cacheHits > 1
|
final hits = result!.cacheHits > 1
|
||||||
|
|
@ -913,9 +913,9 @@ class _ExplanationPanel extends StatelessWidget {
|
||||||
? l.auditCachedTooltipUnknown(hits)
|
? l.auditCachedTooltipUnknown(hits)
|
||||||
: l.auditCachedTooltipKnown(result!.cachedAt, hits);
|
: l.auditCachedTooltipKnown(result!.cachedAt, hits);
|
||||||
}(),
|
}(),
|
||||||
child: FaiPill(
|
child: ChainPill(
|
||||||
label: l.auditCachedPill,
|
label: l.auditCachedPill,
|
||||||
tone: FaiPillTone.success,
|
tone: ChainPillTone.success,
|
||||||
icon: Icons.bolt,
|
icon: Icons.bolt,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -926,7 +926,7 @@ class _ExplanationPanel extends StatelessWidget {
|
||||||
result!.cached
|
result!.cached
|
||||||
? l.auditOriginalLatency(result!.latencyMs)
|
? l.auditOriginalLatency(result!.latencyMs)
|
||||||
: l.auditLatency(result!.latencyMs),
|
: l.auditLatency(result!.latencyMs),
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 10,
|
size: 10,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -934,7 +934,7 @@ class _ExplanationPanel extends StatelessWidget {
|
||||||
if (result != null &&
|
if (result != null &&
|
||||||
result!.isSuccess &&
|
result!.isSuccess &&
|
||||||
onRegenerate != null) ...[
|
onRegenerate != null) ...[
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: l.auditRegenerateTooltip,
|
message: l.auditRegenerateTooltip,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
|
|
@ -946,7 +946,7 @@ class _ExplanationPanel extends StatelessWidget {
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
if (explaining)
|
if (explaining)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -955,7 +955,7 @@ class _ExplanationPanel extends StatelessWidget {
|
||||||
height: 14,
|
height: 14,
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
l.auditAskingFull,
|
l.auditAskingFull,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -974,7 +974,7 @@ class _ExplanationPanel extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (result!.fixHint(l).isNotEmpty) ...[
|
if (result!.fixHint(l).isNotEmpty) ...[
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
l.auditFixLabel,
|
l.auditFixLabel,
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
|
@ -1063,7 +1063,7 @@ class _ClearAuditDialogState extends State<_ClearAuditDialog> {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: ChainSpace.lg),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _reviewer,
|
controller: _reviewer,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|
@ -1072,7 +1072,7 @@ class _ClearAuditDialogState extends State<_ClearAuditDialog> {
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
ValueListenableBuilder<TextEditingValue>(
|
ValueListenableBuilder<TextEditingValue>(
|
||||||
valueListenable: _reason,
|
valueListenable: _reason,
|
||||||
builder: (_, _, _) => TextField(
|
builder: (_, _, _) => TextField(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../data/error_presentation.dart';
|
import '../data/error_presentation.dart';
|
||||||
import '../data/fai_log.dart';
|
import '../data/chain_log.dart';
|
||||||
import '../data/hub.dart';
|
import '../data/hub.dart';
|
||||||
import '../data/system_actions.dart';
|
import '../data/system_actions.dart';
|
||||||
import '../l10n/app_localizations.dart';
|
import '../l10n/app_localizations.dart';
|
||||||
|
|
@ -47,7 +47,7 @@ class _DoctorPageState extends State<DoctorPage> {
|
||||||
tooltip: AppLocalizations.of(context)!.doctorRecheckTooltip,
|
tooltip: AppLocalizations.of(context)!.doctorRecheckTooltip,
|
||||||
onPressed: _refresh,
|
onPressed: _refresh,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: FutureBuilder<DoctorSnapshot>(
|
body: FutureBuilder<DoctorSnapshot>(
|
||||||
|
|
@ -58,7 +58,7 @@ class _DoctorPageState extends State<DoctorPage> {
|
||||||
}
|
}
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return FaiEmptyState(
|
return ChainEmptyState(
|
||||||
icon: Icons.cloud_off_outlined,
|
icon: Icons.cloud_off_outlined,
|
||||||
iconColor: Theme.of(context).colorScheme.error,
|
iconColor: Theme.of(context).colorScheme.error,
|
||||||
title: l.hubUnreachable,
|
title: l.hubUnreachable,
|
||||||
|
|
@ -74,34 +74,34 @@ class _DoctorPageState extends State<DoctorPage> {
|
||||||
s.update.updateAvailable ||
|
s.update.updateAvailable ||
|
||||||
(!s.update.manifestReachable && s.update.localVersion.isNotEmpty);
|
(!s.update.manifestReachable && s.update.localVersion.isNotEmpty);
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: const EdgeInsets.all(FaiSpace.xl),
|
padding: const EdgeInsets.all(ChainSpace.xl),
|
||||||
children: [
|
children: [
|
||||||
if (showUpdate) _UpdateBanner(status: s.update),
|
if (showUpdate) _UpdateBanner(status: s.update),
|
||||||
if (showUpdate) const SizedBox(height: FaiSpace.lg),
|
if (showUpdate) const SizedBox(height: ChainSpace.lg),
|
||||||
_SummaryStrip(snapshot: s),
|
_SummaryStrip(snapshot: s),
|
||||||
const SizedBox(height: FaiSpace.xl),
|
const SizedBox(height: ChainSpace.xl),
|
||||||
_Section(
|
_Section(
|
||||||
title: AppLocalizations.of(context)!.doctorEventLogSection,
|
title: AppLocalizations.of(context)!.doctorEventLogSection,
|
||||||
child: _EventLogPanel(snapshot: s, onRefresh: _refresh),
|
child: _EventLogPanel(snapshot: s, onRefresh: _refresh),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: ChainSpace.lg),
|
||||||
_Section(
|
_Section(
|
||||||
title: AppLocalizations.of(
|
title: AppLocalizations.of(
|
||||||
context,
|
context,
|
||||||
)!.doctorModulesApprovalsSection,
|
)!.doctorModulesApprovalsSection,
|
||||||
child: _ModulesPanel(snapshot: s),
|
child: _ModulesPanel(snapshot: s),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: ChainSpace.lg),
|
||||||
_Section(
|
_Section(
|
||||||
title: AppLocalizations.of(context)!.doctorHostServicesSection,
|
title: AppLocalizations.of(context)!.doctorHostServicesSection,
|
||||||
child: _ServicesPanel(snapshot: s),
|
child: _ServicesPanel(snapshot: s),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: ChainSpace.lg),
|
||||||
_Section(
|
_Section(
|
||||||
title: AppLocalizations.of(context)!.doctorDaemonFilesSection,
|
title: AppLocalizations.of(context)!.doctorDaemonFilesSection,
|
||||||
child: _DaemonPathsPanel(paths: s.paths),
|
child: _DaemonPathsPanel(paths: s.paths),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: ChainSpace.lg),
|
||||||
_Section(
|
_Section(
|
||||||
title: AppLocalizations.of(context)!.doctorDaemonControlSection,
|
title: AppLocalizations.of(context)!.doctorDaemonControlSection,
|
||||||
child: _DaemonActionsCard(),
|
child: _DaemonActionsCard(),
|
||||||
|
|
@ -128,8 +128,8 @@ class _Section extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
left: FaiSpace.xs,
|
left: ChainSpace.xs,
|
||||||
bottom: FaiSpace.sm,
|
bottom: ChainSpace.sm,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
title.toUpperCase(),
|
title.toUpperCase(),
|
||||||
|
|
@ -162,17 +162,17 @@ class _SummaryStrip extends StatelessWidget {
|
||||||
subtitle: l.doctorSummaryCapabilities(snapshot.capabilityCount),
|
subtitle: l.doctorSummaryCapabilities(snapshot.capabilityCount),
|
||||||
icon: Icons.extension_outlined,
|
icon: Icons.extension_outlined,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
_StatTile(
|
_StatTile(
|
||||||
label: l.doctorSummaryApprovals,
|
label: l.doctorSummaryApprovals,
|
||||||
value: snapshot.pendingApprovals.toString(),
|
value: snapshot.pendingApprovals.toString(),
|
||||||
subtitle: l.doctorSummaryPending,
|
subtitle: l.doctorSummaryPending,
|
||||||
icon: Icons.inbox_outlined,
|
icon: Icons.inbox_outlined,
|
||||||
tone: snapshot.pendingApprovals > 0
|
tone: snapshot.pendingApprovals > 0
|
||||||
? FaiPillTone.warning
|
? ChainPillTone.warning
|
||||||
: FaiPillTone.neutral,
|
: ChainPillTone.neutral,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
_StatTile(
|
_StatTile(
|
||||||
label: l.doctorSummaryAudit,
|
label: l.doctorSummaryAudit,
|
||||||
value: snapshot.chainHealthy ? '✓' : '⚠',
|
value: snapshot.chainHealthy ? '✓' : '⚠',
|
||||||
|
|
@ -182,10 +182,10 @@ class _SummaryStrip extends StatelessWidget {
|
||||||
),
|
),
|
||||||
icon: Icons.shield_outlined,
|
icon: Icons.shield_outlined,
|
||||||
tone: snapshot.chainHealthy
|
tone: snapshot.chainHealthy
|
||||||
? FaiPillTone.success
|
? ChainPillTone.success
|
||||||
: FaiPillTone.danger,
|
: ChainPillTone.danger,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
_StatTile(
|
_StatTile(
|
||||||
label: l.doctorSummaryServices,
|
label: l.doctorSummaryServices,
|
||||||
value: snapshot.services.length.toString(),
|
value: snapshot.services.length.toString(),
|
||||||
|
|
@ -202,36 +202,36 @@ class _StatTile extends StatelessWidget {
|
||||||
final String value;
|
final String value;
|
||||||
final String subtitle;
|
final String subtitle;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final FaiPillTone tone;
|
final ChainPillTone tone;
|
||||||
|
|
||||||
const _StatTile({
|
const _StatTile({
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.subtitle,
|
required this.subtitle,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
this.tone = FaiPillTone.neutral,
|
this.tone = ChainPillTone.neutral,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final accentColor = switch (tone) {
|
final accentColor = switch (tone) {
|
||||||
FaiPillTone.success => FaiColors.success,
|
ChainPillTone.success => ChainColors.success,
|
||||||
FaiPillTone.warning => FaiColors.warning,
|
ChainPillTone.warning => ChainColors.warning,
|
||||||
FaiPillTone.danger => theme.colorScheme.error,
|
ChainPillTone.danger => theme.colorScheme.error,
|
||||||
_ => theme.colorScheme.primary,
|
_ => theme.colorScheme.primary,
|
||||||
};
|
};
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: FaiCard(
|
child: ChainCard(
|
||||||
accentLeft: accentColor,
|
accentLeft: accentColor,
|
||||||
padding: const EdgeInsets.all(FaiSpace.lg),
|
padding: const EdgeInsets.all(ChainSpace.lg),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 14, color: theme.colorScheme.onSurfaceVariant),
|
Icon(icon, size: 14, color: theme.colorScheme.onSurfaceVariant),
|
||||||
const SizedBox(width: FaiSpace.xs),
|
const SizedBox(width: ChainSpace.xs),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
label.toUpperCase(),
|
label.toUpperCase(),
|
||||||
|
|
@ -245,7 +245,7 @@ class _StatTile extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
value,
|
value,
|
||||||
style: theme.textTheme.displaySmall?.copyWith(
|
style: theme.textTheme.displaySmall?.copyWith(
|
||||||
|
|
@ -282,15 +282,15 @@ class _EventLogPanel extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final healthy = snapshot.chainHealthy;
|
final healthy = snapshot.chainHealthy;
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return FaiCard(
|
return ChainCard(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
healthy ? Icons.verified_outlined : Icons.gpp_bad_outlined,
|
healthy ? Icons.verified_outlined : Icons.gpp_bad_outlined,
|
||||||
size: 24,
|
size: 24,
|
||||||
color: healthy ? FaiColors.success : theme.colorScheme.error,
|
color: healthy ? ChainColors.success : theme.colorScheme.error,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.lg),
|
const SizedBox(width: ChainSpace.lg),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -322,10 +322,10 @@ class _EventLogPanel extends StatelessWidget {
|
||||||
icon: const Icon(Icons.fact_check_outlined, size: 16),
|
icon: const Icon(Icons.fact_check_outlined, size: 16),
|
||||||
label: Text(l.doctorVerifyNow),
|
label: Text(l.doctorVerifyNow),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: healthy ? l.doctorChainPillOk : l.doctorChainPillTamper,
|
label: healthy ? l.doctorChainPillOk : l.doctorChainPillTamper,
|
||||||
tone: healthy ? FaiPillTone.success : FaiPillTone.danger,
|
tone: healthy ? ChainPillTone.success : ChainPillTone.danger,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -353,7 +353,7 @@ class _DaemonPathsPanel extends StatelessWidget {
|
||||||
(l.doctorPathLog, paths.logPath, Icons.description_outlined, false),
|
(l.doctorPathLog, paths.logPath, Icons.description_outlined, false),
|
||||||
(
|
(
|
||||||
l.doctorPathStudioErrors,
|
l.doctorPathStudioErrors,
|
||||||
FaiLog.instance.path,
|
ChainLog.instance.path,
|
||||||
Icons.report_problem_outlined,
|
Icons.report_problem_outlined,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
|
@ -365,7 +365,7 @@ class _DaemonPathsPanel extends StatelessWidget {
|
||||||
].where((e) => e.$2.isNotEmpty).toList();
|
].where((e) => e.$2.isNotEmpty).toList();
|
||||||
|
|
||||||
if (entries.isEmpty) {
|
if (entries.isEmpty) {
|
||||||
return FaiCard(
|
return ChainCard(
|
||||||
child: Text(
|
child: Text(
|
||||||
l.doctorPathsEmpty,
|
l.doctorPathsEmpty,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
|
@ -373,7 +373,7 @@ class _DaemonPathsPanel extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return FaiCard(
|
return ChainCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -416,7 +416,7 @@ class _PathRow extends StatelessWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 16, color: theme.colorScheme.onSurfaceVariant),
|
Icon(icon, size: 16, color: theme.colorScheme.onSurfaceVariant),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 90,
|
width: 90,
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
@ -429,13 +429,13 @@ class _PathRow extends StatelessWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
path,
|
path,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
if (isLog || isConfig) ...[
|
if (isLog || isConfig) ...[
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: () => isConfig
|
onPressed: () => isConfig
|
||||||
|
|
@ -447,7 +447,7 @@ class _PathRow extends StatelessWidget {
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.xs),
|
const SizedBox(width: ChainSpace.xs),
|
||||||
],
|
],
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
|
@ -494,7 +494,7 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
||||||
|
|
||||||
/// True when the last daemon action failed because no `fai`
|
/// True when the last daemon action failed because no `fai`
|
||||||
/// binary could be located. Swaps the raw output line for the
|
/// binary could be located. Swaps the raw output line for the
|
||||||
/// actionable [FaiBinaryRecovery] block.
|
/// actionable [ChainBinaryRecovery] block.
|
||||||
bool _binaryMissing = false;
|
bool _binaryMissing = false;
|
||||||
ChannelStatusSnapshot? _channels;
|
ChannelStatusSnapshot? _channels;
|
||||||
|
|
||||||
|
|
@ -557,7 +557,7 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return FaiCard(
|
return ChainCard(
|
||||||
// Row + Expanded forces the card to take the full width
|
// Row + Expanded forces the card to take the full width
|
||||||
// its parent offers. Without this, the card hugs the
|
// its parent offers. Without this, the card hugs the
|
||||||
// intrinsic width of the longest button row and ends up
|
// intrinsic width of the longest button row and ends up
|
||||||
|
|
@ -575,10 +575,10 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
||||||
: Icons.power_off_outlined,
|
: Icons.power_off_outlined,
|
||||||
size: 18,
|
size: 18,
|
||||||
color: active?.running == true
|
color: active?.running == true
|
||||||
? FaiColors.success
|
? ChainColors.success
|
||||||
: theme.colorScheme.onSurfaceVariant,
|
: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
active == null
|
active == null
|
||||||
|
|
@ -593,36 +593,36 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (active != null) ...[
|
if (active != null) ...[
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: active.running
|
label: active.running
|
||||||
? l.doctorPillRunning
|
? l.doctorPillRunning
|
||||||
: l.doctorPillStopped,
|
: l.doctorPillStopped,
|
||||||
tone: active.running
|
tone: active.running
|
||||||
? FaiPillTone.success
|
? ChainPillTone.success
|
||||||
: FaiPillTone.neutral,
|
: ChainPillTone.neutral,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
l.doctorDaemonControlHint,
|
l.doctorDaemonControlHint,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: FaiSpace.sm,
|
spacing: ChainSpace.sm,
|
||||||
runSpacing: FaiSpace.sm,
|
runSpacing: ChainSpace.sm,
|
||||||
children: [
|
children: [
|
||||||
FilledButton.tonalIcon(
|
FilledButton.tonalIcon(
|
||||||
onPressed: _busy
|
onPressed: _busy
|
||||||
? null
|
? null
|
||||||
: () => _run(
|
: () => _run(
|
||||||
l.daemonActionRestart,
|
l.daemonActionRestart,
|
||||||
() => SystemActions.faiDaemon(['restart']),
|
() => SystemActions.chainDaemon(['restart']),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.restart_alt, size: 16),
|
icon: const Icon(Icons.restart_alt, size: 16),
|
||||||
label: Text(l.doctorRestart),
|
label: Text(l.doctorRestart),
|
||||||
|
|
@ -633,7 +633,7 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
||||||
? null
|
? null
|
||||||
: () => _run(
|
: () => _run(
|
||||||
l.daemonActionStart,
|
l.daemonActionStart,
|
||||||
() => SystemActions.faiDaemon(['start']),
|
() => SystemActions.chainDaemon(['start']),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.play_arrow, size: 16),
|
icon: const Icon(Icons.play_arrow, size: 16),
|
||||||
label: Text(l.doctorStart),
|
label: Text(l.doctorStart),
|
||||||
|
|
@ -643,7 +643,7 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
||||||
? null
|
? null
|
||||||
: () => _run(
|
: () => _run(
|
||||||
l.daemonActionStop,
|
l.daemonActionStop,
|
||||||
() => SystemActions.faiDaemon(['stop']),
|
() => SystemActions.chainDaemon(['stop']),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.stop_circle_outlined, size: 16),
|
icon: const Icon(Icons.stop_circle_outlined, size: 16),
|
||||||
label: Text(l.doctorStop),
|
label: Text(l.doctorStop),
|
||||||
|
|
@ -653,7 +653,7 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
||||||
? null
|
? null
|
||||||
: () => _run(
|
: () => _run(
|
||||||
l.daemonActionStatus,
|
l.daemonActionStatus,
|
||||||
() => SystemActions.faiDaemon(['status']),
|
() => SystemActions.chainDaemon(['status']),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.health_and_safety_outlined, size: 16),
|
icon: const Icon(Icons.health_and_safety_outlined, size: 16),
|
||||||
label: Text(l.doctorStatusAction),
|
label: Text(l.doctorStatusAction),
|
||||||
|
|
@ -661,7 +661,7 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (_busy) ...[
|
if (_busy) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
|
@ -669,7 +669,7 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
||||||
height: 14,
|
height: 14,
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
l.doctorDaemonControlWorking,
|
l.doctorDaemonControlWorking,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -680,27 +680,27 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (_binaryMissing) ...[
|
if (_binaryMissing) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
FaiBinaryRecovery(
|
ChainBinaryRecovery(
|
||||||
onLocated: () => _run(
|
onLocated: () => _run(
|
||||||
l.daemonActionStatus,
|
l.daemonActionStatus,
|
||||||
() => SystemActions.faiDaemon(['status']),
|
() => SystemActions.chainDaemon(['status']),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (_output != null) ...[
|
if (_output != null) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.sm),
|
padding: const EdgeInsets.all(ChainSpace.sm),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
_output!,
|
_output!,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
|
|
@ -732,7 +732,7 @@ class _ModulesPanel extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return FaiCard(
|
return ChainCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -743,7 +743,7 @@ class _ModulesPanel extends StatelessWidget {
|
||||||
size: 18,
|
size: 18,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -767,17 +767,17 @@ class _ModulesPanel extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: snapshot.moduleCount > 0
|
label: snapshot.moduleCount > 0
|
||||||
? l.doctorPillLoaded
|
? l.doctorPillLoaded
|
||||||
: l.doctorPillEmpty,
|
: l.doctorPillEmpty,
|
||||||
tone: snapshot.moduleCount > 0
|
tone: snapshot.moduleCount > 0
|
||||||
? FaiPillTone.success
|
? ChainPillTone.success
|
||||||
: FaiPillTone.neutral,
|
: ChainPillTone.neutral,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Divider(height: FaiSpace.xl),
|
const Divider(height: ChainSpace.xl),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
|
|
@ -785,7 +785,7 @@ class _ModulesPanel extends StatelessWidget {
|
||||||
size: 18,
|
size: 18,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
snapshot.pendingApprovals == 0
|
snapshot.pendingApprovals == 0
|
||||||
? l.doctorApprovalsNone
|
? l.doctorApprovalsNone
|
||||||
|
|
@ -794,9 +794,9 @@ class _ModulesPanel extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if (snapshot.pendingApprovals > 0)
|
if (snapshot.pendingApprovals > 0)
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: l.doctorApprovalsAttentionPill,
|
label: l.doctorApprovalsAttentionPill,
|
||||||
tone: FaiPillTone.warning,
|
tone: ChainPillTone.warning,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -819,10 +819,10 @@ class _ServicesPanel extends StatelessWidget {
|
||||||
// Wrap on narrow windows so the mono-spaced hint
|
// Wrap on narrow windows so the mono-spaced hint
|
||||||
// (`add to ~/.chain/config.yaml under services:`) does not
|
// (`add to ~/.chain/config.yaml under services:`) does not
|
||||||
// overflow horizontally past the card's right edge.
|
// overflow horizontally past the card's right edge.
|
||||||
return FaiCard(
|
return ChainCard(
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
spacing: FaiSpace.sm,
|
spacing: ChainSpace.sm,
|
||||||
runSpacing: FaiSpace.xs,
|
runSpacing: ChainSpace.xs,
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -833,7 +833,7 @@ class _ServicesPanel extends StatelessWidget {
|
||||||
size: 18,
|
size: 18,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
l.doctorServicesEmpty,
|
l.doctorServicesEmpty,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
|
@ -844,7 +844,7 @@ class _ServicesPanel extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
l.doctorServicesEmptyHint,
|
l.doctorServicesEmptyHint,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -853,7 +853,7 @@ class _ServicesPanel extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return FaiCard(
|
return ChainCard(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -861,7 +861,7 @@ class _ServicesPanel extends StatelessWidget {
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
Divider(height: 1, color: theme.colorScheme.outlineVariant),
|
Divider(height: 1, color: theme.colorScheme.outlineVariant),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(FaiSpace.lg),
|
padding: const EdgeInsets.all(ChainSpace.lg),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
|
|
@ -869,25 +869,25 @@ class _ServicesPanel extends StatelessWidget {
|
||||||
size: 16,
|
size: 16,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
snapshot.services[i].name,
|
snapshot.services[i].name,
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
Text(
|
Text(
|
||||||
snapshot.services[i].endpoint,
|
snapshot.services[i].endpoint,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
for (final tag in snapshot.services[i].tags) ...[
|
for (final tag in snapshot.services[i].tags) ...[
|
||||||
FaiPill(label: tag, tone: FaiPillTone.neutral),
|
ChainPill(label: tag, tone: ChainPillTone.neutral),
|
||||||
const SizedBox(width: FaiSpace.xs),
|
const SizedBox(width: ChainSpace.xs),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -917,7 +917,7 @@ class _UpdateBannerState extends State<_UpdateBanner> {
|
||||||
_applying = true;
|
_applying = true;
|
||||||
_applyOutput = null;
|
_applyOutput = null;
|
||||||
});
|
});
|
||||||
final r = await SystemActions.faiUpdateApply(widget.status.channel);
|
final r = await SystemActions.chainUpdateApply(widget.status.channel);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_applying = false;
|
_applying = false;
|
||||||
|
|
@ -945,7 +945,7 @@ class _UpdateBannerState extends State<_UpdateBanner> {
|
||||||
final body = available
|
final body = available
|
||||||
? l.doctorUpdateBody(status.channel, status.latestVersion)
|
? l.doctorUpdateBody(status.channel, status.latestVersion)
|
||||||
: (status.reason ?? l.doctorUpdateUnreachableBody(status.channel));
|
: (status.reason ?? l.doctorUpdateUnreachableBody(status.channel));
|
||||||
return FaiCard(
|
return ChainCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -953,7 +953,7 @@ class _UpdateBannerState extends State<_UpdateBanner> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Icon(iconData, size: 20, color: iconColor),
|
Icon(iconData, size: 20, color: iconColor),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -967,7 +967,7 @@ class _UpdateBannerState extends State<_UpdateBanner> {
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
body,
|
body,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -976,7 +976,7 @@ class _UpdateBannerState extends State<_UpdateBanner> {
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
l.doctorReleaseNotes(status.releaseNotesUrl!),
|
l.doctorReleaseNotes(status.releaseNotesUrl!),
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
|
|
@ -999,16 +999,16 @@ class _UpdateBannerState extends State<_UpdateBanner> {
|
||||||
_applying ? l.doctorApplying : l.doctorApplyUpdate,
|
_applying ? l.doctorApplying : l.doctorApplyUpdate,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: available ? l.doctorPillNew : l.doctorPillOffline,
|
label: available ? l.doctorPillNew : l.doctorPillOffline,
|
||||||
tone: available ? FaiPillTone.success : FaiPillTone.neutral,
|
tone: available ? ChainPillTone.success : ChainPillTone.neutral,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (_applyOutput != null) ...[
|
if (_applyOutput != null) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
FaiErrorBox(text: _applyOutput!, maxHeight: 240),
|
ChainErrorBox(text: _applyOutput!, maxHeight: 240),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ class _FederationPageState extends State<FederationPage> {
|
||||||
tooltip: l.federationReloadTooltip,
|
tooltip: l.federationReloadTooltip,
|
||||||
onPressed: _refresh,
|
onPressed: _refresh,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
|
|
@ -118,7 +118,7 @@ class _FederationPageState extends State<FederationPage> {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return FaiEmptyState(
|
return ChainEmptyState(
|
||||||
icon: Icons.cloud_off_outlined,
|
icon: Icons.cloud_off_outlined,
|
||||||
iconColor: theme.colorScheme.error,
|
iconColor: theme.colorScheme.error,
|
||||||
title: l.hubUnreachable,
|
title: l.hubUnreachable,
|
||||||
|
|
@ -131,7 +131,7 @@ class _FederationPageState extends State<FederationPage> {
|
||||||
}
|
}
|
||||||
final sats = snapshot.data ?? [];
|
final sats = snapshot.data ?? [];
|
||||||
if (sats.isEmpty) {
|
if (sats.isEmpty) {
|
||||||
return FaiEmptyState(
|
return ChainEmptyState(
|
||||||
icon: Icons.hub_outlined,
|
icon: Icons.hub_outlined,
|
||||||
title: l.federationEmptyTitle,
|
title: l.federationEmptyTitle,
|
||||||
hint: l.federationEmptyHint,
|
hint: l.federationEmptyHint,
|
||||||
|
|
@ -143,9 +143,9 @@ class _FederationPageState extends State<FederationPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
padding: const EdgeInsets.all(FaiSpace.xl),
|
padding: const EdgeInsets.all(ChainSpace.xl),
|
||||||
itemCount: sats.length,
|
itemCount: sats.length,
|
||||||
separatorBuilder: (_, _) => const SizedBox(height: FaiSpace.md),
|
separatorBuilder: (_, _) => const SizedBox(height: ChainSpace.md),
|
||||||
itemBuilder: (context, i) => _SatelliteCard(satellite: sats[i]),
|
itemBuilder: (context, i) => _SatelliteCard(satellite: sats[i]),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -162,19 +162,19 @@ class _SatelliteCard extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return FaiCard(
|
return ChainCard(
|
||||||
accentTop: true,
|
accentTop: true,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: l.federationPillConnected,
|
label: l.federationPillConnected,
|
||||||
tone: FaiPillTone.success,
|
tone: ChainPillTone.success,
|
||||||
icon: Icons.link,
|
icon: Icons.link,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
satellite.displayName,
|
satellite.displayName,
|
||||||
|
|
@ -183,19 +183,19 @@ class _SatelliteCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: satellite.region.isEmpty
|
label: satellite.region.isEmpty
|
||||||
? l.federationRegionNone
|
? l.federationRegionNone
|
||||||
: satellite.region,
|
: satellite.region,
|
||||||
tone: FaiPillTone.neutral,
|
tone: ChainPillTone.neutral,
|
||||||
icon: Icons.public,
|
icon: Icons.public,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: FaiSpace.md,
|
spacing: ChainSpace.md,
|
||||||
runSpacing: FaiSpace.xs,
|
runSpacing: ChainSpace.xs,
|
||||||
children: [
|
children: [
|
||||||
_meta(theme, 'v${satellite.hubVersion}'),
|
_meta(theme, 'v${satellite.hubVersion}'),
|
||||||
_meta(theme, 'wire ${satellite.wireVersion}'),
|
_meta(theme, 'wire ${satellite.wireVersion}'),
|
||||||
|
|
@ -203,7 +203,7 @@ class _SatelliteCard extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (satellite.capabilities.isNotEmpty) ...[
|
if (satellite.capabilities.isNotEmpty) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Text(
|
Text(
|
||||||
l.federationCapabilities(satellite.capabilities.length),
|
l.federationCapabilities(satellite.capabilities.length),
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
|
@ -213,13 +213,13 @@ class _SatelliteCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: FaiSpace.xs,
|
spacing: ChainSpace.xs,
|
||||||
runSpacing: FaiSpace.xs,
|
runSpacing: ChainSpace.xs,
|
||||||
children: satellite.capabilities
|
children: satellite.capabilities
|
||||||
.map(
|
.map(
|
||||||
(c) => FaiPill(
|
(c) => ChainPill(
|
||||||
label: c,
|
label: c,
|
||||||
tone: FaiPillTone.accent,
|
tone: ChainPillTone.accent,
|
||||||
icon: Icons.extension_outlined,
|
icon: Icons.extension_outlined,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -233,7 +233,7 @@ class _SatelliteCard extends StatelessWidget {
|
||||||
|
|
||||||
Widget _meta(ThemeData theme, String text) => Text(
|
Widget _meta(ThemeData theme, String text) => Text(
|
||||||
text,
|
text,
|
||||||
style: FaiTheme.mono(size: 10, color: theme.colorScheme.onSurfaceVariant),
|
style: ChainTheme.mono(size: 10, color: theme.colorScheme.onSurfaceVariant),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,11 +261,11 @@ class _EnrollmentDialog extends StatelessWidget {
|
||||||
Text(l.federationTokenLabel, style: theme.textTheme.labelSmall),
|
Text(l.federationTokenLabel, style: theme.textTheme.labelSmall),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
_CopyableBlock(text: enrollment.token, copiedMsg: l.federationCopied),
|
_CopyableBlock(text: enrollment.token, copiedMsg: l.federationCopied),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Text(l.federationConfigLabel, style: theme.textTheme.labelSmall),
|
Text(l.federationConfigLabel, style: theme.textTheme.labelSmall),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
_CopyableBlock(text: config, copiedMsg: l.federationCopied),
|
_CopyableBlock(text: config, copiedMsg: l.federationCopied),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Text(
|
Text(
|
||||||
l.federationEnrollmentHint,
|
l.federationEnrollmentHint,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -299,19 +299,19 @@ class _CopyableBlock extends StatelessWidget {
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
FaiSpace.md,
|
ChainSpace.md,
|
||||||
FaiSpace.md,
|
ChainSpace.md,
|
||||||
40,
|
40,
|
||||||
FaiSpace.md,
|
ChainSpace.md,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
text,
|
text,
|
||||||
style: FaiTheme.mono(size: 11, color: theme.colorScheme.onSurface),
|
style: ChainTheme.mono(size: 11, color: theme.colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ class _ModulesPageState extends State<ModulesPage> {
|
||||||
tooltip: AppLocalizations.of(context)!.modulesReloadTooltip,
|
tooltip: AppLocalizations.of(context)!.modulesReloadTooltip,
|
||||||
onPressed: _refresh,
|
onPressed: _refresh,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: FutureBuilder<List<ModuleSummary>>(
|
body: FutureBuilder<List<ModuleSummary>>(
|
||||||
|
|
@ -59,7 +59,7 @@ class _ModulesPageState extends State<ModulesPage> {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return FaiEmptyState(
|
return ChainEmptyState(
|
||||||
icon: Icons.cloud_off_outlined,
|
icon: Icons.cloud_off_outlined,
|
||||||
iconColor: Theme.of(context).colorScheme.error,
|
iconColor: Theme.of(context).colorScheme.error,
|
||||||
title: l.hubUnreachable,
|
title: l.hubUnreachable,
|
||||||
|
|
@ -72,22 +72,22 @@ class _ModulesPageState extends State<ModulesPage> {
|
||||||
}
|
}
|
||||||
final modules = snapshot.data ?? [];
|
final modules = snapshot.data ?? [];
|
||||||
if (modules.isEmpty) {
|
if (modules.isEmpty) {
|
||||||
return FaiEmptyState(
|
return ChainEmptyState(
|
||||||
icon: Icons.extension_outlined,
|
icon: Icons.extension_outlined,
|
||||||
title: l.modulesNoneTitle,
|
title: l.modulesNoneTitle,
|
||||||
hint: l.modulesNoneHint,
|
hint: l.modulesNoneHint,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: const EdgeInsets.all(FaiSpace.xl),
|
padding: const EdgeInsets.all(ChainSpace.xl),
|
||||||
children: [
|
children: [
|
||||||
_RecentActivityPanel(future: _historyFuture),
|
_RecentActivityPanel(future: _historyFuture),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
for (var i = 0; i < modules.length; i++) ...[
|
for (var i = 0; i < modules.length; i++) ...[
|
||||||
if (i > 0) const SizedBox(height: FaiSpace.md),
|
if (i > 0) const SizedBox(height: ChainSpace.md),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final uninstalled = await FaiModuleSheet.show(
|
final uninstalled = await ChainModuleSheet.show(
|
||||||
context,
|
context,
|
||||||
modules[i].name,
|
modules[i].name,
|
||||||
);
|
);
|
||||||
|
|
@ -116,7 +116,7 @@ class _ModuleCard extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return FaiCard(
|
return ChainCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -128,15 +128,15 @@ class _ModuleCard extends StatelessWidget {
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: 'v${module.version}',
|
label: 'v${module.version}',
|
||||||
tone: FaiPillTone.neutral,
|
tone: ChainPillTone.neutral,
|
||||||
monospace: true,
|
monospace: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -152,13 +152,13 @@ class _ModuleCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
spacing: FaiSpace.xs,
|
spacing: ChainSpace.xs,
|
||||||
runSpacing: FaiSpace.xs,
|
runSpacing: ChainSpace.xs,
|
||||||
children: module.capabilities
|
children: module.capabilities
|
||||||
.map(
|
.map(
|
||||||
(c) => FaiPill(
|
(c) => ChainPill(
|
||||||
label: c,
|
label: c,
|
||||||
tone: FaiPillTone.accent,
|
tone: ChainPillTone.accent,
|
||||||
monospace: true,
|
monospace: true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -192,12 +192,12 @@ class _RecentActivityPanel extends StatelessWidget {
|
||||||
if (events.isEmpty) return const SizedBox.shrink();
|
if (events.isEmpty) return const SizedBox.shrink();
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.md,
|
horizontal: ChainSpace.md,
|
||||||
vertical: FaiSpace.sm,
|
vertical: ChainSpace.sm,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainer,
|
color: theme.colorScheme.surfaceContainer,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -219,7 +219,7 @@ class _RecentActivityPanel extends StatelessWidget {
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(
|
AppLocalizations.of(
|
||||||
context,
|
context,
|
||||||
|
|
@ -230,7 +230,7 @@ class _RecentActivityPanel extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.xs),
|
const SizedBox(height: ChainSpace.xs),
|
||||||
for (final e in events) _ActivityRow(event: e),
|
for (final e in events) _ActivityRow(event: e),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -248,7 +248,7 @@ class _ActivityRow extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final installed = event.type == 'module.installed';
|
final installed = event.type == 'module.installed';
|
||||||
final accent = installed ? FaiColors.success : FaiColors.warning;
|
final accent = installed ? ChainColors.success : ChainColors.warning;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -263,7 +263,7 @@ class _ActivityRow extends StatelessWidget {
|
||||||
width: 150,
|
width: 150,
|
||||||
child: Text(
|
child: Text(
|
||||||
_formatTimestamp(event.timestamp.toLocal()),
|
_formatTimestamp(event.timestamp.toLocal()),
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 10,
|
size: 10,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -35,14 +35,14 @@ class WelcomePage extends StatelessWidget {
|
||||||
final hubDown = StudioShellState.of(context)?.connected == false;
|
final hubDown = StudioShellState.of(context)?.connected == false;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: theme.scaffoldBackgroundColor,
|
backgroundColor: theme.scaffoldBackgroundColor,
|
||||||
appBar: AppBar(titleSpacing: FaiSpace.xl, title: Text(l.navWelcome)),
|
appBar: AppBar(titleSpacing: ChainSpace.xl, title: Text(l.navWelcome)),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
FaiSpace.xl,
|
ChainSpace.xl,
|
||||||
FaiSpace.lg,
|
ChainSpace.lg,
|
||||||
FaiSpace.xl,
|
ChainSpace.xl,
|
||||||
FaiSpace.xxl,
|
ChainSpace.xxl,
|
||||||
),
|
),
|
||||||
// Center the 960-px content column on wide windows
|
// Center the 960-px content column on wide windows
|
||||||
// so the right margin matches the left rather than
|
// so the right margin matches the left rather than
|
||||||
|
|
@ -59,24 +59,24 @@ class WelcomePage extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const _Hero(),
|
const _Hero(),
|
||||||
const SizedBox(height: FaiSpace.xxl),
|
const SizedBox(height: ChainSpace.xxl),
|
||||||
// When the hub is down the onboarding checklist
|
// When the hub is down the onboarding checklist
|
||||||
// would render dead (all-unchecked, nothing to
|
// would render dead (all-unchecked, nothing to
|
||||||
// probe). Lead with a "start the hub" card
|
// probe). Lead with a "start the hub" card
|
||||||
// instead; restore the checklist once connected.
|
// instead; restore the checklist once connected.
|
||||||
if (hubDown) ...[
|
if (hubDown) ...[
|
||||||
const _HubDownHero(),
|
const _HubDownHero(),
|
||||||
const SizedBox(height: FaiSpace.xxl),
|
const SizedBox(height: ChainSpace.xxl),
|
||||||
] else
|
] else
|
||||||
const _OnboardingChecklist(),
|
const _OnboardingChecklist(),
|
||||||
const _PillarRow(),
|
const _PillarRow(),
|
||||||
const SizedBox(height: FaiSpace.xxl),
|
const SizedBox(height: ChainSpace.xxl),
|
||||||
const _SectionLabel(textKey: _SectionLabelKey.trust),
|
const _SectionLabel(textKey: _SectionLabelKey.trust),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
const _TrustPosture(),
|
const _TrustPosture(),
|
||||||
const SizedBox(height: FaiSpace.xxl),
|
const SizedBox(height: ChainSpace.xxl),
|
||||||
const _SectionLabel(textKey: _SectionLabelKey.docs),
|
const _SectionLabel(textKey: _SectionLabelKey.docs),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
const _DocsRow(),
|
const _DocsRow(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -100,9 +100,9 @@ class _Hero extends StatelessWidget {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.xxl),
|
padding: const EdgeInsets.all(ChainSpace.xxl),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [
|
colors: [
|
||||||
theme.colorScheme.primary.withValues(alpha: 0.18),
|
theme.colorScheme.primary.withValues(alpha: 0.18),
|
||||||
|
|
@ -123,7 +123,7 @@ class _Hero extends StatelessWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(FaiSpace.md),
|
padding: const EdgeInsets.all(ChainSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.primary.withValues(alpha: 0.14),
|
color: theme.colorScheme.primary.withValues(alpha: 0.14),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
|
|
@ -134,7 +134,7 @@ class _Hero extends StatelessWidget {
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.lg),
|
const SizedBox(width: ChainSpace.lg),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
l.welcomeHeroTitle,
|
l.welcomeHeroTitle,
|
||||||
|
|
@ -145,7 +145,7 @@ class _Hero extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: ChainSpace.lg),
|
||||||
Text(
|
Text(
|
||||||
l.welcomeHeroSubtitle,
|
l.welcomeHeroSubtitle,
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
|
|
@ -189,13 +189,13 @@ class _HubDownHeroState extends State<_HubDownHero> {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
// No `fai` binary → "Start hub" cannot work; lead the
|
// No `fai` binary → "Start hub" cannot work; lead the
|
||||||
// operator to the install guide instead.
|
// operator to the install guide instead.
|
||||||
final canStart = SystemActions.faiBinaryExists();
|
final canStart = SystemActions.chainBinaryExists();
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.xl),
|
padding: const EdgeInsets.all(ChainSpace.xl),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.errorContainer.withValues(alpha: 0.35),
|
color: theme.colorScheme.errorContainer.withValues(alpha: 0.35),
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: theme.colorScheme.error.withValues(alpha: 0.4),
|
color: theme.colorScheme.error.withValues(alpha: 0.4),
|
||||||
),
|
),
|
||||||
|
|
@ -210,7 +210,7 @@ class _HubDownHeroState extends State<_HubDownHero> {
|
||||||
size: 24,
|
size: 24,
|
||||||
color: theme.colorScheme.error,
|
color: theme.colorScheme.error,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
l.welcomeHubDownTitle,
|
l.welcomeHubDownTitle,
|
||||||
|
|
@ -221,7 +221,7 @@ class _HubDownHeroState extends State<_HubDownHero> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Text(
|
Text(
|
||||||
l.welcomeHubDownBody,
|
l.welcomeHubDownBody,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
|
@ -229,15 +229,15 @@ class _HubDownHeroState extends State<_HubDownHero> {
|
||||||
height: 1.45,
|
height: 1.45,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.xs),
|
const SizedBox(height: ChainSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
l.welcomeHubDownEndpoint(HubService.instance.endpointLabel),
|
l.welcomeHubDownEndpoint(HubService.instance.endpointLabel),
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: ChainSpace.lg),
|
||||||
if (canStart)
|
if (canStart)
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: _starting ? null : _start,
|
onPressed: _starting ? null : _start,
|
||||||
|
|
@ -253,8 +253,8 @@ class _HubDownHeroState extends State<_HubDownHero> {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
const FaiBinaryRecovery(),
|
const ChainBinaryRecovery(),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
icon: const Icon(Icons.open_in_new, size: 16),
|
icon: const Icon(Icons.open_in_new, size: 16),
|
||||||
label: Text(l.welcomeHubDownDocsLink),
|
label: Text(l.welcomeHubDownDocsLink),
|
||||||
|
|
@ -323,7 +323,7 @@ class _PillarRow extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
for (final p in pillars) ...[
|
for (final p in pillars) ...[
|
||||||
_Pillar(icon: p.$1, title: p.$2, body: p.$3),
|
_Pillar(icon: p.$1, title: p.$2, body: p.$3),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -339,7 +339,7 @@ class _PillarRow extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
for (var i = 0; i < pillars.length; i++) ...[
|
for (var i = 0; i < pillars.length; i++) ...[
|
||||||
if (i > 0) const SizedBox(width: FaiSpace.md),
|
if (i > 0) const SizedBox(width: ChainSpace.md),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _Pillar(
|
child: _Pillar(
|
||||||
icon: pillars[i].$1,
|
icon: pillars[i].$1,
|
||||||
|
|
@ -367,24 +367,24 @@ class _Pillar extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(FaiSpace.lg),
|
padding: const EdgeInsets.all(ChainSpace.lg),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainer,
|
color: theme.colorScheme.surfaceContainer,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 28, color: theme.colorScheme.primary),
|
Icon(icon, size: 28, color: theme.colorScheme.primary),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: theme.textTheme.titleLarge?.copyWith(
|
style: theme.textTheme.titleLarge?.copyWith(
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
body,
|
body,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
|
@ -427,7 +427,7 @@ class _TrustPosture extends StatelessWidget {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
for (var i = 0; i < rows.length; i++) ...[
|
for (var i = 0; i < rows.length; i++) ...[
|
||||||
if (i > 0) const SizedBox(height: FaiSpace.sm),
|
if (i > 0) const SizedBox(height: ChainSpace.sm),
|
||||||
_TrustRow(icon: rows[i].$1, title: rows[i].$2, body: rows[i].$3),
|
_TrustRow(icon: rows[i].$1, title: rows[i].$2, body: rows[i].$3),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
@ -450,17 +450,17 @@ class _TrustRow extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(FaiSpace.lg),
|
padding: const EdgeInsets.all(ChainSpace.lg),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainer,
|
color: theme.colorScheme.surfaceContainer,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 22, color: theme.colorScheme.primary),
|
Icon(icon, size: 22, color: theme.colorScheme.primary),
|
||||||
const SizedBox(width: FaiSpace.lg),
|
const SizedBox(width: ChainSpace.lg),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -601,13 +601,13 @@ class _OnboardingChecklistState extends State<_OnboardingChecklist> {
|
||||||
_aiOk,
|
_aiOk,
|
||||||
l.welcomeChecklistAi,
|
l.welcomeChecklistAi,
|
||||||
l.welcomeChecklistAiHint,
|
l.welcomeChecklistAiHint,
|
||||||
() => FaiSettingsDialog.show(context),
|
() => ChainSettingsDialog.show(context),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_mcpOk,
|
_mcpOk,
|
||||||
l.welcomeChecklistMcp,
|
l.welcomeChecklistMcp,
|
||||||
l.welcomeChecklistMcpHint,
|
l.welcomeChecklistMcpHint,
|
||||||
() => FaiSettingsDialog.show(context),
|
() => ChainSettingsDialog.show(context),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
_moduleOk,
|
_moduleOk,
|
||||||
|
|
@ -623,7 +623,7 @@ class _OnboardingChecklistState extends State<_OnboardingChecklist> {
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: FaiSpace.xxl),
|
padding: const EdgeInsets.only(bottom: ChainSpace.xxl),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -661,11 +661,11 @@ class _OnboardingChecklistState extends State<_OnboardingChecklist> {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainer,
|
color: theme.colorScheme.surfaceContainer,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -684,7 +684,7 @@ class _OnboardingChecklistState extends State<_OnboardingChecklist> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (allDone) ...[
|
if (allDone) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
_AllDoneCelebration(onDismiss: _dismiss),
|
_AllDoneCelebration(onDismiss: _dismiss),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
@ -707,19 +707,19 @@ class _AllDoneCelebration extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(FaiSpace.lg),
|
padding: const EdgeInsets.all(ChainSpace.lg),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: FaiColors.success.withValues(alpha: 0.08),
|
color: ChainColors.success.withValues(alpha: 0.08),
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
border: Border.all(color: FaiColors.success.withValues(alpha: 0.35)),
|
border: Border.all(color: ChainColors.success.withValues(alpha: 0.35)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.celebration_outlined, color: FaiColors.success),
|
Icon(Icons.celebration_outlined, color: ChainColors.success),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
l.welcomeChecklistAllSetTitle,
|
l.welcomeChecklistAllSetTitle,
|
||||||
|
|
@ -741,7 +741,7 @@ class _AllDoneCelebration extends StatelessWidget {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
_NextStepRow(
|
_NextStepRow(
|
||||||
icon: Icons.timeline_outlined,
|
icon: Icons.timeline_outlined,
|
||||||
title: l.welcomeChecklistNextAuditTitle,
|
title: l.welcomeChecklistNextAuditTitle,
|
||||||
|
|
@ -797,12 +797,12 @@ class _NextStepRow extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final canActivate = doc != null && onOpenDoc != null;
|
final canActivate = doc != null && onOpenDoc != null;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: FaiSpace.sm),
|
padding: const EdgeInsets.only(bottom: ChainSpace.sm),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 18, color: theme.colorScheme.primary),
|
Icon(icon, size: 18, color: theme.colorScheme.primary),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -823,7 +823,7 @@ class _NextStepRow extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: canActivate ? () => onOpenDoc!(doc!) : null,
|
onPressed: canActivate ? () => onOpenDoc!(doc!) : null,
|
||||||
child: Text(buttonLabel),
|
child: Text(buttonLabel),
|
||||||
|
|
@ -856,16 +856,16 @@ class _ChecklistRow extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
final row = Padding(
|
final row = Padding(
|
||||||
padding: const EdgeInsets.all(FaiSpace.lg),
|
padding: const EdgeInsets.all(ChainSpace.lg),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
done ? Icons.check_circle : Icons.radio_button_unchecked,
|
done ? Icons.check_circle : Icons.radio_button_unchecked,
|
||||||
size: 20,
|
size: 20,
|
||||||
color: done ? FaiColors.success : theme.colorScheme.outline,
|
color: done ? ChainColors.success : theme.colorScheme.outline,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.lg),
|
const SizedBox(width: ChainSpace.lg),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -890,10 +890,10 @@ class _ChecklistRow extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: done ? l.welcomeChecklistDone : l.welcomeChecklistTodo,
|
label: done ? l.welcomeChecklistDone : l.welcomeChecklistTodo,
|
||||||
tone: done ? FaiPillTone.success : FaiPillTone.neutral,
|
tone: done ? ChainPillTone.success : ChainPillTone.neutral,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -985,7 +985,7 @@ class _DocsRow extends StatelessWidget {
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final twoCols = constraints.maxWidth >= 640;
|
final twoCols = constraints.maxWidth >= 640;
|
||||||
|
|
@ -997,16 +997,16 @@ class _DocsRow extends StatelessWidget {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
for (var i = 0; i < _kDocs.length; i++) ...[
|
for (var i = 0; i < _kDocs.length; i++) ...[
|
||||||
if (i > 0) const SizedBox(height: FaiSpace.md),
|
if (i > 0) const SizedBox(height: ChainSpace.md),
|
||||||
_DocCard(entry: _kDocs[i]),
|
_DocCard(entry: _kDocs[i]),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final cardWidth = (constraints.maxWidth - FaiSpace.md) / 2;
|
final cardWidth = (constraints.maxWidth - ChainSpace.md) / 2;
|
||||||
return Wrap(
|
return Wrap(
|
||||||
spacing: FaiSpace.md,
|
spacing: ChainSpace.md,
|
||||||
runSpacing: FaiSpace.md,
|
runSpacing: ChainSpace.md,
|
||||||
children: [
|
children: [
|
||||||
for (final d in _kDocs)
|
for (final d in _kDocs)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|
@ -1045,30 +1045,30 @@ class _DocCardState extends State<_DocCard> {
|
||||||
onEnter: (_) => setState(() => _hovered = true),
|
onEnter: (_) => setState(() => _hovered = true),
|
||||||
onExit: (_) => setState(() => _hovered = false),
|
onExit: (_) => setState(() => _hovered = false),
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: FaiMotion.base,
|
duration: ChainMotion.base,
|
||||||
curve: FaiMotion.easing,
|
curve: ChainMotion.easing,
|
||||||
transform: Matrix4.translationValues(0, _hovered ? -2 : 0, 0),
|
transform: Matrix4.translationValues(0, _hovered ? -2 : 0, 0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
boxShadow: _hovered ? FaiElevation.medium(theme.brightness) : null,
|
boxShadow: _hovered ? ChainElevation.medium(theme.brightness) : null,
|
||||||
),
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: theme.colorScheme.surfaceContainer,
|
color: theme.colorScheme.surfaceContainer,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => _DocReaderSheet.show(context, entry),
|
onTap: () => _DocReaderSheet.show(context, entry),
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(FaiSpace.lg),
|
padding: const EdgeInsets.all(ChainSpace.lg),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Icon(entry.icon, size: 22, color: theme.colorScheme.primary),
|
Icon(entry.icon, size: 22, color: theme.colorScheme.primary),
|
||||||
const SizedBox(width: FaiSpace.lg),
|
const SizedBox(width: ChainSpace.lg),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -1159,7 +1159,7 @@ class _DocReaderSheet extends StatefulWidget {
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
|
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(FaiRadius.md)),
|
borderRadius: BorderRadius.vertical(top: Radius.circular(ChainRadius.md)),
|
||||||
),
|
),
|
||||||
builder: (_) => _DocReaderSheet(entry: entry),
|
builder: (_) => _DocReaderSheet(entry: entry),
|
||||||
);
|
);
|
||||||
|
|
@ -1191,7 +1191,7 @@ class _DocReaderSheetState extends State<_DocReaderSheet> {
|
||||||
// the .dart_tool asset manifest can be inconsistent with
|
// the .dart_tool asset manifest can be inconsistent with
|
||||||
// the compiled binary — surface "rebuild Studio" as a
|
// the compiled binary — surface "rebuild Studio" as a
|
||||||
// first-class hint rather than just dumping the raw
|
// first-class hint rather than just dumping the raw
|
||||||
// PlatformException string into FaiErrorBox.
|
// PlatformException string into ChainErrorBox.
|
||||||
try {
|
try {
|
||||||
return await rootBundle.loadString(localised);
|
return await rootBundle.loadString(localised);
|
||||||
} catch (firstErr) {
|
} catch (firstErr) {
|
||||||
|
|
@ -1219,7 +1219,7 @@ class _DocReaderSheetState extends State<_DocReaderSheet> {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: FaiSpace.sm),
|
padding: const EdgeInsets.symmetric(vertical: ChainSpace.sm),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 4,
|
height: 4,
|
||||||
|
|
@ -1231,10 +1231,10 @@ class _DocReaderSheetState extends State<_DocReaderSheet> {
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
FaiSpace.xxl,
|
ChainSpace.xxl,
|
||||||
FaiSpace.sm,
|
ChainSpace.sm,
|
||||||
FaiSpace.lg,
|
ChainSpace.lg,
|
||||||
FaiSpace.sm,
|
ChainSpace.sm,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -1243,7 +1243,7 @@ class _DocReaderSheetState extends State<_DocReaderSheet> {
|
||||||
size: 20,
|
size: 20,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.entry.title(l),
|
widget.entry.title(l),
|
||||||
|
|
@ -1268,7 +1268,7 @@ class _DocReaderSheetState extends State<_DocReaderSheet> {
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
if (snap.connectionState == ConnectionState.waiting) {
|
if (snap.connectionState == ConnectionState.waiting) {
|
||||||
return const Padding(
|
return const Padding(
|
||||||
padding: EdgeInsets.all(FaiSpace.xxl),
|
padding: EdgeInsets.all(ChainSpace.xxl),
|
||||||
child: Center(child: CircularProgressIndicator()),
|
child: Center(child: CircularProgressIndicator()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1276,7 +1276,7 @@ class _DocReaderSheetState extends State<_DocReaderSheet> {
|
||||||
final err = snap.error;
|
final err = snap.error;
|
||||||
final structured = err is _DocReaderError ? err : null;
|
final structured = err is _DocReaderError ? err : null;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(FaiSpace.xxl),
|
padding: const EdgeInsets.all(ChainSpace.xxl),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -1286,21 +1286,21 @@ class _DocReaderSheetState extends State<_DocReaderSheet> {
|
||||||
color: theme.colorScheme.error,
|
color: theme.colorScheme.error,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
if (structured != null)
|
if (structured != null)
|
||||||
FaiErrorBox(
|
ChainErrorBox(
|
||||||
text: structured.localizedBody(l),
|
text: structured.localizedBody(l),
|
||||||
isError: true,
|
isError: true,
|
||||||
maxHeight: 200,
|
maxHeight: 200,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
FaiErrorBox(
|
ChainErrorBox(
|
||||||
error: err,
|
error: err,
|
||||||
isError: true,
|
isError: true,
|
||||||
maxHeight: 200,
|
maxHeight: 200,
|
||||||
),
|
),
|
||||||
if (structured != null) ...[
|
if (structured != null) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
icon: const Icon(Icons.open_in_new, size: 16),
|
icon: const Icon(Icons.open_in_new, size: 16),
|
||||||
label: Text(structured.forgejoUrl),
|
label: Text(structured.forgejoUrl),
|
||||||
|
|
@ -1314,14 +1314,14 @@ class _DocReaderSheetState extends State<_DocReaderSheet> {
|
||||||
}
|
}
|
||||||
return Markdown(
|
return Markdown(
|
||||||
data: snap.data ?? '',
|
data: snap.data ?? '',
|
||||||
padding: const EdgeInsets.all(FaiSpace.xxl),
|
padding: const EdgeInsets.all(ChainSpace.xxl),
|
||||||
selectable: true,
|
selectable: true,
|
||||||
onTapLink: (text, href, title) async {
|
onTapLink: (text, href, title) async {
|
||||||
if (href != null && href.isNotEmpty) {
|
if (href != null && href.isNotEmpty) {
|
||||||
await SystemActions.openInOs(href);
|
await SystemActions.openInOs(href);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
styleSheet: FaiTheme.markdownStyle(theme),
|
styleSheet: ChainTheme.markdownStyle(theme),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
|
||||||
|
|
||||||
import 'tokens.dart';
|
import 'tokens.dart';
|
||||||
|
|
||||||
class FaiTheme {
|
class ChainTheme {
|
||||||
FaiTheme._();
|
ChainTheme._();
|
||||||
|
|
||||||
/// Bundled font families. Declared in pubspec.yaml `fonts:` and
|
/// Bundled font families. Declared in pubspec.yaml `fonts:` and
|
||||||
/// shipped as TTF assets under `assets/fonts/` — NOT fetched at
|
/// shipped as TTF assets under `assets/fonts/` — NOT fetched at
|
||||||
|
|
@ -79,7 +79,7 @@ class FaiTheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JetBrains Mono for monospaced technical content. Used by
|
/// JetBrains Mono for monospaced technical content. Used by
|
||||||
/// FaiMono helper. Only Regular (400) and Medium (500) weights
|
/// ChainMono helper. Only Regular (400) and Medium (500) weights
|
||||||
/// are bundled — heavier requests fall back to the nearest.
|
/// are bundled — heavier requests fall back to the nearest.
|
||||||
static TextStyle mono({
|
static TextStyle mono({
|
||||||
double size = 12,
|
double size = 12,
|
||||||
|
|
@ -125,25 +125,25 @@ class FaiTheme {
|
||||||
).copyWith(backgroundColor: Colors.transparent),
|
).copyWith(backgroundColor: Colors.transparent),
|
||||||
codeblockDecoration: BoxDecoration(
|
codeblockDecoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
codeblockPadding: const EdgeInsets.all(FaiSpace.md),
|
codeblockPadding: const EdgeInsets.all(ChainSpace.md),
|
||||||
blockquote: theme.textTheme.bodyMedium?.copyWith(
|
blockquote: theme.textTheme.bodyMedium?.copyWith(
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
blockquoteDecoration: BoxDecoration(
|
blockquoteDecoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainer,
|
color: theme.colorScheme.surfaceContainer,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border(
|
border: Border(
|
||||||
left: BorderSide(color: theme.colorScheme.primary, width: 3),
|
left: BorderSide(color: theme.colorScheme.primary, width: 3),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
blockquotePadding: const EdgeInsets.fromLTRB(
|
blockquotePadding: const EdgeInsets.fromLTRB(
|
||||||
FaiSpace.md,
|
ChainSpace.md,
|
||||||
FaiSpace.sm,
|
ChainSpace.sm,
|
||||||
FaiSpace.sm,
|
ChainSpace.sm,
|
||||||
FaiSpace.sm,
|
ChainSpace.sm,
|
||||||
),
|
),
|
||||||
tableCellsDecoration: BoxDecoration(
|
tableCellsDecoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
|
|
@ -164,32 +164,32 @@ class FaiTheme {
|
||||||
static ThemeData dark() {
|
static ThemeData dark() {
|
||||||
final scheme = ColorScheme(
|
final scheme = ColorScheme(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
primary: FaiColors.accent,
|
primary: ChainColors.accent,
|
||||||
onPrimary: FaiColors.black,
|
onPrimary: ChainColors.black,
|
||||||
primaryContainer: FaiColors.accentMuted,
|
primaryContainer: ChainColors.accentMuted,
|
||||||
onPrimaryContainer: FaiColors.textStrong,
|
onPrimaryContainer: ChainColors.textStrong,
|
||||||
secondary: FaiColors.accentDeep,
|
secondary: ChainColors.accentDeep,
|
||||||
onSecondary: FaiColors.black,
|
onSecondary: ChainColors.black,
|
||||||
secondaryContainer: FaiColors.surfaceHigh,
|
secondaryContainer: ChainColors.surfaceHigh,
|
||||||
onSecondaryContainer: FaiColors.text,
|
onSecondaryContainer: ChainColors.text,
|
||||||
tertiary: FaiColors.accent,
|
tertiary: ChainColors.accent,
|
||||||
onTertiary: FaiColors.black,
|
onTertiary: ChainColors.black,
|
||||||
tertiaryContainer: FaiColors.surfaceHigh,
|
tertiaryContainer: ChainColors.surfaceHigh,
|
||||||
onTertiaryContainer: FaiColors.text,
|
onTertiaryContainer: ChainColors.text,
|
||||||
error: FaiColors.danger,
|
error: ChainColors.danger,
|
||||||
onError: FaiColors.textStrong,
|
onError: ChainColors.textStrong,
|
||||||
errorContainer: const Color(0xFF3F1518),
|
errorContainer: const Color(0xFF3F1518),
|
||||||
onErrorContainer: const Color(0xFFFCA5A5),
|
onErrorContainer: const Color(0xFFFCA5A5),
|
||||||
surface: FaiColors.black,
|
surface: ChainColors.black,
|
||||||
onSurface: FaiColors.text,
|
onSurface: ChainColors.text,
|
||||||
onSurfaceVariant: FaiColors.muted,
|
onSurfaceVariant: ChainColors.muted,
|
||||||
outline: FaiColors.border,
|
outline: ChainColors.border,
|
||||||
outlineVariant: FaiColors.border,
|
outlineVariant: ChainColors.border,
|
||||||
surfaceContainerLowest: FaiColors.black,
|
surfaceContainerLowest: ChainColors.black,
|
||||||
surfaceContainerLow: FaiColors.surface,
|
surfaceContainerLow: ChainColors.surface,
|
||||||
surfaceContainer: FaiColors.surface,
|
surfaceContainer: ChainColors.surface,
|
||||||
surfaceContainerHigh: FaiColors.surfaceHigh,
|
surfaceContainerHigh: ChainColors.surfaceHigh,
|
||||||
surfaceContainerHighest: FaiColors.surfaceHigh,
|
surfaceContainerHighest: ChainColors.surfaceHigh,
|
||||||
);
|
);
|
||||||
return _build(scheme);
|
return _build(scheme);
|
||||||
}
|
}
|
||||||
|
|
@ -197,32 +197,32 @@ class FaiTheme {
|
||||||
static ThemeData light() {
|
static ThemeData light() {
|
||||||
final scheme = ColorScheme(
|
final scheme = ColorScheme(
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
primary: FaiColors.accentDeep,
|
primary: ChainColors.accentDeep,
|
||||||
onPrimary: FaiColors.lightSurface,
|
onPrimary: ChainColors.lightSurface,
|
||||||
primaryContainer: const Color(0xFFE0F2FE),
|
primaryContainer: const Color(0xFFE0F2FE),
|
||||||
onPrimaryContainer: FaiColors.accentMuted,
|
onPrimaryContainer: ChainColors.accentMuted,
|
||||||
secondary: FaiColors.accentMuted,
|
secondary: ChainColors.accentMuted,
|
||||||
onSecondary: FaiColors.lightSurface,
|
onSecondary: ChainColors.lightSurface,
|
||||||
secondaryContainer: FaiColors.lightSurfaceHigh,
|
secondaryContainer: ChainColors.lightSurfaceHigh,
|
||||||
onSecondaryContainer: FaiColors.lightText,
|
onSecondaryContainer: ChainColors.lightText,
|
||||||
tertiary: FaiColors.accentDeep,
|
tertiary: ChainColors.accentDeep,
|
||||||
onTertiary: FaiColors.lightSurface,
|
onTertiary: ChainColors.lightSurface,
|
||||||
tertiaryContainer: const Color(0xFFE0F2FE),
|
tertiaryContainer: const Color(0xFFE0F2FE),
|
||||||
onTertiaryContainer: FaiColors.accentMuted,
|
onTertiaryContainer: ChainColors.accentMuted,
|
||||||
error: FaiColors.danger,
|
error: ChainColors.danger,
|
||||||
onError: FaiColors.lightSurface,
|
onError: ChainColors.lightSurface,
|
||||||
errorContainer: const Color(0xFFFEE2E2),
|
errorContainer: const Color(0xFFFEE2E2),
|
||||||
onErrorContainer: const Color(0xFF7F1D1D),
|
onErrorContainer: const Color(0xFF7F1D1D),
|
||||||
surface: FaiColors.lightCanvas,
|
surface: ChainColors.lightCanvas,
|
||||||
onSurface: FaiColors.lightText,
|
onSurface: ChainColors.lightText,
|
||||||
onSurfaceVariant: FaiColors.lightMuted,
|
onSurfaceVariant: ChainColors.lightMuted,
|
||||||
outline: FaiColors.lightBorder,
|
outline: ChainColors.lightBorder,
|
||||||
outlineVariant: FaiColors.lightBorder,
|
outlineVariant: ChainColors.lightBorder,
|
||||||
surfaceContainerLowest: FaiColors.lightCanvas,
|
surfaceContainerLowest: ChainColors.lightCanvas,
|
||||||
surfaceContainerLow: FaiColors.lightSurface,
|
surfaceContainerLow: ChainColors.lightSurface,
|
||||||
surfaceContainer: FaiColors.lightSurface,
|
surfaceContainer: ChainColors.lightSurface,
|
||||||
surfaceContainerHigh: FaiColors.lightSurfaceHigh,
|
surfaceContainerHigh: ChainColors.lightSurfaceHigh,
|
||||||
surfaceContainerHighest: FaiColors.lightSurfaceHigh,
|
surfaceContainerHighest: ChainColors.lightSurfaceHigh,
|
||||||
);
|
);
|
||||||
return _build(scheme);
|
return _build(scheme);
|
||||||
}
|
}
|
||||||
|
|
@ -256,7 +256,7 @@ class FaiTheme {
|
||||||
color: scheme.surfaceContainer,
|
color: scheme.surfaceContainer,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
side: BorderSide(color: scheme.outlineVariant),
|
side: BorderSide(color: scheme.outlineVariant),
|
||||||
),
|
),
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
|
|
@ -266,14 +266,14 @@ class FaiTheme {
|
||||||
backgroundColor: scheme.primary,
|
backgroundColor: scheme.primary,
|
||||||
foregroundColor: scheme.onPrimary,
|
foregroundColor: scheme.onPrimary,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.lg,
|
horizontal: ChainSpace.lg,
|
||||||
vertical: FaiSpace.md,
|
vertical: ChainSpace.md,
|
||||||
),
|
),
|
||||||
textStyle: textTheme.labelMedium,
|
textStyle: textTheme.labelMedium,
|
||||||
animationDuration: FaiMotion.fast,
|
animationDuration: ChainMotion.fast,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||||
|
|
@ -281,14 +281,14 @@ class FaiTheme {
|
||||||
foregroundColor: scheme.onSurface,
|
foregroundColor: scheme.onSurface,
|
||||||
side: BorderSide(color: scheme.outline),
|
side: BorderSide(color: scheme.outline),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.lg,
|
horizontal: ChainSpace.lg,
|
||||||
vertical: FaiSpace.md,
|
vertical: ChainSpace.md,
|
||||||
),
|
),
|
||||||
textStyle: textTheme.labelMedium,
|
textStyle: textTheme.labelMedium,
|
||||||
animationDuration: FaiMotion.fast,
|
animationDuration: ChainMotion.fast,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
iconButtonTheme: IconButtonThemeData(
|
iconButtonTheme: IconButtonThemeData(
|
||||||
|
|
@ -323,7 +323,7 @@ class FaiTheme {
|
||||||
),
|
),
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// Colour palette. Single strong accent (Ch∆In Cyan) plus neutral
|
/// Colour palette. Single strong accent (Ch∆In Cyan) plus neutral
|
||||||
/// greys. Status colours stay restrained — no candy palette.
|
/// greys. Status colours stay restrained — no candy palette.
|
||||||
class FaiColors {
|
class ChainColors {
|
||||||
FaiColors._();
|
ChainColors._();
|
||||||
|
|
||||||
// Brand accent. Slightly cooler / more saturated than the
|
// Brand accent. Slightly cooler / more saturated than the
|
||||||
// initial #1E3A8A — this reads more as "deep tech, alive"
|
// initial #1E3A8A — this reads more as "deep tech, alive"
|
||||||
|
|
@ -43,8 +43,8 @@ class FaiColors {
|
||||||
|
|
||||||
/// Spacing scale. Stick to multiples of 4. Keep the vocabulary
|
/// Spacing scale. Stick to multiples of 4. Keep the vocabulary
|
||||||
/// small — six steps cover everything.
|
/// small — six steps cover everything.
|
||||||
class FaiSpace {
|
class ChainSpace {
|
||||||
FaiSpace._();
|
ChainSpace._();
|
||||||
static const xs = 4.0;
|
static const xs = 4.0;
|
||||||
static const sm = 8.0;
|
static const sm = 8.0;
|
||||||
static const md = 12.0;
|
static const md = 12.0;
|
||||||
|
|
@ -55,8 +55,8 @@ class FaiSpace {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Border radii. Two sizes only.
|
/// Border radii. Two sizes only.
|
||||||
class FaiRadius {
|
class ChainRadius {
|
||||||
FaiRadius._();
|
ChainRadius._();
|
||||||
static const sm = 6.0;
|
static const sm = 6.0;
|
||||||
static const md = 10.0;
|
static const md = 10.0;
|
||||||
}
|
}
|
||||||
|
|
@ -66,8 +66,8 @@ class FaiRadius {
|
||||||
/// Brightness-aware: dark themes lean on deeper, lower-alpha
|
/// Brightness-aware: dark themes lean on deeper, lower-alpha
|
||||||
/// shadows (they read as ambient occlusion against near-black
|
/// shadows (they read as ambient occlusion against near-black
|
||||||
/// surfaces); light themes use softer, slightly larger spreads.
|
/// surfaces); light themes use softer, slightly larger spreads.
|
||||||
class FaiElevation {
|
class ChainElevation {
|
||||||
FaiElevation._();
|
ChainElevation._();
|
||||||
|
|
||||||
/// Resting elevation for cards and sheets. Just enough to lift
|
/// Resting elevation for cards and sheets. Just enough to lift
|
||||||
/// a surface off the canvas without announcing itself.
|
/// a surface off the canvas without announcing itself.
|
||||||
|
|
@ -106,8 +106,8 @@ class FaiElevation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Animation durations. Keep them under 300ms for power-user feel.
|
/// Animation durations. Keep them under 300ms for power-user feel.
|
||||||
class FaiMotion {
|
class ChainMotion {
|
||||||
FaiMotion._();
|
ChainMotion._();
|
||||||
static const fast = Duration(milliseconds: 120);
|
static const fast = Duration(milliseconds: 120);
|
||||||
static const base = Duration(milliseconds: 200);
|
static const base = Duration(milliseconds: 200);
|
||||||
static const slow = Duration(milliseconds: 320);
|
static const slow = Duration(milliseconds: 320);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Recovery affordance shown when Studio cannot locate the `fai`
|
// Recovery affordance shown when Studio cannot locate the `fai`
|
||||||
// binary on the machine. Replaces the old CLI-jargon dead-end
|
// binary on the machine. Replaces the old CLI-jargon dead-end
|
||||||
// ("set FAI_BIN to its full path") with two acts a non-CLI
|
// ("set CHAIN_BIN to its full path") with two acts a non-CLI
|
||||||
// operator can actually take:
|
// operator can actually take:
|
||||||
//
|
//
|
||||||
// 1. Locate the binary with a native file picker, persisting
|
// 1. Locate the binary with a native file picker, persisting
|
||||||
|
|
@ -14,26 +14,26 @@ import '../data/system_actions.dart';
|
||||||
import '../l10n/app_localizations.dart';
|
import '../l10n/app_localizations.dart';
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
|
|
||||||
class FaiBinaryRecovery extends StatelessWidget {
|
class ChainBinaryRecovery extends StatelessWidget {
|
||||||
/// Fired after the operator successfully picks a `fai` binary,
|
/// Fired after the operator successfully picks a `fai` binary,
|
||||||
/// so the host can retry whatever action hit the missing
|
/// so the host can retry whatever action hit the missing
|
||||||
/// binary (e.g. re-run the daemon start / status probe).
|
/// binary (e.g. re-run the daemon start / status probe).
|
||||||
final VoidCallback? onLocated;
|
final VoidCallback? onLocated;
|
||||||
|
|
||||||
const FaiBinaryRecovery({super.key, this.onLocated});
|
const ChainBinaryRecovery({super.key, this.onLocated});
|
||||||
|
|
||||||
Future<void> _locate(BuildContext context) async {
|
Future<void> _locate(BuildContext context) async {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
final messenger = ScaffoldMessenger.of(context);
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
final picked = await FilePicker.pickFiles(
|
final picked = await FilePicker.pickFiles(
|
||||||
dialogTitle: l.faiBinaryPickerTitle,
|
dialogTitle: l.chainBinaryPickerTitle,
|
||||||
);
|
);
|
||||||
final path = picked?.files.single.path;
|
final path = picked?.files.single.path;
|
||||||
if (path == null) return;
|
if (path == null) return;
|
||||||
final ok = await SystemActions.setFaiBinaryPath(path);
|
final ok = await SystemActions.setFaiBinaryPath(path);
|
||||||
messenger.showSnackBar(
|
messenger.showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(ok ? l.faiBinarySetOk(path) : l.faiBinaryNotFound),
|
content: Text(ok ? l.chainBinarySetOk(path) : l.chainBinaryNotFound),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (ok) onLocated?.call();
|
if (ok) onLocated?.call();
|
||||||
|
|
@ -45,10 +45,10 @@ class FaiBinaryRecovery extends StatelessWidget {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.md),
|
padding: const EdgeInsets.all(ChainSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.errorContainer.withValues(alpha: 0.4),
|
color: theme.colorScheme.errorContainer.withValues(alpha: 0.4),
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: theme.colorScheme.error.withValues(alpha: 0.4),
|
color: theme.colorScheme.error.withValues(alpha: 0.4),
|
||||||
),
|
),
|
||||||
|
|
@ -57,34 +57,34 @@ class FaiBinaryRecovery extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
l.faiBinaryNotFound,
|
l.chainBinaryNotFound,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: theme.colorScheme.error,
|
color: theme.colorScheme.error,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.xs),
|
const SizedBox(height: ChainSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
l.faiBinaryNotFoundHint,
|
l.chainBinaryNotFoundHint,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: FaiSpace.sm,
|
spacing: ChainSpace.sm,
|
||||||
runSpacing: FaiSpace.sm,
|
runSpacing: ChainSpace.sm,
|
||||||
children: [
|
children: [
|
||||||
FilledButton.tonalIcon(
|
FilledButton.tonalIcon(
|
||||||
onPressed: () => _locate(context),
|
onPressed: () => _locate(context),
|
||||||
icon: const Icon(Icons.folder_open, size: 16),
|
icon: const Icon(Icons.folder_open, size: 16),
|
||||||
label: Text(l.faiBinaryLocateButton),
|
label: Text(l.chainBinaryLocateButton),
|
||||||
),
|
),
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
SystemActions.openInOs(kFaiInstallDocsUrl),
|
SystemActions.openInOs(kFaiInstallDocsUrl),
|
||||||
icon: const Icon(Icons.open_in_new, size: 16),
|
icon: const Icon(Icons.open_in_new, size: 16),
|
||||||
label: Text(l.faiBinaryInstallDocsButton),
|
label: Text(l.chainBinaryInstallDocsButton),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
// FaiCard — flat card with subtle accent gradient on the top
|
// ChainCard — flat card with subtle accent gradient on the top
|
||||||
// border. Replaces Material's default Card for the Ch∆In look.
|
// border. Replaces Material's default Card for the Ch∆In look.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
|
|
||||||
class FaiCard extends StatelessWidget {
|
class ChainCard extends StatelessWidget {
|
||||||
/// Card body. Padding is applied internally; pass plain content.
|
/// Card body. Padding is applied internally; pass plain content.
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
|
|
@ -18,15 +18,15 @@ class FaiCard extends StatelessWidget {
|
||||||
/// Used by status rows (live event, healthy service).
|
/// Used by status rows (live event, healthy service).
|
||||||
final Color? accentLeft;
|
final Color? accentLeft;
|
||||||
|
|
||||||
/// Internal padding. Defaults to [FaiSpace.lg].
|
/// Internal padding. Defaults to [ChainSpace.lg].
|
||||||
final EdgeInsetsGeometry padding;
|
final EdgeInsetsGeometry padding;
|
||||||
|
|
||||||
const FaiCard({
|
const ChainCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.child,
|
required this.child,
|
||||||
this.accentTop = false,
|
this.accentTop = false,
|
||||||
this.accentLeft,
|
this.accentLeft,
|
||||||
this.padding = const EdgeInsets.all(FaiSpace.lg),
|
this.padding = const EdgeInsets.all(ChainSpace.lg),
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -35,7 +35,7 @@ class FaiCard extends StatelessWidget {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainer,
|
color: theme.colorScheme.surfaceContainer,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
|
|
@ -61,8 +61,8 @@ class FaiCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (accentLeft != null)
|
if (accentLeft != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
top: FaiSpace.md,
|
top: ChainSpace.md,
|
||||||
bottom: FaiSpace.md,
|
bottom: ChainSpace.md,
|
||||||
left: 0,
|
left: 0,
|
||||||
width: 3,
|
width: 3,
|
||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// FaiDataRow — Linear-style dense list row used by the audit
|
// ChainDataRow — Linear-style dense list row used by the audit
|
||||||
// stream. Hover-elevation, accent-coloured leading stripe, mono
|
// stream. Hover-elevation, accent-coloured leading stripe, mono
|
||||||
// timestamp, expressive type badge.
|
// timestamp, expressive type badge.
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
|
||||||
import '../theme/theme.dart';
|
import '../theme/theme.dart';
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
|
|
||||||
class FaiDataRow extends StatefulWidget {
|
class ChainDataRow extends StatefulWidget {
|
||||||
/// Coloured leading stripe (4px). Used to encode event type
|
/// Coloured leading stripe (4px). Used to encode event type
|
||||||
/// (success / warning / failure) at a glance.
|
/// (success / warning / failure) at a glance.
|
||||||
final Color accent;
|
final Color accent;
|
||||||
|
|
@ -27,7 +27,7 @@ class FaiDataRow extends StatefulWidget {
|
||||||
/// Optional tap handler.
|
/// Optional tap handler.
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
const FaiDataRow({
|
const ChainDataRow({
|
||||||
super.key,
|
super.key,
|
||||||
required this.accent,
|
required this.accent,
|
||||||
required this.leading,
|
required this.leading,
|
||||||
|
|
@ -38,10 +38,10 @@ class FaiDataRow extends StatefulWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FaiDataRow> createState() => _FaiDataRowState();
|
State<ChainDataRow> createState() => _FaiDataRowState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FaiDataRowState extends State<FaiDataRow> {
|
class _FaiDataRowState extends State<ChainDataRow> {
|
||||||
bool _hovered = false;
|
bool _hovered = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -57,12 +57,12 @@ class _FaiDataRowState extends State<FaiDataRow> {
|
||||||
onTap: widget.onTap,
|
onTap: widget.onTap,
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: FaiMotion.fast,
|
duration: ChainMotion.fast,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _hovered
|
color: _hovered
|
||||||
? theme.colorScheme.surfaceContainerHigh
|
? theme.colorScheme.surfaceContainerHigh
|
||||||
: theme.colorScheme.surfaceContainer,
|
: theme.colorScheme.surfaceContainer,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _hovered
|
color: _hovered
|
||||||
? theme.colorScheme.outline
|
? theme.colorScheme.outline
|
||||||
|
|
@ -70,8 +70,8 @@ class _FaiDataRowState extends State<FaiDataRow> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.md,
|
horizontal: ChainSpace.md,
|
||||||
vertical: FaiSpace.sm,
|
vertical: ChainSpace.sm,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -83,12 +83,12 @@ class _FaiDataRowState extends State<FaiDataRow> {
|
||||||
borderRadius: BorderRadius.circular(2),
|
borderRadius: BorderRadius.circular(2),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 88,
|
width: 88,
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.leading,
|
widget.leading,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -98,7 +98,7 @@ class _FaiDataRowState extends State<FaiDataRow> {
|
||||||
width: 200,
|
width: 200,
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.title,
|
widget.title,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 12,
|
size: 12,
|
||||||
weight: FontWeight.w500,
|
weight: FontWeight.w500,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
|
|
@ -116,10 +116,10 @@ class _FaiDataRowState extends State<FaiDataRow> {
|
||||||
),
|
),
|
||||||
if (widget.trailing != null)
|
if (widget.trailing != null)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: FaiSpace.md),
|
padding: const EdgeInsets.only(left: ChainSpace.md),
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.trailing!,
|
widget.trailing!,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// FaiDeltaMark — the Ch∆In signature element. A precise triangle
|
// ChainDeltaMark — the Ch∆In signature element. A precise triangle
|
||||||
// (∆) drawn from primitives, not a font glyph. Three states:
|
// (∆) drawn from primitives, not a font glyph. Three states:
|
||||||
//
|
//
|
||||||
// - idle: static, accent colour, soft glow
|
// - idle: static, accent colour, soft glow
|
||||||
|
|
@ -12,25 +12,25 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
enum FaiDeltaMode { idle, live, busy }
|
enum ChainDeltaMode { idle, live, busy }
|
||||||
|
|
||||||
class FaiDeltaMark extends StatefulWidget {
|
class ChainDeltaMark extends StatefulWidget {
|
||||||
final FaiDeltaMode mode;
|
final ChainDeltaMode mode;
|
||||||
final double size;
|
final double size;
|
||||||
final Color color;
|
final Color color;
|
||||||
|
|
||||||
const FaiDeltaMark({
|
const ChainDeltaMark({
|
||||||
super.key,
|
super.key,
|
||||||
required this.color,
|
required this.color,
|
||||||
this.mode = FaiDeltaMode.idle,
|
this.mode = ChainDeltaMode.idle,
|
||||||
this.size = 36,
|
this.size = 36,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FaiDeltaMark> createState() => _FaiDeltaMarkState();
|
State<ChainDeltaMark> createState() => _FaiDeltaMarkState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FaiDeltaMarkState extends State<FaiDeltaMark>
|
class _FaiDeltaMarkState extends State<ChainDeltaMark>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late final AnimationController _ctrl;
|
late final AnimationController _ctrl;
|
||||||
|
|
||||||
|
|
@ -44,13 +44,13 @@ class _FaiDeltaMarkState extends State<FaiDeltaMark>
|
||||||
_restart();
|
_restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
Duration _durationFor(FaiDeltaMode m) {
|
Duration _durationFor(ChainDeltaMode m) {
|
||||||
switch (m) {
|
switch (m) {
|
||||||
case FaiDeltaMode.idle:
|
case ChainDeltaMode.idle:
|
||||||
return const Duration(seconds: 1);
|
return const Duration(seconds: 1);
|
||||||
case FaiDeltaMode.live:
|
case ChainDeltaMode.live:
|
||||||
return const Duration(milliseconds: 1600);
|
return const Duration(milliseconds: 1600);
|
||||||
case FaiDeltaMode.busy:
|
case ChainDeltaMode.busy:
|
||||||
return const Duration(seconds: 4);
|
return const Duration(seconds: 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,21 +58,21 @@ class _FaiDeltaMarkState extends State<FaiDeltaMark>
|
||||||
void _restart() {
|
void _restart() {
|
||||||
_ctrl.duration = _durationFor(widget.mode);
|
_ctrl.duration = _durationFor(widget.mode);
|
||||||
switch (widget.mode) {
|
switch (widget.mode) {
|
||||||
case FaiDeltaMode.idle:
|
case ChainDeltaMode.idle:
|
||||||
_ctrl.stop();
|
_ctrl.stop();
|
||||||
_ctrl.value = 0;
|
_ctrl.value = 0;
|
||||||
break;
|
break;
|
||||||
case FaiDeltaMode.live:
|
case ChainDeltaMode.live:
|
||||||
_ctrl.repeat(reverse: true);
|
_ctrl.repeat(reverse: true);
|
||||||
break;
|
break;
|
||||||
case FaiDeltaMode.busy:
|
case ChainDeltaMode.busy:
|
||||||
_ctrl.repeat();
|
_ctrl.repeat();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant FaiDeltaMark old) {
|
void didUpdateWidget(covariant ChainDeltaMark old) {
|
||||||
super.didUpdateWidget(old);
|
super.didUpdateWidget(old);
|
||||||
if (old.mode != widget.mode) _restart();
|
if (old.mode != widget.mode) _restart();
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +103,7 @@ class _FaiDeltaMarkState extends State<FaiDeltaMark>
|
||||||
|
|
||||||
class _DeltaPainter extends CustomPainter {
|
class _DeltaPainter extends CustomPainter {
|
||||||
final Color color;
|
final Color color;
|
||||||
final FaiDeltaMode mode;
|
final ChainDeltaMode mode;
|
||||||
final double t;
|
final double t;
|
||||||
|
|
||||||
_DeltaPainter({required this.color, required this.mode, required this.t});
|
_DeltaPainter({required this.color, required this.mode, required this.t});
|
||||||
|
|
@ -115,12 +115,12 @@ class _DeltaPainter extends CustomPainter {
|
||||||
|
|
||||||
// In live mode: scale the whole mark between 0.85 and 1.15
|
// In live mode: scale the whole mark between 0.85 and 1.15
|
||||||
// so the pulse is visible without staring. Idle stays at 1.0.
|
// so the pulse is visible without staring. Idle stays at 1.0.
|
||||||
final scale = mode == FaiDeltaMode.live ? 0.85 + 0.30 * t : 1.0;
|
final scale = mode == ChainDeltaMode.live ? 0.85 + 0.30 * t : 1.0;
|
||||||
final r = size.width * 0.36 * scale;
|
final r = size.width * 0.36 * scale;
|
||||||
|
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.translate(cx, cy);
|
canvas.translate(cx, cy);
|
||||||
if (mode == FaiDeltaMode.busy) {
|
if (mode == ChainDeltaMode.busy) {
|
||||||
canvas.rotate(t * 2 * pi);
|
canvas.rotate(t * 2 * pi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,8 +133,8 @@ class _DeltaPainter extends CustomPainter {
|
||||||
|
|
||||||
// Glow halo. In live mode the glow ring grows with the pulse
|
// Glow halo. In live mode the glow ring grows with the pulse
|
||||||
// and the alpha breathes harder.
|
// and the alpha breathes harder.
|
||||||
if (mode != FaiDeltaMode.idle) {
|
if (mode != ChainDeltaMode.idle) {
|
||||||
final pulseStrength = mode == FaiDeltaMode.live ? t : 1.0;
|
final pulseStrength = mode == ChainDeltaMode.live ? t : 1.0;
|
||||||
final glow = Paint()
|
final glow = Paint()
|
||||||
..color = color.withValues(alpha: 0.30 + 0.40 * pulseStrength)
|
..color = color.withValues(alpha: 0.30 + 0.40 * pulseStrength)
|
||||||
..maskFilter = MaskFilter.blur(BlurStyle.normal, 8 + 14 * pulseStrength)
|
..maskFilter = MaskFilter.blur(BlurStyle.normal, 8 + 14 * pulseStrength)
|
||||||
|
|
@ -143,7 +143,7 @@ class _DeltaPainter extends CustomPainter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filled triangle, brighter on the up-beat for live mode.
|
// Filled triangle, brighter on the up-beat for live mode.
|
||||||
final fillAlpha = mode == FaiDeltaMode.live ? 0.15 + 0.20 * t : 0.18;
|
final fillAlpha = mode == ChainDeltaMode.live ? 0.15 + 0.20 * t : 0.18;
|
||||||
final fill = Paint()
|
final fill = Paint()
|
||||||
..color = color.withValues(alpha: fillAlpha)
|
..color = color.withValues(alpha: fillAlpha)
|
||||||
..style = PaintingStyle.fill;
|
..style = PaintingStyle.fill;
|
||||||
|
|
@ -158,7 +158,7 @@ class _DeltaPainter extends CustomPainter {
|
||||||
canvas.drawPath(path, stroke);
|
canvas.drawPath(path, stroke);
|
||||||
|
|
||||||
// Inner accent dot.
|
// Inner accent dot.
|
||||||
final dotAlpha = mode == FaiDeltaMode.live ? 0.55 + 0.45 * t : 1.0;
|
final dotAlpha = mode == ChainDeltaMode.live ? 0.55 + 0.45 * t : 1.0;
|
||||||
final dot = Paint()
|
final dot = Paint()
|
||||||
..color = color.withValues(alpha: dotAlpha)
|
..color = color.withValues(alpha: dotAlpha)
|
||||||
..style = PaintingStyle.fill;
|
..style = PaintingStyle.fill;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// FaiEmptyState — gracious empty / loading / error placeholder.
|
// ChainEmptyState — gracious empty / loading / error placeholder.
|
||||||
// Replaces "(no data)" strings with one tone, one icon, one tip.
|
// Replaces "(no data)" strings with one tone, one icon, one tip.
|
||||||
//
|
//
|
||||||
// The blank surface is where an app most easily feels unfinished,
|
// The blank surface is where an app most easily feels unfinished,
|
||||||
|
|
@ -11,9 +11,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
import 'fai_delta_mark.dart';
|
import 'chain_delta_mark.dart';
|
||||||
|
|
||||||
class FaiEmptyState extends StatelessWidget {
|
class ChainEmptyState extends StatelessWidget {
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final String title;
|
final String title;
|
||||||
final String? hint;
|
final String? hint;
|
||||||
|
|
@ -23,7 +23,7 @@ class FaiEmptyState extends StatelessWidget {
|
||||||
/// for connection-failure variants.
|
/// for connection-failure variants.
|
||||||
final Color? iconColor;
|
final Color? iconColor;
|
||||||
|
|
||||||
const FaiEmptyState({
|
const ChainEmptyState({
|
||||||
super.key,
|
super.key,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.title,
|
required this.title,
|
||||||
|
|
@ -40,7 +40,7 @@ class FaiEmptyState extends StatelessWidget {
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 440),
|
constraints: const BoxConstraints(maxWidth: 440),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(FaiSpace.xxl),
|
padding: const EdgeInsets.all(ChainSpace.xxl),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
@ -57,7 +57,7 @@ class FaiEmptyState extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: 0.10,
|
opacity: 0.10,
|
||||||
child: FaiDeltaMark(
|
child: ChainDeltaMark(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
size: 112,
|
size: 112,
|
||||||
),
|
),
|
||||||
|
|
@ -71,14 +71,14 @@ class FaiEmptyState extends StatelessWidget {
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: color.withValues(alpha: 0.18),
|
color: color.withValues(alpha: 0.18),
|
||||||
),
|
),
|
||||||
boxShadow: FaiElevation.low(theme.brightness),
|
boxShadow: ChainElevation.low(theme.brightness),
|
||||||
),
|
),
|
||||||
child: Icon(icon, size: 26, color: color),
|
child: Icon(icon, size: 26, color: color),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.xl),
|
const SizedBox(height: ChainSpace.xl),
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
|
@ -87,7 +87,7 @@ class FaiEmptyState extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (hint != null) ...[
|
if (hint != null) ...[
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
hint!,
|
hint!,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
|
@ -98,7 +98,7 @@ class FaiEmptyState extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (action != null) ...[
|
if (action != null) ...[
|
||||||
const SizedBox(height: FaiSpace.xl),
|
const SizedBox(height: ChainSpace.xl),
|
||||||
action!,
|
action!,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// FaiEnBadge — small "[EN]" pill shown next to text that came
|
// ChainEnBadge — small "[EN]" pill shown next to text that came
|
||||||
// from outside Studio's localization pipeline (MCP server tool
|
// from outside Studio's localization pipeline (MCP server tool
|
||||||
// names, n8n endpoint descriptions, native LLM responses).
|
// names, n8n endpoint descriptions, native LLM responses).
|
||||||
//
|
//
|
||||||
|
|
@ -14,21 +14,21 @@ import 'package:flutter/material.dart';
|
||||||
import '../l10n/app_localizations.dart';
|
import '../l10n/app_localizations.dart';
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
|
|
||||||
class FaiEnBadge extends StatelessWidget {
|
class ChainEnBadge extends StatelessWidget {
|
||||||
/// When true, the badge renders. When false (e.g. the active
|
/// When true, the badge renders. When false (e.g. the active
|
||||||
/// locale already is English), it returns
|
/// locale already is English), it returns
|
||||||
/// `SizedBox.shrink()` so callers can drop it inline without
|
/// `SizedBox.shrink()` so callers can drop it inline without
|
||||||
/// guarding visibility themselves.
|
/// guarding visibility themselves.
|
||||||
final bool visible;
|
final bool visible;
|
||||||
|
|
||||||
const FaiEnBadge({super.key, required this.visible});
|
const ChainEnBadge({super.key, required this.visible});
|
||||||
|
|
||||||
/// Convenience constructor: derives `visible` from the active
|
/// Convenience constructor: derives `visible` from the active
|
||||||
/// Localizations locale. Use this from inside a Build method
|
/// Localizations locale. Use this from inside a Build method
|
||||||
/// where you already have a BuildContext.
|
/// where you already have a BuildContext.
|
||||||
factory FaiEnBadge.forContext(BuildContext context) {
|
factory ChainEnBadge.forContext(BuildContext context) {
|
||||||
final lang = Localizations.localeOf(context).languageCode;
|
final lang = Localizations.localeOf(context).languageCode;
|
||||||
return FaiEnBadge(visible: lang != 'en');
|
return ChainEnBadge(visible: lang != 'en');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -42,7 +42,7 @@ class FaiEnBadge extends StatelessWidget {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHighest,
|
color: theme.colorScheme.surfaceContainerHighest,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// FaiErrorBox — selectable monospace error / output block with
|
// ChainErrorBox — selectable monospace error / output block with
|
||||||
// a small copy-to-clipboard button in the top-right corner.
|
// a small copy-to-clipboard button in the top-right corner.
|
||||||
//
|
//
|
||||||
// Used wherever the operator might want to paste daemon stderr
|
// Used wherever the operator might want to paste daemon stderr
|
||||||
|
|
@ -15,7 +15,7 @@ import '../l10n/app_localizations.dart';
|
||||||
import '../theme/theme.dart';
|
import '../theme/theme.dart';
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
|
|
||||||
class FaiErrorBox extends StatefulWidget {
|
class ChainErrorBox extends StatefulWidget {
|
||||||
/// The text the operator wants to read (and copy). Rendered
|
/// The text the operator wants to read (and copy). Rendered
|
||||||
/// monospace, selectable, multi-line. Ignored when [error] is
|
/// monospace, selectable, multi-line. Ignored when [error] is
|
||||||
/// supplied.
|
/// supplied.
|
||||||
|
|
@ -41,7 +41,7 @@ class FaiErrorBox extends StatefulWidget {
|
||||||
/// trailers otherwise.
|
/// trailers otherwise.
|
||||||
final Object? error;
|
final Object? error;
|
||||||
|
|
||||||
const FaiErrorBox({
|
const ChainErrorBox({
|
||||||
super.key,
|
super.key,
|
||||||
this.text = '',
|
this.text = '',
|
||||||
this.isError = false,
|
this.isError = false,
|
||||||
|
|
@ -49,14 +49,14 @@ class FaiErrorBox extends StatefulWidget {
|
||||||
this.error,
|
this.error,
|
||||||
}) : assert(
|
}) : assert(
|
||||||
text != '' || error != null,
|
text != '' || error != null,
|
||||||
'FaiErrorBox needs either text or error',
|
'ChainErrorBox needs either text or error',
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FaiErrorBox> createState() => _FaiErrorBoxState();
|
State<ChainErrorBox> createState() => _FaiErrorBoxState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FaiErrorBoxState extends State<FaiErrorBox> {
|
class _FaiErrorBoxState extends State<ChainErrorBox> {
|
||||||
bool _justCopied = false;
|
bool _justCopied = false;
|
||||||
bool _detailExpanded = false;
|
bool _detailExpanded = false;
|
||||||
|
|
||||||
|
|
@ -86,14 +86,14 @@ class _FaiErrorBoxState extends State<FaiErrorBox> {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
FaiSpace.sm,
|
ChainSpace.sm,
|
||||||
FaiSpace.xs,
|
ChainSpace.xs,
|
||||||
FaiSpace.xs,
|
ChainSpace.xs,
|
||||||
FaiSpace.sm,
|
ChainSpace.sm,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: accent.withValues(alpha: 0.4)),
|
border: Border.all(color: accent.withValues(alpha: 0.4)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -133,7 +133,7 @@ class _FaiErrorBoxState extends State<FaiErrorBox> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (friendly.detail.isNotEmpty) ...[
|
if (friendly.detail.isNotEmpty) ...[
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () => setState(() {
|
onTap: () => setState(() {
|
||||||
_detailExpanded = !_detailExpanded;
|
_detailExpanded = !_detailExpanded;
|
||||||
|
|
@ -159,7 +159,7 @@ class _FaiErrorBoxState extends State<FaiErrorBox> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_detailExpanded) ...[
|
if (_detailExpanded) ...[
|
||||||
const SizedBox(height: FaiSpace.xs),
|
const SizedBox(height: ChainSpace.xs),
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxHeight: widget.maxHeight ?? double.infinity,
|
maxHeight: widget.maxHeight ?? double.infinity,
|
||||||
|
|
@ -168,7 +168,7 @@ class _FaiErrorBoxState extends State<FaiErrorBox> {
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
friendly.detail,
|
friendly.detail,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -187,7 +187,7 @@ class _FaiErrorBoxState extends State<FaiErrorBox> {
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
widget.text,
|
widget.text,
|
||||||
style: FaiTheme.mono(size: 11, color: fg),
|
style: ChainTheme.mono(size: 11, color: fg),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// FaiFlowOutput — type-aware renderer for one entry in
|
// ChainFlowOutput — type-aware renderer for one entry in
|
||||||
// `runSavedFlow`'s output map. Reads `FlowOutput` and dispatches
|
// `runSavedFlow`'s output map. Reads `FlowOutput` and dispatches
|
||||||
// to the right widget:
|
// to the right widget:
|
||||||
//
|
//
|
||||||
|
|
@ -27,10 +27,10 @@ import '../l10n/app_localizations.dart';
|
||||||
import '../theme/theme.dart';
|
import '../theme/theme.dart';
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
|
|
||||||
class FaiFlowOutput extends StatelessWidget {
|
class ChainFlowOutput extends StatelessWidget {
|
||||||
final FlowOutput output;
|
final FlowOutput output;
|
||||||
|
|
||||||
const FaiFlowOutput({super.key, required this.output});
|
const ChainFlowOutput({super.key, required this.output});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -75,10 +75,10 @@ class _TextView extends StatelessWidget {
|
||||||
if (_looksLikeMarkdown) {
|
if (_looksLikeMarkdown) {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.md),
|
padding: const EdgeInsets.all(ChainSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: MarkdownBody(
|
child: MarkdownBody(
|
||||||
|
|
@ -89,16 +89,16 @@ class _TextView extends StatelessWidget {
|
||||||
await SystemActions.openInOs(href);
|
await SystemActions.openInOs(href);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
styleSheet: FaiTheme.markdownStyle(theme),
|
styleSheet: ChainTheme.markdownStyle(theme),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.md),
|
padding: const EdgeInsets.all(ChainSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
|
|
@ -119,15 +119,15 @@ class _CodeBlock extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.md),
|
padding: const EdgeInsets.all(ChainSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
text,
|
text,
|
||||||
style: FaiTheme.mono(size: 12, color: theme.colorScheme.onSurface),
|
style: ChainTheme.mono(size: 12, color: theme.colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -183,10 +183,10 @@ class _BytesView extends StatelessWidget {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.md),
|
padding: const EdgeInsets.all(ChainSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -196,11 +196,11 @@ class _BytesView extends StatelessWidget {
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxHeight: 240),
|
constraints: const BoxConstraints(maxHeight: 240),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
child: Image.memory(bytes, fit: BoxFit.contain),
|
child: Image.memory(bytes, fit: BoxFit.contain),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
],
|
],
|
||||||
Text(
|
Text(
|
||||||
'${mimeType.isEmpty ? l.flowsOutputBytesUnknownMime : mimeType}'
|
'${mimeType.isEmpty ? l.flowsOutputBytesUnknownMime : mimeType}'
|
||||||
|
|
@ -209,7 +209,7 @@ class _BytesView extends StatelessWidget {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
icon: const Icon(Icons.save_alt, size: 16),
|
icon: const Icon(Icons.save_alt, size: 16),
|
||||||
label: Text(l.flowsOutputSaveAs),
|
label: Text(l.flowsOutputSaveAs),
|
||||||
|
|
@ -247,10 +247,10 @@ class _FileView extends StatelessWidget {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.md),
|
padding: const EdgeInsets.all(ChainSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -258,7 +258,7 @@ class _FileView extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
SelectableText(
|
SelectableText(
|
||||||
uri,
|
uri,
|
||||||
style: FaiTheme.mono(size: 11, color: theme.colorScheme.primary),
|
style: ChainTheme.mono(size: 11, color: theme.colorScheme.primary),
|
||||||
),
|
),
|
||||||
if (mimeType.isNotEmpty) ...[
|
if (mimeType.isNotEmpty) ...[
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
|
|
@ -269,7 +269,7 @@ class _FileView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
icon: const Icon(Icons.open_in_new, size: 16),
|
icon: const Icon(Icons.open_in_new, size: 16),
|
||||||
label: Text(l.flowsOutputOpen),
|
label: Text(l.flowsOutputOpen),
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// FaiLogViewer — modal sheet that renders a log file inline with
|
// ChainLogViewer — modal sheet that renders a log file inline with
|
||||||
// line numbers, basic syntax colouring and a Copy-all button.
|
// line numbers, basic syntax colouring and a Copy-all button.
|
||||||
//
|
//
|
||||||
// Use [showFaiLogViewer] to open it. Two log shapes are supported
|
// Use [showFaiLogViewer] to open it. Two log shapes are supported
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
// anything else renders neutral.
|
// anything else renders neutral.
|
||||||
// - tracing text (the daemon's `~/.chain/run/<channel>.log`):
|
// - tracing text (the daemon's `~/.chain/run/<channel>.log`):
|
||||||
// `[INFO]`, `[WARN]`, `[ERROR]`, `[DEBUG]` substrings get the
|
// `[INFO]`, `[WARN]`, `[ERROR]`, `[DEBUG]` substrings get the
|
||||||
// matching tone from the FaiTheme palette.
|
// matching tone from the ChainTheme palette.
|
||||||
//
|
//
|
||||||
// The viewer reads the *last* N lines (default 500) to keep
|
// The viewer reads the *last* N lines (default 500) to keep
|
||||||
// rendering snappy on huge log files. A Refresh button re-reads
|
// rendering snappy on huge log files. A Refresh button re-reads
|
||||||
|
|
@ -42,7 +42,7 @@ Future<void> showFaiLogViewer(
|
||||||
return showDialog<void>(
|
return showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: true,
|
barrierDismissible: true,
|
||||||
builder: (_) => FaiLogViewer(
|
builder: (_) => ChainLogViewer(
|
||||||
path: path,
|
path: path,
|
||||||
title: title ?? path.split(Platform.pathSeparator).last,
|
title: title ?? path.split(Platform.pathSeparator).last,
|
||||||
tailLines: tailLines,
|
tailLines: tailLines,
|
||||||
|
|
@ -72,7 +72,7 @@ Future<void> showFaiConfigViewer(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class FaiLogViewer extends StatefulWidget {
|
class ChainLogViewer extends StatefulWidget {
|
||||||
final String path;
|
final String path;
|
||||||
final String title;
|
final String title;
|
||||||
final int tailLines;
|
final int tailLines;
|
||||||
|
|
@ -84,7 +84,7 @@ class FaiLogViewer extends StatefulWidget {
|
||||||
/// (for config / plain-text files).
|
/// (for config / plain-text files).
|
||||||
final bool plain;
|
final bool plain;
|
||||||
|
|
||||||
const FaiLogViewer({
|
const ChainLogViewer({
|
||||||
super.key,
|
super.key,
|
||||||
required this.path,
|
required this.path,
|
||||||
required this.title,
|
required this.title,
|
||||||
|
|
@ -94,10 +94,10 @@ class FaiLogViewer extends StatefulWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FaiLogViewer> createState() => _FaiLogViewerState();
|
State<ChainLogViewer> createState() => _FaiLogViewerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FaiLogViewerState extends State<FaiLogViewer> {
|
class _FaiLogViewerState extends State<ChainLogViewer> {
|
||||||
List<String> _lines = const <String>[];
|
List<String> _lines = const <String>[];
|
||||||
bool _loading = true;
|
bool _loading = true;
|
||||||
bool _justCopied = false;
|
bool _justCopied = false;
|
||||||
|
|
@ -141,8 +141,8 @@ class _FaiLogViewerState extends State<FaiLogViewer> {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return Dialog(
|
return Dialog(
|
||||||
insetPadding: const EdgeInsets.symmetric(
|
insetPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.xl,
|
horizontal: ChainSpace.xl,
|
||||||
vertical: FaiSpace.xl,
|
vertical: ChainSpace.xl,
|
||||||
),
|
),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 980, maxHeight: 720),
|
constraints: const BoxConstraints(maxWidth: 980, maxHeight: 720),
|
||||||
|
|
@ -169,7 +169,7 @@ class _FaiLogViewerState extends State<FaiLogViewer> {
|
||||||
: _lines.isEmpty
|
: _lines.isEmpty
|
||||||
? Center(
|
? Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(FaiSpace.xl),
|
padding: const EdgeInsets.all(ChainSpace.xl),
|
||||||
child: Text(
|
child: Text(
|
||||||
l.logViewerEmpty,
|
l.logViewerEmpty,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -218,10 +218,10 @@ class _Header extends StatelessWidget {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
FaiSpace.lg,
|
ChainSpace.lg,
|
||||||
FaiSpace.md,
|
ChainSpace.md,
|
||||||
FaiSpace.sm,
|
ChainSpace.sm,
|
||||||
FaiSpace.md,
|
ChainSpace.md,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -230,7 +230,7 @@ class _Header extends StatelessWidget {
|
||||||
size: 18,
|
size: 18,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -246,7 +246,7 @@ class _Header extends StatelessWidget {
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
'$path · ${l.logViewerLineCount(lineCount)}',
|
'$path · ${l.logViewerLineCount(lineCount)}',
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 10,
|
size: 10,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -296,7 +296,7 @@ class _LogBody extends StatelessWidget {
|
||||||
final gutterWidth = 12.0 + 8.0 * lines.length.toString().length;
|
final gutterWidth = 12.0 + 8.0 * lines.length.toString().length;
|
||||||
return Scrollbar(
|
return Scrollbar(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
padding: const EdgeInsets.symmetric(vertical: FaiSpace.sm),
|
padding: const EdgeInsets.symmetric(vertical: ChainSpace.sm),
|
||||||
itemCount: lines.length,
|
itemCount: lines.length,
|
||||||
itemBuilder: (context, i) => _LogLine(
|
itemBuilder: (context, i) => _LogLine(
|
||||||
lineNumber: i + 1,
|
lineNumber: i + 1,
|
||||||
|
|
@ -328,7 +328,7 @@ class _LogLine extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: FaiSpace.sm, vertical: 1),
|
padding: const EdgeInsets.symmetric(horizontal: ChainSpace.sm, vertical: 1),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -337,7 +337,7 @@ class _LogLine extends StatelessWidget {
|
||||||
child: Text(
|
child: Text(
|
||||||
'$lineNumber',
|
'$lineNumber',
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurfaceVariant.withValues(
|
color: theme.colorScheme.onSurfaceVariant.withValues(
|
||||||
alpha: 0.6,
|
alpha: 0.6,
|
||||||
|
|
@ -345,7 +345,7 @@ class _LogLine extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SelectableText.rich(
|
child: SelectableText.rich(
|
||||||
plain
|
plain
|
||||||
|
|
@ -354,7 +354,7 @@ class _LogLine extends StatelessWidget {
|
||||||
style: TextStyle(color: theme.colorScheme.onSurface),
|
style: TextStyle(color: theme.colorScheme.onSurface),
|
||||||
)
|
)
|
||||||
: _highlightLine(text, theme),
|
: _highlightLine(text, theme),
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
|
|
@ -388,7 +388,7 @@ TextSpan _highlightJsonLine(String line, ThemeData theme) {
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
);
|
);
|
||||||
final stringStyle = TextStyle(color: FaiColors.success);
|
final stringStyle = TextStyle(color: ChainColors.success);
|
||||||
final errorStringStyle = TextStyle(color: theme.colorScheme.error);
|
final errorStringStyle = TextStyle(color: theme.colorScheme.error);
|
||||||
|
|
||||||
final keyRe = RegExp(r'"(ts|level|source|error|context)"\s*:');
|
final keyRe = RegExp(r'"(ts|level|source|error|context)"\s*:');
|
||||||
|
|
@ -440,7 +440,7 @@ TextSpan _highlightTraceLine(String line, ThemeData theme) {
|
||||||
break;
|
break;
|
||||||
case 'WARN':
|
case 'WARN':
|
||||||
case 'WARNING':
|
case 'WARNING':
|
||||||
tone = FaiColors.warning;
|
tone = ChainColors.warning;
|
||||||
break;
|
break;
|
||||||
case 'INFO':
|
case 'INFO':
|
||||||
tone = theme.colorScheme.primary;
|
tone = theme.colorScheme.primary;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// FaiModuleSheet — modal bottom-sheet showing detailed module
|
// ChainModuleSheet — modal bottom-sheet showing detailed module
|
||||||
// info: full capability list, declared permissions, on-disk
|
// info: full capability list, declared permissions, on-disk
|
||||||
// directory. Opened by tapping a module card on the Modules
|
// directory. Opened by tapping a module card on the Modules
|
||||||
// page.
|
// page.
|
||||||
|
|
@ -11,12 +11,12 @@ import '../l10n/app_localizations.dart';
|
||||||
import '../pages/welcome.dart' show showFaiDoc;
|
import '../pages/welcome.dart' show showFaiDoc;
|
||||||
import '../theme/theme.dart';
|
import '../theme/theme.dart';
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
import 'fai_pill.dart';
|
import 'chain_pill.dart';
|
||||||
|
|
||||||
class FaiModuleSheet extends StatefulWidget {
|
class ChainModuleSheet extends StatefulWidget {
|
||||||
final String moduleName;
|
final String moduleName;
|
||||||
|
|
||||||
const FaiModuleSheet({super.key, required this.moduleName});
|
const ChainModuleSheet({super.key, required this.moduleName});
|
||||||
|
|
||||||
/// Convenience launcher used from the Modules list. Resolves
|
/// Convenience launcher used from the Modules list. Resolves
|
||||||
/// to `true` when the operator uninstalled the module — the
|
/// to `true` when the operator uninstalled the module — the
|
||||||
|
|
@ -28,18 +28,18 @@ class FaiModuleSheet extends StatefulWidget {
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(FaiRadius.md)),
|
borderRadius: BorderRadius.vertical(top: Radius.circular(ChainRadius.md)),
|
||||||
),
|
),
|
||||||
builder: (_) => FaiModuleSheet(moduleName: name),
|
builder: (_) => ChainModuleSheet(moduleName: name),
|
||||||
);
|
);
|
||||||
return r ?? false;
|
return r ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FaiModuleSheet> createState() => _FaiModuleSheetState();
|
State<ChainModuleSheet> createState() => _FaiModuleSheetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FaiModuleSheetState extends State<FaiModuleSheet> {
|
class _FaiModuleSheetState extends State<ChainModuleSheet> {
|
||||||
late final Future<ModuleDetail> _future;
|
late final Future<ModuleDetail> _future;
|
||||||
bool _uninstalling = false;
|
bool _uninstalling = false;
|
||||||
|
|
||||||
|
|
@ -127,12 +127,12 @@ class _FaiModuleSheetState extends State<FaiModuleSheet> {
|
||||||
_Handle(),
|
_Handle(),
|
||||||
if (snapshot.connectionState == ConnectionState.waiting)
|
if (snapshot.connectionState == ConnectionState.waiting)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.all(FaiSpace.xxxl),
|
padding: EdgeInsets.all(ChainSpace.xxxl),
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
)
|
)
|
||||||
else if (snapshot.hasError)
|
else if (snapshot.hasError)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(FaiSpace.xl),
|
padding: const EdgeInsets.all(ChainSpace.xl),
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(
|
AppLocalizations.of(
|
||||||
context,
|
context,
|
||||||
|
|
@ -163,7 +163,7 @@ class _Handle extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: FaiSpace.sm),
|
padding: const EdgeInsets.symmetric(vertical: ChainSpace.sm),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 4,
|
height: 4,
|
||||||
|
|
@ -193,10 +193,10 @@ class _Body extends StatelessWidget {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
FaiSpace.xxl,
|
ChainSpace.xxl,
|
||||||
FaiSpace.md,
|
ChainSpace.md,
|
||||||
FaiSpace.xxl,
|
ChainSpace.xxl,
|
||||||
FaiSpace.xxl,
|
ChainSpace.xxl,
|
||||||
),
|
),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -208,39 +208,39 @@ class _Body extends StatelessWidget {
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: 'v${detail.version}',
|
label: 'v${detail.version}',
|
||||||
tone: FaiPillTone.accent,
|
tone: ChainPillTone.accent,
|
||||||
monospace: true,
|
monospace: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.xs),
|
const SizedBox(height: ChainSpace.xs),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
detail.directory,
|
detail.directory,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.xl),
|
const SizedBox(height: ChainSpace.xl),
|
||||||
_SectionHeader(l.moduleSheetCapabilities),
|
_SectionHeader(l.moduleSheetCapabilities),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: FaiSpace.xs,
|
spacing: ChainSpace.xs,
|
||||||
runSpacing: FaiSpace.xs,
|
runSpacing: ChainSpace.xs,
|
||||||
children: detail.capabilities
|
children: detail.capabilities
|
||||||
.map(
|
.map(
|
||||||
(c) => FaiPill(
|
(c) => ChainPill(
|
||||||
label: c,
|
label: c,
|
||||||
tone: FaiPillTone.accent,
|
tone: ChainPillTone.accent,
|
||||||
monospace: true,
|
monospace: true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.xl),
|
const SizedBox(height: ChainSpace.xl),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
_SectionHeader(l.moduleSheetPermissions),
|
_SectionHeader(l.moduleSheetPermissions),
|
||||||
|
|
@ -259,7 +259,7 @@ class _Body extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
if (detail.permissions.isEmpty)
|
if (detail.permissions.isEmpty)
|
||||||
Text(
|
Text(
|
||||||
l.moduleSheetNoPermissions,
|
l.moduleSheetNoPermissions,
|
||||||
|
|
@ -282,10 +282,10 @@ class _Body extends StatelessWidget {
|
||||||
size: 14,
|
size: 14,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
p,
|
p,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 12,
|
size: 12,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
|
|
@ -296,7 +296,7 @@ class _Body extends StatelessWidget {
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.xxl),
|
const SizedBox(height: ChainSpace.xxl),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
|
@ -378,7 +378,7 @@ class _UninstallVersionPickerDialogState
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(l.uninstallVersionPickerBody(widget.moduleName)),
|
Text(l.uninstallVersionPickerBody(widget.moduleName)),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
RadioGroup<String>(
|
RadioGroup<String>(
|
||||||
groupValue: _picked,
|
groupValue: _picked,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// FaiPill — small inline label. Used for capabilities, versions,
|
// ChainPill — small inline label. Used for capabilities, versions,
|
||||||
// permission scopes, status text. Replaces ad-hoc Container chips.
|
// permission scopes, status text. Replaces ad-hoc Container chips.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -6,18 +6,18 @@ import 'package:flutter/material.dart';
|
||||||
import '../theme/theme.dart';
|
import '../theme/theme.dart';
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
|
|
||||||
enum FaiPillTone { neutral, accent, success, warning, danger }
|
enum ChainPillTone { neutral, accent, success, warning, danger }
|
||||||
|
|
||||||
class FaiPill extends StatelessWidget {
|
class ChainPill extends StatelessWidget {
|
||||||
final String label;
|
final String label;
|
||||||
final FaiPillTone tone;
|
final ChainPillTone tone;
|
||||||
final IconData? icon;
|
final IconData? icon;
|
||||||
final bool monospace;
|
final bool monospace;
|
||||||
|
|
||||||
const FaiPill({
|
const ChainPill({
|
||||||
super.key,
|
super.key,
|
||||||
required this.label,
|
required this.label,
|
||||||
this.tone = FaiPillTone.neutral,
|
this.tone = ChainPillTone.neutral,
|
||||||
this.icon,
|
this.icon,
|
||||||
this.monospace = false,
|
this.monospace = false,
|
||||||
});
|
});
|
||||||
|
|
@ -27,13 +27,13 @@ class FaiPill extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final (bg, fg) = _colors(theme.colorScheme);
|
final (bg, fg) = _colors(theme.colorScheme);
|
||||||
final textStyle = monospace
|
final textStyle = monospace
|
||||||
? FaiTheme.mono(size: 11, weight: FontWeight.w500, color: fg)
|
? ChainTheme.mono(size: 11, weight: FontWeight.w500, color: fg)
|
||||||
: theme.textTheme.labelSmall?.copyWith(color: fg);
|
: theme.textTheme.labelSmall?.copyWith(color: fg);
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: FaiSpace.sm, vertical: 3),
|
padding: const EdgeInsets.symmetric(horizontal: ChainSpace.sm, vertical: 3),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: bg,
|
color: bg,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
@ -50,25 +50,25 @@ class FaiPill extends StatelessWidget {
|
||||||
|
|
||||||
(Color, Color) _colors(ColorScheme scheme) {
|
(Color, Color) _colors(ColorScheme scheme) {
|
||||||
switch (tone) {
|
switch (tone) {
|
||||||
case FaiPillTone.neutral:
|
case ChainPillTone.neutral:
|
||||||
return (scheme.surfaceContainerHighest, scheme.onSurfaceVariant);
|
return (scheme.surfaceContainerHighest, scheme.onSurfaceVariant);
|
||||||
case FaiPillTone.accent:
|
case ChainPillTone.accent:
|
||||||
return (scheme.primary.withValues(alpha: 0.15), scheme.primary);
|
return (scheme.primary.withValues(alpha: 0.15), scheme.primary);
|
||||||
case FaiPillTone.success:
|
case ChainPillTone.success:
|
||||||
return (
|
return (
|
||||||
FaiColors.success.withValues(alpha: 0.15),
|
ChainColors.success.withValues(alpha: 0.15),
|
||||||
scheme.brightness == Brightness.dark
|
scheme.brightness == Brightness.dark
|
||||||
? FaiColors.success
|
? ChainColors.success
|
||||||
: const Color(0xFF15803D),
|
: const Color(0xFF15803D),
|
||||||
);
|
);
|
||||||
case FaiPillTone.warning:
|
case ChainPillTone.warning:
|
||||||
return (
|
return (
|
||||||
FaiColors.warning.withValues(alpha: 0.15),
|
ChainColors.warning.withValues(alpha: 0.15),
|
||||||
scheme.brightness == Brightness.dark
|
scheme.brightness == Brightness.dark
|
||||||
? FaiColors.warning
|
? ChainColors.warning
|
||||||
: const Color(0xFFB45309),
|
: const Color(0xFFB45309),
|
||||||
);
|
);
|
||||||
case FaiPillTone.danger:
|
case ChainPillTone.danger:
|
||||||
return (scheme.errorContainer, scheme.onErrorContainer);
|
return (scheme.errorContainer, scheme.onErrorContainer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// FaiSearchPalette — global Cmd+K command bar.
|
// ChainSearchPalette — global Cmd+K command bar.
|
||||||
//
|
//
|
||||||
// Indexes: nav destinations, installed modules, store entries,
|
// Indexes: nav destinations, installed modules, store entries,
|
||||||
// saved flows. Filters client-side as the operator types.
|
// saved flows. Filters client-side as the operator types.
|
||||||
|
|
@ -12,12 +12,12 @@ import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import '../data/hub.dart';
|
import '../data/hub.dart';
|
||||||
import '../l10n/app_localizations.dart';
|
import '../l10n/app_localizations.dart';
|
||||||
import 'fai_module_sheet.dart';
|
import 'chain_module_sheet.dart';
|
||||||
|
|
||||||
/// One row in the palette. Carries everything needed to render
|
/// One row in the palette. Carries everything needed to render
|
||||||
/// + dispatch — keep it serializable-shaped so we can persist
|
/// + dispatch — keep it serializable-shaped so we can persist
|
||||||
/// recent picks later.
|
/// recent picks later.
|
||||||
class FaiSearchHit {
|
class ChainSearchHit {
|
||||||
final String label;
|
final String label;
|
||||||
final String hint;
|
final String hint;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
|
|
@ -33,7 +33,7 @@ class FaiSearchHit {
|
||||||
/// Action to run when this hit is selected.
|
/// Action to run when this hit is selected.
|
||||||
final VoidCallback onSelect;
|
final VoidCallback onSelect;
|
||||||
|
|
||||||
FaiSearchHit({
|
ChainSearchHit({
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.hint,
|
required this.hint,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
|
|
@ -42,33 +42,33 @@ class FaiSearchHit {
|
||||||
}) : haystack = '$label $hint $group'.toLowerCase();
|
}) : haystack = '$label $hint $group'.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
class FaiSearchPalette extends StatefulWidget {
|
class ChainSearchPalette extends StatefulWidget {
|
||||||
/// Pages the operator can navigate to. Each entry maps to a
|
/// Pages the operator can navigate to. Each entry maps to a
|
||||||
/// callback that selects the right tab back in the shell.
|
/// callback that selects the right tab back in the shell.
|
||||||
final List<FaiSearchHit> staticHits;
|
final List<ChainSearchHit> staticHits;
|
||||||
|
|
||||||
const FaiSearchPalette({super.key, required this.staticHits});
|
const ChainSearchPalette({super.key, required this.staticHits});
|
||||||
|
|
||||||
/// Convenience launcher — used from the Cmd+K shortcut.
|
/// Convenience launcher — used from the Cmd+K shortcut.
|
||||||
static Future<void> show(
|
static Future<void> show(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required List<FaiSearchHit> staticHits,
|
required List<ChainSearchHit> staticHits,
|
||||||
}) {
|
}) {
|
||||||
return showDialog<void>(
|
return showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
barrierColor: Colors.black54,
|
barrierColor: Colors.black54,
|
||||||
builder: (_) => FaiSearchPalette(staticHits: staticHits),
|
builder: (_) => ChainSearchPalette(staticHits: staticHits),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FaiSearchPalette> createState() => _FaiSearchPaletteState();
|
State<ChainSearchPalette> createState() => _FaiSearchPaletteState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
class _FaiSearchPaletteState extends State<ChainSearchPalette> {
|
||||||
final _query = TextEditingController();
|
final _query = TextEditingController();
|
||||||
final _focus = FocusNode();
|
final _focus = FocusNode();
|
||||||
Future<List<FaiSearchHit>>? _dynamicFuture;
|
Future<List<ChainSearchHit>>? _dynamicFuture;
|
||||||
int _highlight = 0;
|
int _highlight = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -92,7 +92,7 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
/// Pull modules / store / flows in parallel so the palette
|
/// Pull modules / store / flows in parallel so the palette
|
||||||
/// is searchable as soon as the operator finishes typing the
|
/// is searchable as soon as the operator finishes typing the
|
||||||
/// first character.
|
/// first character.
|
||||||
Future<List<FaiSearchHit>> _loadDynamic() async {
|
Future<List<ChainSearchHit>> _loadDynamic() async {
|
||||||
final svc = HubService.instance;
|
final svc = HubService.instance;
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
final results = await Future.wait([
|
final results = await Future.wait([
|
||||||
|
|
@ -104,17 +104,17 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
final store = results[1] as List<StoreItem>;
|
final store = results[1] as List<StoreItem>;
|
||||||
final flows = results[2] as List<SavedFlow>;
|
final flows = results[2] as List<SavedFlow>;
|
||||||
|
|
||||||
final hits = <FaiSearchHit>[];
|
final hits = <ChainSearchHit>[];
|
||||||
for (final m in modules) {
|
for (final m in modules) {
|
||||||
hits.add(
|
hits.add(
|
||||||
FaiSearchHit(
|
ChainSearchHit(
|
||||||
label: m.name,
|
label: m.name,
|
||||||
hint: l.searchInstalledModuleHint(m.version),
|
hint: l.searchInstalledModuleHint(m.version),
|
||||||
icon: Icons.extension,
|
icon: Icons.extension,
|
||||||
group: l.searchGroupModules,
|
group: l.searchGroupModules,
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
FaiModuleSheet.show(context, m.name);
|
ChainModuleSheet.show(context, m.name);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -123,7 +123,7 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
final pagesGroup = l.searchGroupPages;
|
final pagesGroup = l.searchGroupPages;
|
||||||
for (final s in store) {
|
for (final s in store) {
|
||||||
hits.add(
|
hits.add(
|
||||||
FaiSearchHit(
|
ChainSearchHit(
|
||||||
label: s.name,
|
label: s.name,
|
||||||
hint: s.taglineEn.isEmpty
|
hint: s.taglineEn.isEmpty
|
||||||
? l.searchStoreHintWithCategory(
|
? l.searchStoreHintWithCategory(
|
||||||
|
|
@ -151,7 +151,7 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
final flowsGroup = l.searchGroupFlows;
|
final flowsGroup = l.searchGroupFlows;
|
||||||
for (final f in flows) {
|
for (final f in flows) {
|
||||||
hits.add(
|
hits.add(
|
||||||
FaiSearchHit(
|
ChainSearchHit(
|
||||||
label: f.name,
|
label: f.name,
|
||||||
hint: l.searchSavedFlowHint,
|
hint: l.searchSavedFlowHint,
|
||||||
icon: Icons.account_tree_outlined,
|
icon: Icons.account_tree_outlined,
|
||||||
|
|
@ -174,11 +174,11 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
/// Filtered + grouped result list. Empty query returns the
|
/// Filtered + grouped result list. Empty query returns the
|
||||||
/// pages first then everything else, capped to 50 rows so the
|
/// pages first then everything else, capped to 50 rows so the
|
||||||
/// dialog stays scrollable.
|
/// dialog stays scrollable.
|
||||||
List<FaiSearchHit> _resultsFor(List<FaiSearchHit> dynamic_, String raw) {
|
List<ChainSearchHit> _resultsFor(List<ChainSearchHit> dynamic_, String raw) {
|
||||||
final query = raw.trim().toLowerCase();
|
final query = raw.trim().toLowerCase();
|
||||||
final all = [...widget.staticHits, ...dynamic_];
|
final all = [...widget.staticHits, ...dynamic_];
|
||||||
if (query.isEmpty) return all.take(50).toList();
|
if (query.isEmpty) return all.take(50).toList();
|
||||||
final scored = <(int, FaiSearchHit)>[];
|
final scored = <(int, ChainSearchHit)>[];
|
||||||
for (final h in all) {
|
for (final h in all) {
|
||||||
final i = h.haystack.indexOf(query);
|
final i = h.haystack.indexOf(query);
|
||||||
if (i < 0) continue;
|
if (i < 0) continue;
|
||||||
|
|
@ -224,10 +224,10 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 640, maxHeight: 540),
|
constraints: const BoxConstraints(maxWidth: 640, maxHeight: 540),
|
||||||
child: FutureBuilder<List<FaiSearchHit>>(
|
child: FutureBuilder<List<ChainSearchHit>>(
|
||||||
future: _dynamicFuture,
|
future: _dynamicFuture,
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
final dyn = snap.data ?? const <FaiSearchHit>[];
|
final dyn = snap.data ?? const <ChainSearchHit>[];
|
||||||
return ValueListenableBuilder<TextEditingValue>(
|
return ValueListenableBuilder<TextEditingValue>(
|
||||||
valueListenable: _query,
|
valueListenable: _query,
|
||||||
builder: (_, value, _) {
|
builder: (_, value, _) {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// FaiSettingsDialog — modal for changing the hub endpoint.
|
// ChainSettingsDialog — modal for changing the hub endpoint.
|
||||||
// Reads current values from HubService, persists on save via
|
// Reads current values from HubService, persists on save via
|
||||||
// HubService.reconnect.
|
// HubService.reconnect.
|
||||||
|
|
||||||
|
|
@ -14,25 +14,25 @@ import '../l10n/app_localizations.dart';
|
||||||
import '../pages/welcome.dart' show showFaiDoc;
|
import '../pages/welcome.dart' show showFaiDoc;
|
||||||
import '../theme/theme.dart';
|
import '../theme/theme.dart';
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
import 'fai_error_box.dart';
|
import 'chain_error_box.dart';
|
||||||
import 'fai_pill.dart';
|
import 'chain_pill.dart';
|
||||||
import 'fai_system_ai_editor.dart';
|
import 'chain_system_ai_editor.dart';
|
||||||
import 'theme_picker_grid.dart';
|
import 'theme_picker_grid.dart';
|
||||||
|
|
||||||
class FaiSettingsDialog extends StatefulWidget {
|
class ChainSettingsDialog extends StatefulWidget {
|
||||||
const FaiSettingsDialog({super.key});
|
const ChainSettingsDialog({super.key});
|
||||||
|
|
||||||
/// Convenience launcher used from the sidebar gear icon.
|
/// Convenience launcher used from the sidebar gear icon.
|
||||||
static Future<bool> show(BuildContext context) async {
|
static Future<bool> show(BuildContext context) async {
|
||||||
final ok = await showDialog<bool>(
|
final ok = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => const FaiSettingsDialog(),
|
builder: (_) => const ChainSettingsDialog(),
|
||||||
);
|
);
|
||||||
return ok ?? false;
|
return ok ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FaiSettingsDialog> createState() => _FaiSettingsDialogState();
|
State<ChainSettingsDialog> createState() => _FaiSettingsDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sidebar categories — every Settings panel belongs to exactly
|
/// Sidebar categories — every Settings panel belongs to exactly
|
||||||
|
|
@ -40,7 +40,7 @@ class FaiSettingsDialog extends StatefulWidget {
|
||||||
/// first-opened category.
|
/// first-opened category.
|
||||||
enum _Category { general, appearance, ai, integrations, security, maintenance }
|
enum _Category { general, appearance, ai, integrations, security, maintenance }
|
||||||
|
|
||||||
class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
class _FaiSettingsDialogState extends State<ChainSettingsDialog> {
|
||||||
late final TextEditingController _host;
|
late final TextEditingController _host;
|
||||||
late final TextEditingController _port;
|
late final TextEditingController _port;
|
||||||
bool _secure = false;
|
bool _secure = false;
|
||||||
|
|
@ -291,7 +291,7 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
_saving = true;
|
_saving = true;
|
||||||
_channelToast = null;
|
_channelToast = null;
|
||||||
});
|
});
|
||||||
final r = await SystemActions.faiChannelSwitch(name);
|
final r = await SystemActions.chainChannelSwitch(name);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -384,10 +384,10 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
};
|
};
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
FaiSpace.xl,
|
ChainSpace.xl,
|
||||||
FaiSpace.xl,
|
ChainSpace.xl,
|
||||||
FaiSpace.xl,
|
ChainSpace.xl,
|
||||||
FaiSpace.lg,
|
ChainSpace.lg,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -403,7 +403,7 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
String? docSlug,
|
String? docSlug,
|
||||||
}) {
|
}) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: FaiSpace.lg),
|
padding: const EdgeInsets.only(bottom: ChainSpace.lg),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -455,7 +455,7 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
),
|
),
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
@ -470,7 +470,7 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: ChainSpace.md),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child: SwitchListTile(
|
child: SwitchListTile(
|
||||||
|
|
@ -490,7 +490,7 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (_error != null) ...[
|
if (_error != null) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Text(
|
Text(
|
||||||
_error!,
|
_error!,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -498,12 +498,12 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(FaiSpace.sm),
|
padding: const EdgeInsets.all(ChainSpace.sm),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -512,10 +512,10 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
size: 14,
|
size: 14,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
_previewUrl(),
|
_previewUrl(),
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
|
|
@ -524,7 +524,7 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_channels != null) ...[
|
if (_channels != null) ...[
|
||||||
const SizedBox(height: FaiSpace.xl),
|
const SizedBox(height: ChainSpace.xl),
|
||||||
Text(
|
Text(
|
||||||
l.channelsHeader,
|
l.channelsHeader,
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
|
@ -540,7 +540,7 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
for (final ch in _channels!.channels)
|
for (final ch in _channels!.channels)
|
||||||
_ChannelRow(
|
_ChannelRow(
|
||||||
channel: ch,
|
channel: ch,
|
||||||
|
|
@ -551,21 +551,21 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
? null
|
? null
|
||||||
: () => _runDaemon(
|
: () => _runDaemon(
|
||||||
l.daemonActionEnableAutostart,
|
l.daemonActionEnableAutostart,
|
||||||
() => SystemActions.faiDaemonEnable(ch.name),
|
() => SystemActions.chainDaemonEnable(ch.name),
|
||||||
),
|
),
|
||||||
onDisableAutostart: _saving
|
onDisableAutostart: _saving
|
||||||
? null
|
? null
|
||||||
: () => _runDaemon(
|
: () => _runDaemon(
|
||||||
l.daemonActionDisableAutostart,
|
l.daemonActionDisableAutostart,
|
||||||
() => SystemActions.faiDaemonDisable(ch.name),
|
() => SystemActions.chainDaemonDisable(ch.name),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_channelToast != null) ...[
|
if (_channelToast != null) ...[
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
FaiErrorBox(text: _channelToast!, maxHeight: 200),
|
ChainErrorBox(text: _channelToast!, maxHeight: 200),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
const SizedBox(height: FaiSpace.xl),
|
const SizedBox(height: ChainSpace.xl),
|
||||||
const _DefaultScopePanel(),
|
const _DefaultScopePanel(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -594,7 +594,7 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
_SystemAiPanel(
|
_SystemAiPanel(
|
||||||
status: _aiStatus!,
|
status: _aiStatus!,
|
||||||
onEdit: () async {
|
onEdit: () async {
|
||||||
final updated = await FaiSystemAiEditor.show(context, _aiStatus!);
|
final updated = await ChainSystemAiEditor.show(context, _aiStatus!);
|
||||||
if (updated != null && mounted) {
|
if (updated != null && mounted) {
|
||||||
setState(() => _aiStatus = updated);
|
setState(() => _aiStatus = updated);
|
||||||
}
|
}
|
||||||
|
|
@ -612,7 +612,7 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
),
|
),
|
||||||
if (_mcpClients == null)
|
if (_mcpClients == null)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: FaiSpace.md),
|
padding: EdgeInsets.symmetric(vertical: ChainSpace.md),
|
||||||
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
|
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
|
@ -622,7 +622,7 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
onRemove: _removeMcpClient,
|
onRemove: _removeMcpClient,
|
||||||
onRefresh: _refreshMcpClients,
|
onRefresh: _refreshMcpClients,
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.xl),
|
const SizedBox(height: ChainSpace.xl),
|
||||||
if (_n8nEndpoints != null)
|
if (_n8nEndpoints != null)
|
||||||
_N8nEndpointsPanel(
|
_N8nEndpointsPanel(
|
||||||
endpoints: _n8nEndpoints!,
|
endpoints: _n8nEndpoints!,
|
||||||
|
|
@ -646,7 +646,7 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
onSave: _saveRegistryToken,
|
onSave: _saveRegistryToken,
|
||||||
onClear: _clearRegistryToken,
|
onClear: _clearRegistryToken,
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.xl),
|
const SizedBox(height: ChainSpace.xl),
|
||||||
_HubAuthTokenPanel(
|
_HubAuthTokenPanel(
|
||||||
configuredChars: _hubAuthTokenChars,
|
configuredChars: _hubAuthTokenChars,
|
||||||
onSave: _saveHubAuthToken,
|
onSave: _saveHubAuthToken,
|
||||||
|
|
@ -686,7 +686,7 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(l.settingsTitle),
|
title: Text(l.settingsTitle),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
),
|
),
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
content: ConstrainedBox(
|
content: ConstrainedBox(
|
||||||
|
|
@ -770,15 +770,15 @@ class _ChannelRow extends StatelessWidget {
|
||||||
channel.running ? Icons.circle : Icons.circle_outlined,
|
channel.running ? Icons.circle : Icons.circle_outlined,
|
||||||
size: 10,
|
size: 10,
|
||||||
color: channel.running
|
color: channel.running
|
||||||
? FaiColors.success
|
? ChainColors.success
|
||||||
: theme.colorScheme.outline,
|
: theme.colorScheme.outline,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 88,
|
width: 88,
|
||||||
child: Text(
|
child: Text(
|
||||||
channel.name,
|
channel.name,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
weight: active ? FontWeight.w600 : FontWeight.w400,
|
weight: active ? FontWeight.w600 : FontWeight.w400,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
|
|
@ -789,7 +789,7 @@ class _ChannelRow extends StatelessWidget {
|
||||||
width: 64,
|
width: 64,
|
||||||
child: Text(
|
child: Text(
|
||||||
':${channel.port}',
|
':${channel.port}',
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 11,
|
size: 11,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -805,10 +805,10 @@ class _ChannelRow extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (active)
|
if (active)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: FaiSpace.sm),
|
padding: const EdgeInsets.only(right: ChainSpace.sm),
|
||||||
child: FaiPill(
|
child: ChainPill(
|
||||||
label: l.channelsActive,
|
label: l.channelsActive,
|
||||||
tone: FaiPillTone.success,
|
tone: ChainPillTone.success,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
|
|
@ -876,7 +876,7 @@ class _MenuRow extends StatelessWidget {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 14),
|
Icon(icon, size: 14),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Text(text),
|
Text(text),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -906,18 +906,18 @@ class _SystemAiPanel extends StatelessWidget {
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: status.enabled ? l.systemAiEnabled : l.systemAiOff,
|
label: status.enabled ? l.systemAiEnabled : l.systemAiOff,
|
||||||
tone: status.enabled ? FaiPillTone.success : FaiPillTone.neutral,
|
tone: status.enabled ? ChainPillTone.success : ChainPillTone.neutral,
|
||||||
),
|
),
|
||||||
if (status.enabled) ...[
|
if (status.enabled) ...[
|
||||||
const SizedBox(width: FaiSpace.xs),
|
const SizedBox(width: ChainSpace.xs),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: status.privacyMode,
|
label: status.privacyMode,
|
||||||
tone: status.privacyMode == 'full'
|
tone: status.privacyMode == 'full'
|
||||||
? FaiPillTone.warning
|
? ChainPillTone.warning
|
||||||
: FaiPillTone.neutral,
|
: ChainPillTone.neutral,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
|
@ -985,7 +985,7 @@ class _StatRow extends StatelessWidget {
|
||||||
child: Text(
|
child: Text(
|
||||||
value.isEmpty ? '—' : value,
|
value.isEmpty ? '—' : value,
|
||||||
style: mono
|
style: mono
|
||||||
? FaiTheme.mono(size: 11, color: theme.colorScheme.onSurface)
|
? ChainTheme.mono(size: 11, color: theme.colorScheme.onSurface)
|
||||||
: theme.textTheme.bodySmall,
|
: theme.textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1045,10 +1045,10 @@ class _McpClientsPanel extends StatelessWidget {
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: l.mcpServersCount(clients.length),
|
label: l.mcpServersCount(clients.length),
|
||||||
tone: FaiPillTone.neutral,
|
tone: ChainPillTone.neutral,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|
@ -1075,10 +1075,10 @@ class _McpClientsPanel extends StatelessWidget {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
if (clients.isEmpty)
|
if (clients.isEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: FaiSpace.sm),
|
padding: const EdgeInsets.symmetric(vertical: ChainSpace.sm),
|
||||||
child: Text(
|
child: Text(
|
||||||
l.mcpEmpty,
|
l.mcpEmpty,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -1105,7 +1105,7 @@ class _McpClientRow extends StatelessWidget {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
final ok = client.healthy;
|
final ok = client.healthy;
|
||||||
final dotColor = client.errorKind.isEmpty
|
final dotColor = client.errorKind.isEmpty
|
||||||
? (ok ? FaiColors.success : FaiColors.muted)
|
? (ok ? ChainColors.success : ChainColors.muted)
|
||||||
: theme.colorScheme.error;
|
: theme.colorScheme.error;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
|
@ -1116,7 +1116,7 @@ class _McpClientRow extends StatelessWidget {
|
||||||
padding: const EdgeInsets.only(top: 6),
|
padding: const EdgeInsets.only(top: 6),
|
||||||
child: Icon(Icons.circle, size: 8, color: dotColor),
|
child: Icon(Icons.circle, size: 8, color: dotColor),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -1125,28 +1125,28 @@ class _McpClientRow extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
client.name,
|
client.name,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 12,
|
size: 12,
|
||||||
weight: FontWeight.w600,
|
weight: FontWeight.w600,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
if (client.toolCount > 0)
|
if (client.toolCount > 0)
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: l.mcpToolsCount(client.toolCount),
|
label: l.mcpToolsCount(client.toolCount),
|
||||||
tone: FaiPillTone.success,
|
tone: ChainPillTone.success,
|
||||||
)
|
)
|
||||||
else if (client.errorKind.isNotEmpty)
|
else if (client.errorKind.isNotEmpty)
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: client.errorKind,
|
label: client.errorKind,
|
||||||
tone: FaiPillTone.danger,
|
tone: ChainPillTone.danger,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
client.endpoint,
|
client.endpoint,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 10,
|
size: 10,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -1234,7 +1234,7 @@ class _AddMcpClientDialogState extends State<_AddMcpClientDialog> {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
// Curated suggestions from the official Anthropic
|
// Curated suggestions from the official Anthropic
|
||||||
// MCP servers + a few community staples. Click a
|
// MCP servers + a few community staples. Click a
|
||||||
// chip to pre-fill the form. The operator still
|
// chip to pre-fill the form. The operator still
|
||||||
|
|
@ -1249,8 +1249,8 @@ class _AddMcpClientDialogState extends State<_AddMcpClientDialog> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: FaiSpace.xs,
|
spacing: ChainSpace.xs,
|
||||||
runSpacing: FaiSpace.xs,
|
runSpacing: ChainSpace.xs,
|
||||||
children: [
|
children: [
|
||||||
for (final s in _kMcpSuggestions)
|
for (final s in _kMcpSuggestions)
|
||||||
ActionChip(
|
ActionChip(
|
||||||
|
|
@ -1260,7 +1260,7 @@ class _AddMcpClientDialogState extends State<_AddMcpClientDialog> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Divider(height: FaiSpace.xl),
|
const Divider(height: ChainSpace.xl),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _name,
|
controller: _name,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
|
|
@ -1271,7 +1271,7 @@ class _AddMcpClientDialogState extends State<_AddMcpClientDialog> {
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _endpoint,
|
controller: _endpoint,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|
@ -1281,7 +1281,7 @@ class _AddMcpClientDialogState extends State<_AddMcpClientDialog> {
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _apiKey,
|
controller: _apiKey,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|
@ -1292,7 +1292,7 @@ class _AddMcpClientDialogState extends State<_AddMcpClientDialog> {
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _desc,
|
controller: _desc,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|
@ -1377,10 +1377,10 @@ class _N8nEndpointsPanel extends StatelessWidget {
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: l.n8nEndpointsCount(endpoints.length),
|
label: l.n8nEndpointsCount(endpoints.length),
|
||||||
tone: FaiPillTone.neutral,
|
tone: ChainPillTone.neutral,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|
@ -1407,10 +1407,10 @@ class _N8nEndpointsPanel extends StatelessWidget {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
if (endpoints.isEmpty)
|
if (endpoints.isEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: FaiSpace.sm),
|
padding: const EdgeInsets.symmetric(vertical: ChainSpace.sm),
|
||||||
child: Text(
|
child: Text(
|
||||||
l.n8nEmpty,
|
l.n8nEmpty,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -1437,7 +1437,7 @@ class _N8nEndpointRow extends StatelessWidget {
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
final ok = endpoint.healthy;
|
final ok = endpoint.healthy;
|
||||||
final dotColor = endpoint.errorKind.isEmpty
|
final dotColor = endpoint.errorKind.isEmpty
|
||||||
? (ok ? FaiColors.success : FaiColors.muted)
|
? (ok ? ChainColors.success : ChainColors.muted)
|
||||||
: theme.colorScheme.error;
|
: theme.colorScheme.error;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
|
@ -1448,7 +1448,7 @@ class _N8nEndpointRow extends StatelessWidget {
|
||||||
padding: const EdgeInsets.only(top: 6),
|
padding: const EdgeInsets.only(top: 6),
|
||||||
child: Icon(Icons.circle, size: 8, color: dotColor),
|
child: Icon(Icons.circle, size: 8, color: dotColor),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -1457,28 +1457,28 @@ class _N8nEndpointRow extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
endpoint.name,
|
endpoint.name,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 12,
|
size: 12,
|
||||||
weight: FontWeight.w600,
|
weight: FontWeight.w600,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
if (endpoint.workflowCount > 0)
|
if (endpoint.workflowCount > 0)
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: l.n8nWorkflowsCount(endpoint.workflowCount),
|
label: l.n8nWorkflowsCount(endpoint.workflowCount),
|
||||||
tone: FaiPillTone.success,
|
tone: ChainPillTone.success,
|
||||||
)
|
)
|
||||||
else if (endpoint.errorKind.isNotEmpty)
|
else if (endpoint.errorKind.isNotEmpty)
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: endpoint.errorKind,
|
label: endpoint.errorKind,
|
||||||
tone: FaiPillTone.danger,
|
tone: ChainPillTone.danger,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
endpoint.baseUrl,
|
endpoint.baseUrl,
|
||||||
style: FaiTheme.mono(
|
style: ChainTheme.mono(
|
||||||
size: 10,
|
size: 10,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -1554,7 +1554,7 @@ class _AddN8nEndpointDialogState extends State<_AddN8nEndpointDialog> {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _name,
|
controller: _name,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
|
|
@ -1565,7 +1565,7 @@ class _AddN8nEndpointDialogState extends State<_AddN8nEndpointDialog> {
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _baseUrl,
|
controller: _baseUrl,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|
@ -1575,7 +1575,7 @@ class _AddN8nEndpointDialogState extends State<_AddN8nEndpointDialog> {
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _apiKey,
|
controller: _apiKey,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|
@ -1586,7 +1586,7 @@ class _AddN8nEndpointDialogState extends State<_AddN8nEndpointDialog> {
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _desc,
|
controller: _desc,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|
@ -1718,7 +1718,7 @@ class _MaintenancePanelState extends State<_MaintenancePanel> {
|
||||||
_resetting = true;
|
_resetting = true;
|
||||||
_resultText = null;
|
_resultText = null;
|
||||||
});
|
});
|
||||||
final r = await SystemActions.faiReset(
|
final r = await SystemActions.chainReset(
|
||||||
keepModules: _keepModules,
|
keepModules: _keepModules,
|
||||||
keepData: _keepData,
|
keepData: _keepData,
|
||||||
);
|
);
|
||||||
|
|
@ -1755,7 +1755,7 @@ class _MaintenancePanelState extends State<_MaintenancePanel> {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
dense: true,
|
dense: true,
|
||||||
|
|
@ -1788,7 +1788,7 @@ class _MaintenancePanelState extends State<_MaintenancePanel> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
|
|
@ -1813,8 +1813,8 @@ class _MaintenancePanelState extends State<_MaintenancePanel> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (_resultText != null) ...[
|
if (_resultText != null) ...[
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
FaiErrorBox(text: _resultText!, isError: !_resultOk, maxHeight: 240),
|
ChainErrorBox(text: _resultText!, isError: !_resultOk, maxHeight: 240),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -1891,7 +1891,7 @@ class _RegistryCredentialsPanelState extends State<_RegistryCredentialsPanel> {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
|
|
@ -1901,7 +1901,7 @@ class _RegistryCredentialsPanelState extends State<_RegistryCredentialsPanel> {
|
||||||
? theme.colorScheme.primary
|
? theme.colorScheme.primary
|
||||||
: theme.colorScheme.onSurfaceVariant,
|
: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.xs),
|
const SizedBox(width: ChainSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
isConfigured
|
isConfigured
|
||||||
? l.registryTokenStatusConfigured(widget.configuredChars!)
|
? l.registryTokenStatusConfigured(widget.configuredChars!)
|
||||||
|
|
@ -1925,7 +1925,7 @@ class _RegistryCredentialsPanelState extends State<_RegistryCredentialsPanel> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
@ -1947,7 +1947,7 @@ class _RegistryCredentialsPanelState extends State<_RegistryCredentialsPanel> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
icon: const Icon(Icons.save_outlined, size: 14),
|
icon: const Icon(Icons.save_outlined, size: 14),
|
||||||
label: Text(l.registryTokenSaveButton),
|
label: Text(l.registryTokenSaveButton),
|
||||||
|
|
@ -2040,7 +2040,7 @@ class _HubAuthTokenPanelState extends State<_HubAuthTokenPanel> {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
|
|
@ -2050,7 +2050,7 @@ class _HubAuthTokenPanelState extends State<_HubAuthTokenPanel> {
|
||||||
? theme.colorScheme.primary
|
? theme.colorScheme.primary
|
||||||
: theme.colorScheme.onSurfaceVariant,
|
: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.xs),
|
const SizedBox(width: ChainSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
isConfigured
|
isConfigured
|
||||||
? l.hubAuthTokenStatusConfigured(widget.configuredChars!)
|
? l.hubAuthTokenStatusConfigured(widget.configuredChars!)
|
||||||
|
|
@ -2074,7 +2074,7 @@ class _HubAuthTokenPanelState extends State<_HubAuthTokenPanel> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
@ -2096,7 +2096,7 @@ class _HubAuthTokenPanelState extends State<_HubAuthTokenPanel> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
icon: const Icon(Icons.save_outlined, size: 14),
|
icon: const Icon(Icons.save_outlined, size: 14),
|
||||||
label: Text(l.hubAuthTokenSaveButton),
|
label: Text(l.hubAuthTokenSaveButton),
|
||||||
|
|
@ -2410,10 +2410,10 @@ class _DefaultScopePanelState extends State<_DefaultScopePanel> {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
if (_loading)
|
if (_loading)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: FaiSpace.sm),
|
padding: EdgeInsets.symmetric(vertical: ChainSpace.sm),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
width: 16,
|
width: 16,
|
||||||
|
|
@ -2438,7 +2438,7 @@ class _DefaultScopePanelState extends State<_DefaultScopePanel> {
|
||||||
onDown: () => _moveDown(i),
|
onDown: () => _moveDown(i),
|
||||||
onRemove: () => _removeAt(i),
|
onRemove: () => _removeAt(i),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
@ -2453,7 +2453,7 @@ class _DefaultScopePanelState extends State<_DefaultScopePanel> {
|
||||||
onSubmitted: _saving ? null : _addEntry,
|
onSubmitted: _saving ? null : _addEntry,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
icon: const Icon(Icons.add, size: 14),
|
icon: const Icon(Icons.add, size: 14),
|
||||||
label: Text(l.defaultScopeAddButton),
|
label: Text(l.defaultScopeAddButton),
|
||||||
|
|
@ -2464,7 +2464,7 @@ class _DefaultScopePanelState extends State<_DefaultScopePanel> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (unusedSuggestions.isNotEmpty) ...[
|
if (unusedSuggestions.isNotEmpty) ...[
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
l.defaultScopeSuggestions,
|
l.defaultScopeSuggestions,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -2474,7 +2474,7 @@ class _DefaultScopePanelState extends State<_DefaultScopePanel> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: FaiSpace.xs,
|
spacing: ChainSpace.xs,
|
||||||
runSpacing: 4,
|
runSpacing: 4,
|
||||||
children: [
|
children: [
|
||||||
for (final s in unusedSuggestions)
|
for (final s in unusedSuggestions)
|
||||||
|
|
@ -2489,7 +2489,7 @@ class _DefaultScopePanelState extends State<_DefaultScopePanel> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (_error != null) ...[
|
if (_error != null) ...[
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
_error!,
|
_error!,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -2603,7 +2603,7 @@ class _Sidebar extends StatelessWidget {
|
||||||
child: Container(
|
child: Container(
|
||||||
color: theme.colorScheme.surfaceContainer,
|
color: theme.colorScheme.surfaceContainer,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.symmetric(vertical: FaiSpace.lg),
|
padding: const EdgeInsets.symmetric(vertical: ChainSpace.lg),
|
||||||
children: [
|
children: [
|
||||||
for (final (cat, icon, label) in items)
|
for (final (cat, icon, label) in items)
|
||||||
_SidebarRow(
|
_SidebarRow(
|
||||||
|
|
@ -2639,24 +2639,24 @@ class _SidebarRow extends StatelessWidget {
|
||||||
? theme.colorScheme.onSurface
|
? theme.colorScheme.onSurface
|
||||||
: theme.colorScheme.onSurfaceVariant;
|
: theme.colorScheme.onSurfaceVariant;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: FaiSpace.sm, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: ChainSpace.sm, vertical: 2),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: selected
|
color: selected
|
||||||
? theme.colorScheme.primary.withValues(alpha: 0.14)
|
? theme.colorScheme.primary.withValues(alpha: 0.14)
|
||||||
: Colors.transparent,
|
: Colors.transparent,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.md,
|
horizontal: ChainSpace.md,
|
||||||
vertical: 8,
|
vertical: 8,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 16, color: fg),
|
Icon(icon, size: 16, color: fg),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
label,
|
label,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
// FaiStatusDot — small pulsing dot used as a "live" indicator.
|
// ChainStatusDot — small pulsing dot used as a "live" indicator.
|
||||||
// Goes still when [pulsing] is false, breathes gently when true.
|
// Goes still when [pulsing] is false, breathes gently when true.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
|
|
||||||
class FaiStatusDot extends StatefulWidget {
|
class ChainStatusDot extends StatefulWidget {
|
||||||
final Color color;
|
final Color color;
|
||||||
|
|
||||||
/// When true, the dot fades between full and 30% opacity.
|
/// When true, the dot fades between full and 30% opacity.
|
||||||
|
|
@ -14,7 +14,7 @@ class FaiStatusDot extends StatefulWidget {
|
||||||
|
|
||||||
final double size;
|
final double size;
|
||||||
|
|
||||||
const FaiStatusDot({
|
const ChainStatusDot({
|
||||||
super.key,
|
super.key,
|
||||||
required this.color,
|
required this.color,
|
||||||
this.pulsing = false,
|
this.pulsing = false,
|
||||||
|
|
@ -22,10 +22,10 @@ class FaiStatusDot extends StatefulWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FaiStatusDot> createState() => _FaiStatusDotState();
|
State<ChainStatusDot> createState() => _FaiStatusDotState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FaiStatusDotState extends State<FaiStatusDot>
|
class _FaiStatusDotState extends State<ChainStatusDot>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late final AnimationController _ctrl;
|
late final AnimationController _ctrl;
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ class _FaiStatusDotState extends State<FaiStatusDot>
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant FaiStatusDot old) {
|
void didUpdateWidget(covariant ChainStatusDot old) {
|
||||||
super.didUpdateWidget(old);
|
super.didUpdateWidget(old);
|
||||||
if (widget.pulsing && !_ctrl.isAnimating) {
|
if (widget.pulsing && !_ctrl.isAnimating) {
|
||||||
_ctrl.repeat(reverse: true);
|
_ctrl.repeat(reverse: true);
|
||||||
|
|
@ -86,10 +86,10 @@ class _FaiStatusDotState extends State<FaiStatusDot>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience presets.
|
/// Convenience presets.
|
||||||
class FaiStatusDots {
|
class ChainStatusDots {
|
||||||
FaiStatusDots._();
|
ChainStatusDots._();
|
||||||
static Widget live() =>
|
static Widget live() =>
|
||||||
const FaiStatusDot(color: FaiColors.success, pulsing: true);
|
const ChainStatusDot(color: ChainColors.success, pulsing: true);
|
||||||
static Widget idle() => const FaiStatusDot(color: FaiColors.muted);
|
static Widget idle() => const ChainStatusDot(color: ChainColors.muted);
|
||||||
static Widget down() => const FaiStatusDot(color: FaiColors.danger);
|
static Widget down() => const ChainStatusDot(color: ChainColors.danger);
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// FaiSystemAiEditor — modal for configuring the hub-internal
|
// ChainSystemAiEditor — modal for configuring the hub-internal
|
||||||
// System AI from inside Studio. Mirrors the YAML block but with
|
// System AI from inside Studio. Mirrors the YAML block but with
|
||||||
// provider presets, in-place explainers, and a "Test connection"
|
// provider presets, in-place explainers, and a "Test connection"
|
||||||
// button. On save, calls HubAdmin.UpdateSystemAi which both
|
// button. On save, calls HubAdmin.UpdateSystemAi which both
|
||||||
|
|
@ -11,10 +11,10 @@ import '../data/hub.dart';
|
||||||
import '../l10n/app_localizations.dart';
|
import '../l10n/app_localizations.dart';
|
||||||
import '../theme/theme.dart';
|
import '../theme/theme.dart';
|
||||||
import '../theme/tokens.dart';
|
import '../theme/tokens.dart';
|
||||||
import 'fai_pill.dart';
|
import 'chain_pill.dart';
|
||||||
|
|
||||||
/// Provider preset metadata kept in sync with
|
/// Provider preset metadata kept in sync with
|
||||||
/// `fai_hub::operator_config::SystemLlmProvider`. Anything that
|
/// `chain_hub::operator_config::SystemLlmProvider`. Anything that
|
||||||
/// is operator-visible (label, description, default endpoint,
|
/// is operator-visible (label, description, default endpoint,
|
||||||
/// suggested env-var) lives here so the dropdown self-explains
|
/// suggested env-var) lives here so the dropdown self-explains
|
||||||
/// without needing to read the source.
|
/// without needing to read the source.
|
||||||
|
|
@ -108,10 +108,10 @@ class _ProviderPreset {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FaiSystemAiEditor extends StatefulWidget {
|
class ChainSystemAiEditor extends StatefulWidget {
|
||||||
final SystemAiStatus initial;
|
final SystemAiStatus initial;
|
||||||
|
|
||||||
const FaiSystemAiEditor({super.key, required this.initial});
|
const ChainSystemAiEditor({super.key, required this.initial});
|
||||||
|
|
||||||
/// Convenience launcher. Returns the new status when the
|
/// Convenience launcher. Returns the new status when the
|
||||||
/// operator saved, null on cancel.
|
/// operator saved, null on cancel.
|
||||||
|
|
@ -121,15 +121,15 @@ class FaiSystemAiEditor extends StatefulWidget {
|
||||||
) {
|
) {
|
||||||
return showDialog<SystemAiStatus>(
|
return showDialog<SystemAiStatus>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => FaiSystemAiEditor(initial: initial),
|
builder: (_) => ChainSystemAiEditor(initial: initial),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FaiSystemAiEditor> createState() => _FaiSystemAiEditorState();
|
State<ChainSystemAiEditor> createState() => _FaiSystemAiEditorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FaiSystemAiEditorState extends State<FaiSystemAiEditor> {
|
class _FaiSystemAiEditorState extends State<ChainSystemAiEditor> {
|
||||||
late _ProviderPreset _preset;
|
late _ProviderPreset _preset;
|
||||||
late final TextEditingController _endpoint;
|
late final TextEditingController _endpoint;
|
||||||
late final TextEditingController _model;
|
late final TextEditingController _model;
|
||||||
|
|
@ -365,11 +365,11 @@ class _FaiSystemAiEditorState extends State<FaiSystemAiEditor> {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(l.systemAiTitle),
|
title: Text(l.systemAiTitle),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(ChainRadius.md),
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.xl,
|
horizontal: ChainSpace.xl,
|
||||||
vertical: FaiSpace.lg,
|
vertical: ChainSpace.lg,
|
||||||
),
|
),
|
||||||
content: ConstrainedBox(
|
content: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 520, maxHeight: 600),
|
constraints: const BoxConstraints(maxWidth: 520, maxHeight: 600),
|
||||||
|
|
@ -385,10 +385,10 @@ class _FaiSystemAiEditorState extends State<FaiSystemAiEditor> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_hw != null) ...[
|
if (_hw != null) ...[
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
_HardwareBanner(hw: _hw!, lastReviewed: _curated?.lastReviewed),
|
_HardwareBanner(hw: _hw!, lastReviewed: _curated?.lastReviewed),
|
||||||
],
|
],
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: ChainSpace.lg),
|
||||||
_ProviderDropdown(value: _preset, onChanged: _onProviderChanged),
|
_ProviderDropdown(value: _preset, onChanged: _onProviderChanged),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -397,17 +397,17 @@ class _FaiSystemAiEditorState extends State<FaiSystemAiEditor> {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _endpoint,
|
controller: _endpoint,
|
||||||
style: FaiTheme.mono(size: 12),
|
style: ChainTheme.mono(size: 12),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: l.systemAiEndpointLabel,
|
labelText: l.systemAiEndpointLabel,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
_ModelPicker(
|
_ModelPicker(
|
||||||
controller: _model,
|
controller: _model,
|
||||||
preset: _preset,
|
preset: _preset,
|
||||||
|
|
@ -424,10 +424,10 @@ class _FaiSystemAiEditorState extends State<FaiSystemAiEditor> {
|
||||||
? null
|
? null
|
||||||
: _pullModel,
|
: _pullModel,
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _apiKeyEnv,
|
controller: _apiKeyEnv,
|
||||||
style: FaiTheme.mono(size: 12),
|
style: ChainTheme.mono(size: 12),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: _preset.wire == 'openai'
|
labelText: _preset.wire == 'openai'
|
||||||
? l.systemAiApiKeyRequired
|
? l.systemAiApiKeyRequired
|
||||||
|
|
@ -445,20 +445,20 @@ class _FaiSystemAiEditorState extends State<FaiSystemAiEditor> {
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: ChainSpace.lg),
|
||||||
_PrivacyModeChips(
|
_PrivacyModeChips(
|
||||||
value: _privacyMode,
|
value: _privacyMode,
|
||||||
onChanged: (v) => setState(() => _privacyMode = v),
|
onChanged: (v) => setState(() => _privacyMode = v),
|
||||||
),
|
),
|
||||||
if (widget.initial.cacheCount > 0) ...[
|
if (widget.initial.cacheCount > 0) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
_CacheStatusRow(
|
_CacheStatusRow(
|
||||||
cacheCount: widget.initial.cacheCount,
|
cacheCount: widget.initial.cacheCount,
|
||||||
onClear: _saving || _testing ? null : _clearCache,
|
onClear: _saving || _testing ? null : _clearCache,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (_error != null) ...[
|
if (_error != null) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Text(
|
Text(
|
||||||
_error!,
|
_error!,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -467,7 +467,7 @@ class _FaiSystemAiEditorState extends State<FaiSystemAiEditor> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (_testResult != null) ...[
|
if (_testResult != null) ...[
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
_TestResultPanel(result: _testResult!),
|
_TestResultPanel(result: _testResult!),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
@ -573,11 +573,11 @@ class _PrivacyModeChips extends StatelessWidget {
|
||||||
for (final (wire, label, desc) in modes)
|
for (final (wire, label, desc) in modes)
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () => onChanged(wire),
|
onTap: () => onChanged(wire),
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: FaiSpace.sm,
|
vertical: ChainSpace.sm,
|
||||||
horizontal: FaiSpace.sm,
|
horizontal: ChainSpace.sm,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -616,13 +616,13 @@ class _TestResultPanel extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
final ok = result.isSuccess;
|
final ok = result.isSuccess;
|
||||||
final color = ok ? FaiColors.success : theme.colorScheme.error;
|
final color = ok ? ChainColors.success : theme.colorScheme.error;
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(FaiSpace.md),
|
padding: const EdgeInsets.all(ChainSpace.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: color.withValues(alpha: 0.3)),
|
border: Border.all(color: color.withValues(alpha: 0.3)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -635,16 +635,16 @@ class _TestResultPanel extends StatelessWidget {
|
||||||
size: 14,
|
size: 14,
|
||||||
color: color,
|
color: color,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.xs),
|
const SizedBox(width: ChainSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
ok ? l.systemAiConnectionOk : l.systemAiConnectionFailed,
|
ok ? l.systemAiConnectionOk : l.systemAiConnectionFailed,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(color: color),
|
style: theme.textTheme.bodyMedium?.copyWith(color: color),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if (ok && result.latencyMs > 0)
|
if (ok && result.latencyMs > 0)
|
||||||
FaiPill(
|
ChainPill(
|
||||||
label: '${result.latencyMs} ms',
|
label: '${result.latencyMs} ms',
|
||||||
tone: FaiPillTone.neutral,
|
tone: ChainPillTone.neutral,
|
||||||
monospace: true,
|
monospace: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -652,10 +652,10 @@ class _TestResultPanel extends StatelessWidget {
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
ok ? l.systemAiReplyPrefix(result.text) : result.text,
|
ok ? l.systemAiReplyPrefix(result.text) : result.text,
|
||||||
style: FaiTheme.mono(size: 11, color: theme.colorScheme.onSurface),
|
style: ChainTheme.mono(size: 11, color: theme.colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
if (!ok && result.fixHint(l).isNotEmpty) ...[
|
if (!ok && result.fixHint(l).isNotEmpty) ...[
|
||||||
const SizedBox(height: FaiSpace.xs),
|
const SizedBox(height: ChainSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
result.fixHint(l),
|
result.fixHint(l),
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -720,7 +720,7 @@ class _ModelPicker extends StatelessWidget {
|
||||||
valueListenable: controller,
|
valueListenable: controller,
|
||||||
builder: (_, _, _) => TextField(
|
builder: (_, _, _) => TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
style: FaiTheme.mono(size: 12),
|
style: ChainTheme.mono(size: 12),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: l.systemAiModelLabel,
|
labelText: l.systemAiModelLabel,
|
||||||
hintText: preset.modelHint.isEmpty
|
hintText: preset.modelHint.isEmpty
|
||||||
|
|
@ -738,7 +738,7 @@ class _ModelPicker extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: onRefresh,
|
onPressed: onRefresh,
|
||||||
icon: loading
|
icon: loading
|
||||||
|
|
@ -751,7 +751,7 @@ class _ModelPicker extends StatelessWidget {
|
||||||
label: Text(l.systemAiRefresh),
|
label: Text(l.systemAiRefresh),
|
||||||
),
|
),
|
||||||
if (_isOllama) ...[
|
if (_isOllama) ...[
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: ChainSpace.sm),
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: onPull,
|
onPressed: onPull,
|
||||||
icon: pulling
|
icon: pulling
|
||||||
|
|
@ -776,7 +776,7 @@ class _ModelPicker extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (models != null) ...[
|
if (models != null) ...[
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
if (models!.isEmpty)
|
if (models!.isEmpty)
|
||||||
Text(
|
Text(
|
||||||
_isOllama ? l.systemAiNoModelsOllama : l.systemAiNoModelsGeneric,
|
_isOllama ? l.systemAiNoModelsOllama : l.systemAiNoModelsGeneric,
|
||||||
|
|
@ -786,8 +786,8 @@ class _ModelPicker extends StatelessWidget {
|
||||||
)
|
)
|
||||||
else ...[
|
else ...[
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: FaiSpace.xs,
|
spacing: ChainSpace.xs,
|
||||||
runSpacing: FaiSpace.xs,
|
runSpacing: ChainSpace.xs,
|
||||||
children: [
|
children: [
|
||||||
for (final id in _sortedBySuitability(
|
for (final id in _sortedBySuitability(
|
||||||
models!,
|
models!,
|
||||||
|
|
@ -850,13 +850,13 @@ extension _SuitabilityCopy on _Suitability {
|
||||||
Color color(ColorScheme cs) {
|
Color color(ColorScheme cs) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case _Suitability.recommended:
|
case _Suitability.recommended:
|
||||||
return FaiColors.success;
|
return ChainColors.success;
|
||||||
case _Suitability.balanced:
|
case _Suitability.balanced:
|
||||||
return cs.primary;
|
return cs.primary;
|
||||||
case _Suitability.small:
|
case _Suitability.small:
|
||||||
return FaiColors.warning;
|
return ChainColors.warning;
|
||||||
case _Suitability.large:
|
case _Suitability.large:
|
||||||
return FaiColors.warning;
|
return ChainColors.warning;
|
||||||
case _Suitability.huge:
|
case _Suitability.huge:
|
||||||
return cs.error;
|
return cs.error;
|
||||||
case _Suitability.unknown:
|
case _Suitability.unknown:
|
||||||
|
|
@ -1005,7 +1005,7 @@ class _ModelChip extends StatelessWidget {
|
||||||
size: suitability == _Suitability.recommended ? 14 : 9,
|
size: suitability == _Suitability.recommended ? 14 : 9,
|
||||||
color: color,
|
color: color,
|
||||||
),
|
),
|
||||||
label: Text(id, style: FaiTheme.mono(size: 11)),
|
label: Text(id, style: ChainTheme.mono(size: 11)),
|
||||||
side: BorderSide(color: color.withValues(alpha: 0.4)),
|
side: BorderSide(color: color.withValues(alpha: 0.4)),
|
||||||
onPressed: onTap,
|
onPressed: onTap,
|
||||||
),
|
),
|
||||||
|
|
@ -1030,10 +1030,10 @@ class _HardwareBanner extends StatelessWidget {
|
||||||
? l.systemAiHwReviewed(lastReviewed!)
|
? l.systemAiHwReviewed(lastReviewed!)
|
||||||
: '';
|
: '';
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: FaiSpace.sm, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: ChainSpace.sm, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -1067,7 +1067,7 @@ class _SuitabilityLegend extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
Widget dot(Color c, String label) => Padding(
|
Widget dot(Color c, String label) => Padding(
|
||||||
padding: const EdgeInsets.only(right: FaiSpace.md),
|
padding: const EdgeInsets.only(right: ChainSpace.md),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -1090,11 +1090,11 @@ class _SuitabilityLegend extends StatelessWidget {
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: FaiSpace.md),
|
padding: const EdgeInsets.only(right: ChainSpace.md),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.star, size: 11, color: FaiColors.success),
|
Icon(Icons.star, size: 11, color: ChainColors.success),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
l.systemAiLegendRecommended(tierTag),
|
l.systemAiLegendRecommended(tierTag),
|
||||||
|
|
@ -1107,7 +1107,7 @@ class _SuitabilityLegend extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
dot(theme.colorScheme.primary, l.systemAiLegendBalanced),
|
dot(theme.colorScheme.primary, l.systemAiLegendBalanced),
|
||||||
dot(FaiColors.warning, l.systemAiLegendSmallLarge),
|
dot(ChainColors.warning, l.systemAiLegendSmallLarge),
|
||||||
dot(theme.colorScheme.error, l.systemAiLegendHuge),
|
dot(theme.colorScheme.error, l.systemAiLegendHuge),
|
||||||
dot(theme.colorScheme.outline, l.systemAiLegendUnknown),
|
dot(theme.colorScheme.outline, l.systemAiLegendUnknown),
|
||||||
],
|
],
|
||||||
|
|
@ -1131,10 +1131,10 @@ class _CacheStatusRow extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final l = AppLocalizations.of(context)!;
|
final l = AppLocalizations.of(context)!;
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: FaiSpace.sm, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: ChainSpace.sm, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -55,7 +55,7 @@ class _ThemePickerGridState extends State<ThemePickerGrid> {
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
if (snap.connectionState != ConnectionState.done) {
|
if (snap.connectionState != ConnectionState.done) {
|
||||||
return const Padding(
|
return const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: FaiSpace.sm),
|
padding: EdgeInsets.symmetric(vertical: ChainSpace.sm),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 14,
|
width: 14,
|
||||||
height: 14,
|
height: 14,
|
||||||
|
|
@ -85,7 +85,7 @@ class _ThemePickerGridState extends State<ThemePickerGrid> {
|
||||||
_customTile(theme, l, active),
|
_customTile(theme, l, active),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: ChainSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
l.themePluginHint,
|
l.themePluginHint,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
|
@ -248,13 +248,13 @@ class _Tile extends StatelessWidget {
|
||||||
height: 76,
|
height: 76,
|
||||||
child: Material(
|
child: Material(
|
||||||
color: theme.colorScheme.surfaceContainerHigh,
|
color: theme.colorScheme.surfaceContainerHigh,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(ChainRadius.sm),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: selected
|
color: selected
|
||||||
? theme.colorScheme.primary
|
? theme.colorScheme.primary
|
||||||
|
|
@ -432,7 +432,7 @@ class _ColorPickerDialogState extends State<_ColorPickerDialog> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _hex,
|
controller: _hex,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|
@ -444,7 +444,7 @@ class _ColorPickerDialogState extends State<_ColorPickerDialog> {
|
||||||
onChanged: _commitHex,
|
onChanged: _commitHex,
|
||||||
onSubmitted: _commitHex,
|
onSubmitted: _commitHex,
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: ChainSpace.md),
|
||||||
Text(l.themePluginCustomPreview, style: theme.textTheme.labelSmall),
|
Text(l.themePluginCustomPreview, style: theme.textTheme.labelSmall),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Row(
|
Row(
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,17 @@
|
||||||
// primitives directly — no Material `Card` / generic `Container`
|
// primitives directly — no Material `Card` / generic `Container`
|
||||||
// soup in page code.
|
// soup in page code.
|
||||||
|
|
||||||
export 'fai_binary_recovery.dart';
|
export 'chain_binary_recovery.dart';
|
||||||
export 'fai_card.dart';
|
export 'chain_card.dart';
|
||||||
export 'fai_data_row.dart';
|
export 'chain_data_row.dart';
|
||||||
export 'fai_delta_mark.dart';
|
export 'chain_delta_mark.dart';
|
||||||
export 'fai_empty_state.dart';
|
export 'chain_empty_state.dart';
|
||||||
export 'fai_en_badge.dart';
|
export 'chain_en_badge.dart';
|
||||||
export 'fai_error_box.dart';
|
export 'chain_error_box.dart';
|
||||||
export 'fai_flow_output.dart';
|
export 'chain_flow_output.dart';
|
||||||
export 'fai_log_viewer.dart';
|
export 'chain_log_viewer.dart';
|
||||||
export 'fai_module_sheet.dart';
|
export 'chain_module_sheet.dart';
|
||||||
export 'fai_pill.dart';
|
export 'chain_pill.dart';
|
||||||
export 'fai_settings_dialog.dart';
|
export 'chain_settings_dialog.dart';
|
||||||
export 'fai_status_dot.dart';
|
export 'chain_status_dot.dart';
|
||||||
export 'fai_system_ai_editor.dart';
|
export 'chain_system_ai_editor.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,36 @@
|
||||||
// Unit tests for FaiLog — the central error log that
|
// Unit tests for ChainLog — the central error log that
|
||||||
// `~/.fai/logs/studio-errors.log` is the operator-visible
|
// `~/.fai/logs/studio-errors.log` is the operator-visible
|
||||||
// surface of. The tests redirect the singleton at a temp file
|
// surface of. The tests redirect the singleton at a temp file
|
||||||
// via `FaiLog.testPathOverride` so the real log under HOME is
|
// via `ChainLog.testPathOverride` so the real log under HOME is
|
||||||
// never touched.
|
// never touched.
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:chain_studio/data/fai_log.dart';
|
import 'package:chain_studio/data/chain_log.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('FaiLog', () {
|
group('ChainLog', () {
|
||||||
late Directory tmp;
|
late Directory tmp;
|
||||||
late File logFile;
|
late File logFile;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
tmp = Directory.systemTemp.createTempSync('fai_log_test_');
|
tmp = Directory.systemTemp.createTempSync('chain_log_test_');
|
||||||
logFile = File('${tmp.path}/studio-errors.log');
|
logFile = File('${tmp.path}/studio-errors.log');
|
||||||
FaiLog.testPathOverride = logFile.path;
|
ChainLog.testPathOverride = logFile.path;
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() {
|
tearDown(() {
|
||||||
FaiLog.testPathOverride = null;
|
ChainLog.testPathOverride = null;
|
||||||
try {
|
try {
|
||||||
tmp.deleteSync(recursive: true);
|
tmp.deleteSync(recursive: true);
|
||||||
} catch (_) {/* best-effort */}
|
} catch (_) {/* best-effort */}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('append writes one JSON-shaped line per event', () async {
|
test('append writes one JSON-shaped line per event', () async {
|
||||||
await FaiLog.instance.error('flows.run', 'boom');
|
await ChainLog.instance.error('flows.run', 'boom');
|
||||||
final lines = await FaiLog.instance.tail();
|
final lines = await ChainLog.instance.tail();
|
||||||
expect(lines, hasLength(1));
|
expect(lines, hasLength(1));
|
||||||
final entry = jsonDecode(lines.single) as Map<String, Object?>;
|
final entry = jsonDecode(lines.single) as Map<String, Object?>;
|
||||||
expect(entry['source'], 'flows.run');
|
expect(entry['source'], 'flows.run');
|
||||||
|
|
@ -40,22 +40,22 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('context field is preserved when supplied', () async {
|
test('context field is preserved when supplied', () async {
|
||||||
await FaiLog.instance.error(
|
await ChainLog.instance.error(
|
||||||
'theme.plugin.load',
|
'theme.plugin.load',
|
||||||
Exception('endpoint unreachable'),
|
Exception('endpoint unreachable'),
|
||||||
context: 'capability=studio.theme.space',
|
context: 'capability=studio.theme.space',
|
||||||
);
|
);
|
||||||
final lines = await FaiLog.instance.tail();
|
final lines = await ChainLog.instance.tail();
|
||||||
final entry = jsonDecode(lines.single) as Map<String, Object?>;
|
final entry = jsonDecode(lines.single) as Map<String, Object?>;
|
||||||
expect(entry['context'], 'capability=studio.theme.space');
|
expect(entry['context'], 'capability=studio.theme.space');
|
||||||
expect(entry['error'], contains('endpoint unreachable'));
|
expect(entry['error'], contains('endpoint unreachable'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tail returns oldest first', () async {
|
test('tail returns oldest first', () async {
|
||||||
await FaiLog.instance.error('a', '1');
|
await ChainLog.instance.error('a', '1');
|
||||||
await FaiLog.instance.error('b', '2');
|
await ChainLog.instance.error('b', '2');
|
||||||
await FaiLog.instance.error('c', '3');
|
await ChainLog.instance.error('c', '3');
|
||||||
final lines = await FaiLog.instance.tail();
|
final lines = await ChainLog.instance.tail();
|
||||||
expect(lines, hasLength(3));
|
expect(lines, hasLength(3));
|
||||||
final sources = lines.map((l) => jsonDecode(l)['source']).toList();
|
final sources = lines.map((l) => jsonDecode(l)['source']).toList();
|
||||||
expect(sources, ['a', 'b', 'c']);
|
expect(sources, ['a', 'b', 'c']);
|
||||||
|
|
@ -63,9 +63,9 @@ void main() {
|
||||||
|
|
||||||
test('tail caps output at maxLines and keeps the newest', () async {
|
test('tail caps output at maxLines and keeps the newest', () async {
|
||||||
for (var i = 0; i < 20; i++) {
|
for (var i = 0; i < 20; i++) {
|
||||||
await FaiLog.instance.error('burst', 'event-$i');
|
await ChainLog.instance.error('burst', 'event-$i');
|
||||||
}
|
}
|
||||||
final lines = await FaiLog.instance.tail(maxLines: 5);
|
final lines = await ChainLog.instance.tail(maxLines: 5);
|
||||||
expect(lines, hasLength(5));
|
expect(lines, hasLength(5));
|
||||||
final last = jsonDecode(lines.last) as Map<String, Object?>;
|
final last = jsonDecode(lines.last) as Map<String, Object?>;
|
||||||
expect(last['error'], 'event-19');
|
expect(last['error'], 'event-19');
|
||||||
|
|
@ -77,7 +77,7 @@ void main() {
|
||||||
// `.log.1` and a short fresh `.log`.
|
// `.log.1` and a short fresh `.log`.
|
||||||
logFile.parent.createSync(recursive: true);
|
logFile.parent.createSync(recursive: true);
|
||||||
logFile.writeAsBytesSync(List.filled(257 * 1024, 0x20));
|
logFile.writeAsBytesSync(List.filled(257 * 1024, 0x20));
|
||||||
await FaiLog.instance.error('rotation', 'after-cap');
|
await ChainLog.instance.error('rotation', 'after-cap');
|
||||||
expect(File('${logFile.path}.1').existsSync(), isTrue);
|
expect(File('${logFile.path}.1').existsSync(), isTrue);
|
||||||
final newSize = logFile.lengthSync();
|
final newSize = logFile.lengthSync();
|
||||||
expect(newSize, lessThan(2 * 1024));
|
expect(newSize, lessThan(2 * 1024));
|
||||||
|
|
@ -86,16 +86,16 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('path getter returns the override when set', () {
|
test('path getter returns the override when set', () {
|
||||||
expect(FaiLog.instance.path, logFile.path);
|
expect(ChainLog.instance.path, logFile.path);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('writes failing silently does not throw', () async {
|
test('writes failing silently does not throw', () async {
|
||||||
// Redirect to a deliberately impossible path. The error()
|
// Redirect to a deliberately impossible path. The error()
|
||||||
// call must complete cleanly; the operator UI must never
|
// call must complete cleanly; the operator UI must never
|
||||||
// crash because the disk is full / read-only / missing.
|
// crash because the disk is full / read-only / missing.
|
||||||
FaiLog.testPathOverride = '/this/path/does/not/exist/ever/x.log';
|
ChainLog.testPathOverride = '/this/path/does/not/exist/ever/x.log';
|
||||||
await expectLater(
|
await expectLater(
|
||||||
FaiLog.instance.error('bad.path', 'boom'),
|
ChainLog.instance.error('bad.path', 'boom'),
|
||||||
completes,
|
completes,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -35,14 +35,14 @@ flutter test test/integration/
|
||||||
```
|
```
|
||||||
|
|
||||||
Prereq: a `fai` binary on PATH or at
|
Prereq: a `fai` binary on PATH or at
|
||||||
`../fai_platform/target/release/fai`. The harness skips with a
|
`../fai_chain/target/release/chain`. The harness skips with a
|
||||||
clear message when neither exists, so this command does not
|
clear message when neither exists, so this command does not
|
||||||
fail on a fresh checkout — it just reports skipped tests.
|
fail on a fresh checkout — it just reports skipped tests.
|
||||||
|
|
||||||
To get the binary:
|
To get the binary:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ../fai_platform
|
cd ../fai_chain
|
||||||
cargo build --release --bin fai
|
cargo build --release --bin fai
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -52,11 +52,11 @@ A cold `chain serve` spends its first ~30s building the
|
||||||
curated-model database and initialising SQLite migrations.
|
curated-model database and initialising SQLite migrations.
|
||||||
`HubFixture.start()` waits up to 60s by default; if your local
|
`HubFixture.start()` waits up to 60s by default; if your local
|
||||||
hub takes longer the first time, run `chain serve` once by hand
|
hub takes longer the first time, run `chain serve` once by hand
|
||||||
against any temp `FAI_DATA_DIR` to warm the per-user cargo /
|
against any temp `CHAIN_DATA_DIR` to warm the per-user cargo /
|
||||||
SBOM caches:
|
SBOM caches:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
FAI_DATA_DIR=/tmp/fai_warmup chain serve --bind 127.0.0.1:0
|
CHAIN_DATA_DIR=/tmp/chain_warmup chain serve --bind 127.0.0.1:0
|
||||||
# wait for "hub started", Ctrl-C
|
# wait for "hub started", Ctrl-C
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ Subsequent integration-test runs are quick.
|
||||||
|
|
||||||
`.forgejo/workflows/ci.yml` doesn't run these yet. Adding them
|
`.forgejo/workflows/ci.yml` doesn't run these yet. Adding them
|
||||||
needs an artifact-passing pattern: the platform build job
|
needs an artifact-passing pattern: the platform build job
|
||||||
publishes `target/release/fai` as a CI artifact; the studio
|
publishes `target/release/chain` as a CI artifact; the studio
|
||||||
test job consumes it. Pattern is straightforward once we want
|
test job consumes it. Pattern is straightforward once we want
|
||||||
it; it's deferred because we don't yet have enough integration
|
it; it's deferred because we don't yet have enough integration
|
||||||
tests to justify the CI runtime.
|
tests to justify the CI runtime.
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
// flutter test test/integration/
|
// flutter test test/integration/
|
||||||
//
|
//
|
||||||
// CI does not yet run these by default — they need a built `fai`
|
// CI does not yet run these by default — they need a built `fai`
|
||||||
// binary in PATH or in ../fai_platform/target/release/. Wire that
|
// binary in PATH or in ../chain_platform/target/release/. Wire that
|
||||||
// in via .forgejo/workflows/ci.yml once the platform CI publishes
|
// in via .forgejo/workflows/ci.yml once the platform CI publishes
|
||||||
// a Linux binary as an artifact for downstream jobs to consume.
|
// a Linux binary as an artifact for downstream jobs to consume.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ class HubFixture {
|
||||||
if (skipIfBinaryMissing) {
|
if (skipIfBinaryMissing) {
|
||||||
markTestSkipped(
|
markTestSkipped(
|
||||||
'Hub fixture skipped: no `fai` binary found on PATH or in '
|
'Hub fixture skipped: no `fai` binary found on PATH or in '
|
||||||
'../fai_platform/target/release/fai. Build it with '
|
'../chain_platform/target/release/fai. Build it with '
|
||||||
'`cargo build --release` and re-run.',
|
'`cargo build --release` and re-run.',
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -86,15 +86,15 @@ class HubFixture {
|
||||||
|
|
||||||
// Run as a child process. `serve --bind` lets us avoid the
|
// Run as a child process. `serve --bind` lets us avoid the
|
||||||
// default 50051 (and any local daemon the developer happens
|
// default 50051 (and any local daemon the developer happens
|
||||||
// to be running). FAI_DATA_DIR keeps the temp footprint
|
// to be running). CHAIN_DATA_DIR keeps the temp footprint
|
||||||
// contained — no state leaks into ~/.fai.
|
// contained — no state leaks into ~/.fai.
|
||||||
final process = await Process.start(
|
final process = await Process.start(
|
||||||
binary,
|
binary,
|
||||||
['serve', '--bind', addr],
|
['serve', '--bind', addr],
|
||||||
environment: {
|
environment: {
|
||||||
...Platform.environment,
|
...Platform.environment,
|
||||||
'FAI_DATA_DIR': tempDir.path,
|
'CHAIN_DATA_DIR': tempDir.path,
|
||||||
'FAI_MODULES_DIR': '${tempDir.path}/modules',
|
'CHAIN_MODULES_DIR': '${tempDir.path}/modules',
|
||||||
// Suppress info logs without a CLI flag — the binary
|
// Suppress info logs without a CLI flag — the binary
|
||||||
// respects RUST_LOG for tracing-subscriber.
|
// respects RUST_LOG for tracing-subscriber.
|
||||||
'RUST_LOG': 'warn',
|
'RUST_LOG': 'warn',
|
||||||
|
|
@ -136,7 +136,7 @@ class HubFixture {
|
||||||
await tempDir.delete(recursive: true);
|
await tempDir.delete(recursive: true);
|
||||||
throw StateError(
|
throw StateError(
|
||||||
'Hub did not become healthy within $readyTimeout. '
|
'Hub did not become healthy within $readyTimeout. '
|
||||||
'Check `fai serve` works manually with FAI_DATA_DIR=$tempDir.',
|
'Check `fai serve` works manually with CHAIN_DATA_DIR=$tempDir.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,8 +172,8 @@ class HubFixture {
|
||||||
}
|
}
|
||||||
|
|
||||||
final candidate = Platform.isWindows
|
final candidate = Platform.isWindows
|
||||||
? '../fai_platform/target/release/fai.exe'
|
? '../chain_platform/target/release/fai.exe'
|
||||||
: '../fai_platform/target/release/fai';
|
: '../chain_platform/target/release/fai';
|
||||||
final f = File(candidate);
|
final f = File(candidate);
|
||||||
if (await f.exists()) {
|
if (await f.exists()) {
|
||||||
return f.absolute.path;
|
return f.absolute.path;
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
// flutter test test/integration/plugin_theme_test.dart
|
// flutter test test/integration/plugin_theme_test.dart
|
||||||
//
|
//
|
||||||
// Pre-reqs (handled by HubFixture's skip-when-missing path):
|
// Pre-reqs (handled by HubFixture's skip-when-missing path):
|
||||||
// - fai binary on PATH or at ../fai_platform/target/release/fai
|
// - fai binary on PATH or at ../chain_platform/target/release/fai
|
||||||
// - studio-theme-solarized plugin built to module.wasm in its
|
// - studio-theme-solarized plugin built to module.wasm in its
|
||||||
// fai_plugins dir.
|
// chain_plugins dir.
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ void main() {
|
||||||
// add network + bundle-fetch latency that doesn't
|
// add network + bundle-fetch latency that doesn't
|
||||||
// belong in an in-process round-trip test.
|
// belong in an in-process round-trip test.
|
||||||
final pluginSrc = Directory(
|
final pluginSrc = Directory(
|
||||||
'../fai_plugins/studio-theme-solarized',
|
'../chain_plugins/studio-theme-solarized',
|
||||||
);
|
);
|
||||||
if (!await pluginSrc.exists()) {
|
if (!await pluginSrc.exists()) {
|
||||||
markTestSkipped('studio-theme-solarized source not available');
|
markTestSkipped('studio-theme-solarized source not available');
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import 'package:chain_studio/l10n/app_localizations.dart';
|
||||||
import 'package:chain_studio/theme/theme.dart';
|
import 'package:chain_studio/theme/theme.dart';
|
||||||
|
|
||||||
/// Recreates the exact DocReaderSheet code path that
|
/// Recreates the exact DocReaderSheet code path that
|
||||||
/// welcome.dart uses, including FaiTheme.markdownStyle, so a
|
/// welcome.dart uses, including ChainTheme.markdownStyle, so a
|
||||||
/// regression in either rootBundle, the markdown parser, or
|
/// regression in either rootBundle, the markdown parser, or
|
||||||
/// the style sheet surfaces here instead of only at runtime
|
/// the style sheet surfaces here instead of only at runtime
|
||||||
/// when an operator clicks a doc card.
|
/// when an operator clicks a doc card.
|
||||||
|
|
@ -16,7 +16,7 @@ void main() {
|
||||||
|
|
||||||
testWidgets('DocReaderSheet renders all four german + english docs',
|
testWidgets('DocReaderSheet renders all four german + english docs',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
final theme = FaiTheme.light();
|
final theme = ChainTheme.light();
|
||||||
for (final slug in ['architecture', 'security', 'audit', 'flows']) {
|
for (final slug in ['architecture', 'security', 'audit', 'flows']) {
|
||||||
for (final locale in ['de', 'en']) {
|
for (final locale in ['de', 'en']) {
|
||||||
final path = locale == 'de'
|
final path = locale == 'de'
|
||||||
|
|
@ -39,7 +39,7 @@ void main() {
|
||||||
data: text,
|
data: text,
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
selectable: true,
|
selectable: true,
|
||||||
styleSheet: FaiTheme.markdownStyle(theme),
|
styleSheet: ChainTheme.markdownStyle(theme),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,10 @@ A starter plist lives at `launchd/ai.flemming.chain.today.plist`.
|
||||||
|
|
||||||
| Variable | Default | Example |
|
| Variable | Default | Example |
|
||||||
|-------------------|----------------------------------|-------------------------------------------------|
|
|-------------------|----------------------------------|-------------------------------------------------|
|
||||||
| `FAI_TODAY_API` | `http://127.0.0.1:11434/api/generate` | `https://api.openai.com/v1/chat/completions` |
|
| `CHAIN_TODAY_API` | `http://127.0.0.1:11434/api/generate` | `https://api.openai.com/v1/chat/completions` |
|
||||||
| `FAI_TODAY_MODEL` | `gemma3:4b` | `gpt-4o-mini` |
|
| `CHAIN_TODAY_MODEL` | `gemma3:4b` | `gpt-4o-mini` |
|
||||||
| `FAI_TODAY_KEY` | (unset) | `$OPENAI_API_KEY` |
|
| `CHAIN_TODAY_KEY` | (unset) | `$OPENAI_API_KEY` |
|
||||||
| `FAI_TODAY_N` | `3` | `1` (single shot) |
|
| `CHAIN_TODAY_N` | `3` | `1` (single shot) |
|
||||||
|
|
||||||
OpenAI-compatible endpoints will work as long as they speak `chat/completions`
|
OpenAI-compatible endpoints will work as long as they speak `chat/completions`
|
||||||
or `generate`; the script auto-detects from the URL path.
|
or `generate`; the script auto-detects from the URL path.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
# Does not read the audit log without --with-audit.
|
# Does not read the audit log without --with-audit.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SINCE="${FAI_TODAY_SINCE:-24 hours ago}"
|
SINCE="${CHAIN_TODAY_SINCE:-24 hours ago}"
|
||||||
|
|
||||||
# Walk every fai-* directory next to chain_studio so the collector
|
# Walk every fai-* directory next to chain_studio so the collector
|
||||||
# works from any of the sibling checkouts. Adjust here if the
|
# works from any of the sibling checkouts. Adjust here if the
|
||||||
|
|
@ -31,10 +31,10 @@ for repo in "$ROOT"/fai_*/; do
|
||||||
done
|
done
|
||||||
|
|
||||||
emit_section "STORE INDEX SEED CHANGES"
|
emit_section "STORE INDEX SEED CHANGES"
|
||||||
seed="$ROOT/fai_platform/crates/fai_hub/store-index/seed.yaml"
|
seed="$ROOT/fai_chain/crates/chain_hub/store-index/seed.yaml"
|
||||||
if [ -f "$seed" ] && [ -d "$ROOT/fai_platform/.git" ]; then
|
if [ -f "$seed" ] && [ -d "$ROOT/fai_chain/.git" ]; then
|
||||||
diff=$(git -C "$ROOT/fai_platform" log --since="$SINCE" --oneline -- \
|
diff=$(git -C "$ROOT/fai_chain" log --since="$SINCE" --oneline -- \
|
||||||
crates/fai_hub/store-index/seed.yaml 2>/dev/null || true)
|
crates/chain_hub/store-index/seed.yaml 2>/dev/null || true)
|
||||||
if [ -n "$diff" ]; then
|
if [ -n "$diff" ]; then
|
||||||
printf '%s\n' "$diff"
|
printf '%s\n' "$diff"
|
||||||
else
|
else
|
||||||
|
|
@ -43,9 +43,9 @@ if [ -f "$seed" ] && [ -d "$ROOT/fai_platform/.git" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
emit_section "ARCHITECTURE / SYSTEM-GAPS CHANGES"
|
emit_section "ARCHITECTURE / SYSTEM-GAPS CHANGES"
|
||||||
arch_dir="$ROOT/fai_platform/docs"
|
arch_dir="$ROOT/fai_chain/docs"
|
||||||
if [ -d "$arch_dir" ] && [ -d "$ROOT/fai_platform/.git" ]; then
|
if [ -d "$arch_dir" ] && [ -d "$ROOT/fai_chain/.git" ]; then
|
||||||
changes=$(git -C "$ROOT/fai_platform" log --since="$SINCE" --oneline -- \
|
changes=$(git -C "$ROOT/fai_chain" log --since="$SINCE" --oneline -- \
|
||||||
docs/architecture docs/advanced 2>/dev/null || true)
|
docs/architecture docs/advanced 2>/dev/null || true)
|
||||||
if [ -n "$changes" ]; then
|
if [ -n "$changes" ]; then
|
||||||
printf '%s\n' "$changes"
|
printf '%s\n' "$changes"
|
||||||
|
|
@ -67,9 +67,9 @@ if [ -d "$studio/.git" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
emit_section "AUDIT-LOG HIGHLIGHTS"
|
emit_section "AUDIT-LOG HIGHLIGHTS"
|
||||||
if [ "${FAI_TODAY_WITH_AUDIT:-0}" = "1" ] && command -v fai >/dev/null 2>&1; then
|
if [ "${CHAIN_TODAY_WITH_AUDIT:-0}" = "1" ] && command -v fai >/dev/null 2>&1; then
|
||||||
fai audit recent --limit 20 2>/dev/null | head -40 || \
|
fai audit recent --limit 20 2>/dev/null | head -40 || \
|
||||||
printf '(audit query failed; daemon may be stopped)\n'
|
printf '(audit query failed; daemon may be stopped)\n'
|
||||||
else
|
else
|
||||||
printf '(audit pull disabled — set FAI_TODAY_WITH_AUDIT=1 to opt in)\n'
|
printf '(audit pull disabled — set CHAIN_TODAY_WITH_AUDIT=1 to opt in)\n'
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,17 @@
|
||||||
# Defaults to a local Ollama at 127.0.0.1:11434 with gemma3:4b.
|
# Defaults to a local Ollama at 127.0.0.1:11434 with gemma3:4b.
|
||||||
# See README.md for env-var overrides.
|
# See README.md for env-var overrides.
|
||||||
#
|
#
|
||||||
# Output: ~/.fai/today/proposals/<ISO-DATE>/candidate-<n>.yaml
|
# Output: ~/.chain/today/proposals/<ISO-DATE>/candidate-<n>.yaml
|
||||||
# Operator then runs accept.sh with the chosen file.
|
# Operator then runs accept.sh with the chosen file.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
DATE="$(date -u +%Y-%m-%d)"
|
DATE="$(date -u +%Y-%m-%d)"
|
||||||
OUT_DIR="${FAI_TODAY_OUT:-$HOME/.fai/today/proposals/$DATE}"
|
OUT_DIR="${CHAIN_TODAY_OUT:-$HOME/.chain/today/proposals/$DATE}"
|
||||||
N="${FAI_TODAY_N:-3}"
|
N="${CHAIN_TODAY_N:-3}"
|
||||||
API="${FAI_TODAY_API:-http://127.0.0.1:11434/api/generate}"
|
API="${CHAIN_TODAY_API:-http://127.0.0.1:11434/api/generate}"
|
||||||
MODEL="${FAI_TODAY_MODEL:-gemma3:4b}"
|
MODEL="${CHAIN_TODAY_MODEL:-gemma3:4b}"
|
||||||
KEY="${FAI_TODAY_KEY:-}"
|
KEY="${CHAIN_TODAY_KEY:-}"
|
||||||
|
|
||||||
mkdir -p "$OUT_DIR"
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue