diff --git a/lib/data/hub.dart b/lib/data/hub.dart index 535aa51..15a6e4c 100644 --- a/lib/data/hub.dart +++ b/lib/data/hub.dart @@ -135,6 +135,9 @@ class HubService { version: c.version, moduleName: c.moduleName, kind: c.kind.isEmpty ? 'wasm' : c.kind, + provider: c.provider, + sourceKind: c.sourceKind, + versionAdvisory: c.versionAdvisory, )) .toList(); } @@ -915,12 +918,27 @@ class CapabilityInfo { final String version; final String moduleName; final String kind; + /// Publisher segment of the fully-qualified identifier. + /// "fai" for legacy bundles, "fai.system" for built-ins, + /// operator-config service name for federated capabilities. + final String provider; + /// Transport that delivers the capability: bundle / system / + /// mcp / n8n / temporal / webhook. Empty string falls back + /// to deriving from `kind` for pre-0.12 hubs. + final String sourceKind; + /// Whether the version on this capability is a label rather + /// than a hard contract. True for federation sources whose + /// upstream service does not honour semver. + final bool versionAdvisory; const CapabilityInfo({ required this.capability, required this.version, required this.moduleName, required this.kind, + this.provider = '', + this.sourceKind = '', + this.versionAdvisory = false, }); } diff --git a/lib/data/theme_plugin.dart b/lib/data/theme_plugin.dart index 326d3fe..86a7c07 100644 --- a/lib/data/theme_plugin.dart +++ b/lib/data/theme_plugin.dart @@ -9,9 +9,9 @@ // drop into the running app. import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../theme/theme.dart'; import 'hub.dart'; /// SharedPreferences key for the operator's preferred theme @@ -94,26 +94,15 @@ Future loadThemePluginSchemes(String capability) async { return ThemePluginSchemes(light: light, dark: dark); } -/// Build a `ThemeData` from a plugin-supplied `ColorScheme`, -/// keeping Studio's typography (Inter for UI, JetBrains Mono -/// for code) so a theme swap doesn't reflow text metrics. -/// Mirrors what `FaiTheme.light()` / `.dark()` do internally -/// but with the operator-chosen palette. -ThemeData themeDataFromScheme(ColorScheme scheme) { - return ThemeData( - useMaterial3: true, - colorScheme: scheme, - scaffoldBackgroundColor: scheme.surface, - canvasColor: scheme.surface, - cardColor: scheme.surfaceContainer, - dividerColor: scheme.outlineVariant, - textTheme: GoogleFonts.interTextTheme( - scheme.brightness == Brightness.dark - ? Typography.whiteCupertino - : Typography.blackCupertino, - ), - ); -} +/// Build a `ThemeData` from a plugin-supplied `ColorScheme`. +/// Delegates to [FaiTheme.fromColorScheme] so plugin-themed +/// builds and built-in themes share one structural shape — same +/// textTheme cascade, same component themes, same `inherit` +/// values on every TextStyle. Without that, Flutter's +/// AnimatedDefaultTextStyle lerp blows up when swapping between +/// the two paths. +ThemeData themeDataFromScheme(ColorScheme scheme) => + FaiTheme.fromColorScheme(scheme); /// Translate the plugin's 14 ARGB tokens into a `ColorScheme`. /// Order matches the plugin contract: diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index 7b2dd9f..53c118b 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -141,6 +141,16 @@ class FaiTheme { ); } + /// Build a Studio ThemeData around any [ColorScheme]. Used by + /// the built-in light/dark constructors and by + /// `themeDataFromScheme` when a Studio plugin supplies the + /// palette. Single entry-point so the plugin path and the + /// built-in path always produce structurally identical + /// ThemeData (same textTheme, same component themes); without + /// this, swapping between them at runtime trips Flutter's + /// TextStyle.lerp with mismatched `inherit` values. + static ThemeData fromColorScheme(ColorScheme scheme) => _build(scheme); + static ThemeData dark() { final scheme = ColorScheme( brightness: Brightness.dark,