Files
clide/app/lib/kernel/src/facade.dart
T
jpmschweitzerandClaude b13483e2d5 scaffold Flutter app — kernel, extensions, widgets, Tier 0 built-ins
First real content under app/. Lays the whole Tier 0 foundation in one
commit because the pieces depend on each other circularly (kernel →
extension → widgets → built-ins all reference types from the layer
below); splitting would leave intermediate commits that don't compile.

Key shapes:

  * bare WidgetsApp root — no Material, no Cupertino, no Scaffold.
    ClideTheme InheritedWidget is the only source of tokens.
  * ClideKernel InheritedWidget aggregates 18 services (settings,
    project, extensions, theme, panels, events, ipc, commands +
    palette + keybindings, clipboard, files, notify, dialog, tray,
    secrets, os, net, focus, log, i18n). ExtensionContext exposes
    them through a stable interface.
  * ClideExtension + sealed ContributionPoint hierarchy (Tab,
    StatusItem, Toolbar, Command, TrayItem, LayoutPreset). One
    manifest ships N contributions into kernel slots.
  * Three-tier theme pipeline: palette (named colors) → semantic
    roles → ~60 surface tokens. Defaults at each layer so legacy
    palette-only themes produce a complete SurfaceTokens.
  * A11y baked in from day one — every interactive primitive wraps
    in Semantics(label:, hint:, button:); ensureSemantics() at boot;
    theme/contrast.dart helper exposes token pairs for the WCAG
    gate in the a11y test suite.
  * i18n ported from fframe's L10n pattern (text-driven, namespaced
    JSON catalogs, caller-supplied placeholders) with a proper
    locale fallback chain (exact → language → default → placeholder)
    fframe lacks.
  * Four Tier-0 built-ins live (default-layout, welcome, ipc-status,
    theme-picker) plus 17 id-reserving stubs so later tiers fill in
    without rename churn.

.gitignore extended to cover app/ sub-package artefacts and the
Playwright harness scratch dirs.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-21 15:39:41 +02:00

237 lines
6.8 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:clide/clide.dart';
import 'package:clide_app/kernel/src/clipboard.dart';
import 'package:clide_app/kernel/src/commands/keybindings.dart';
import 'package:clide_app/kernel/src/commands/palette.dart';
import 'package:clide_app/kernel/src/commands/registry.dart';
import 'package:clide_app/kernel/src/dialog.dart';
import 'package:clide_app/kernel/src/events/bus.dart';
import 'package:clide_app/kernel/src/extensions_manager.dart';
import 'package:clide_app/kernel/src/files.dart';
import 'package:clide_app/kernel/src/focus.dart';
import 'package:clide_app/kernel/src/i18n/catalog_loader.dart';
import 'package:clide_app/kernel/src/i18n/i18n.dart';
import 'package:clide_app/kernel/src/ipc/client.dart';
import 'package:clide_app/kernel/src/log.dart';
import 'package:clide_app/kernel/src/net.dart';
import 'package:clide_app/kernel/src/notify.dart';
import 'package:clide_app/kernel/src/os.dart';
import 'package:clide_app/kernel/src/panels/arrangement.dart';
import 'package:clide_app/kernel/src/panels/registry.dart';
import 'package:clide_app/kernel/src/project.dart';
import 'package:clide_app/kernel/src/secrets.dart';
import 'package:clide_app/kernel/src/settings.dart';
import 'package:clide_app/kernel/src/theme/controller.dart';
import 'package:clide_app/kernel/src/theme/loader.dart';
import 'package:clide_app/kernel/src/tray.dart';
import 'package:flutter/widgets.dart';
/// Aggregated kernel services. Feature code that runs outside a
/// BuildContext (extensions, background tasks) holds a [KernelServices]
/// ref directly; widget code reaches them via [ClideKernel.of].
class KernelServices {
KernelServices({
required this.log,
required this.settings,
required this.events,
required this.ipc,
required this.theme,
required this.i18n,
required this.panels,
required this.arrangement,
required this.commands,
required this.palette,
required this.keybindings,
required this.clipboard,
required this.files,
required this.notify,
required this.dialog,
required this.tray,
required this.secrets,
required this.os,
required this.net,
required this.focus,
required this.project,
required this.extensions,
});
final Logger log;
final SettingsStore settings;
final EventBus events;
final DaemonClient ipc;
final ThemeController theme;
final I18n i18n;
final PanelRegistry panels;
final LayoutArrangement arrangement;
final CommandRegistry commands;
final PaletteController palette;
final KeybindingResolver keybindings;
final ClideClipboard clipboard;
final FileServices files;
final Notifications notify;
final DialogRouter dialog;
final TrayRegistry tray;
final SecretsVault secrets;
final OsBridge os;
final NetworkStatus net;
final FocusTracker focus;
final ProjectManager project;
final ExtensionManager extensions;
static Future<KernelServices> boot({
required Directory appDir,
required List<ThemeDefinition> bundledThemes,
required CatalogLoader i18nLoader,
List<String> preloadNamespaces = const [],
Locale defaultLocale = const Locale('en', 'US'),
Locale? initialLocale,
List<Locale> availableLocales = const [Locale('en', 'US')],
String? socketPath,
DaemonClient Function(Logger, EventBus)? daemonClientFactory,
bool autoStartDaemonClient = true,
}) async {
final log = Logger();
final events = EventBus();
final settings = SettingsStore(appDir: appDir);
await settings.load();
final i18n = I18n(
loader: i18nLoader,
log: log,
defaultLocale: defaultLocale,
initialLocale: initialLocale,
availableLocales: availableLocales,
);
for (final ns in preloadNamespaces) {
await i18n.ensureNamespaceLoaded(ns);
}
final theme = ThemeController(bundled: bundledThemes);
final panels = PanelRegistry();
final arrangement = LayoutArrangement();
final commands = CommandRegistry();
final keybindings = KeybindingResolver();
final palette = PaletteController(commands);
final clipboard = ClideClipboard();
final files = FileServices(events);
final notify = Notifications();
final dialog = DialogRouter();
final tray = TrayRegistry();
final secrets = SecretsVault();
final os = OsBridge(log: log, events: events);
final net = NetworkStatus();
final focus = FocusTracker();
final project = ProjectManager(
log: log,
events: events,
settings: settings,
);
final ipc = daemonClientFactory != null
? daemonClientFactory(log, events)
: DaemonClient(
socketPath: socketPath ?? defaultSocketPath(),
log: log,
events: events,
);
final extensions = ExtensionManager(
log: log,
events: events,
settings: settings,
theme: theme,
i18n: i18n,
panels: panels,
arrangement: arrangement,
commands: commands,
palette: palette,
keybindings: keybindings,
clipboard: clipboard,
files: files,
notify: notify,
dialog: dialog,
tray: tray,
secrets: secrets,
os: os,
net: net,
focus: focus,
project: project,
ipc: ipc,
);
if (autoStartDaemonClient) {
unawaited(ipc.start());
}
return KernelServices(
log: log,
settings: settings,
events: events,
ipc: ipc,
theme: theme,
i18n: i18n,
panels: panels,
arrangement: arrangement,
commands: commands,
palette: palette,
keybindings: keybindings,
clipboard: clipboard,
files: files,
notify: notify,
dialog: dialog,
tray: tray,
secrets: secrets,
os: os,
net: net,
focus: focus,
project: project,
extensions: extensions,
);
}
Future<void> dispose() async {
await ipc.stop();
ipc.dispose();
settings.dispose();
theme.dispose();
panels.dispose();
arrangement.dispose();
commands.dispose();
palette.dispose();
i18n.dispose();
notify.dispose();
dialog.dispose();
tray.dispose();
net.dispose();
focus.dispose();
project.dispose();
extensions.dispose();
await log.dispose();
await events.dispose();
}
}
class ClideKernel extends InheritedWidget {
const ClideKernel({
super.key,
required this.services,
required super.child,
});
final KernelServices services;
static KernelServices of(BuildContext context) {
final w = context.dependOnInheritedWidgetOfExactType<ClideKernel>();
if (w == null) {
throw FlutterError(
'ClideKernel.of() called with a context that is not a descendant of a ClideKernel.');
}
return w.services;
}
@override
bool updateShouldNotify(ClideKernel oldWidget) =>
services != oldWidget.services;
}