diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 0f32c3d..21dca6f 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -194,6 +194,9 @@ "storeSectionScreenshots": "Screenshots", "storeSectionDocumentation": "Dokumentation", "storeSectionSource": "Quelle", + "storeSectionPermissions": "Deklarierte Berechtigungen", + "storeSectionDirectory": "Modul-Verzeichnis", + "storeSectionPermissionsNone": "(keine — reines Berechnungsmodul)", "storeInstallingButton": "Installiere…", "storeNotInstallable": "Nicht installierbar", "storeNotInstallableTooltip": "Status „{status}\" — Installations-Pfad noch nicht verdrahtet.", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9144c22..4dfee2c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -195,6 +195,9 @@ "storeSectionScreenshots": "Screenshots", "storeSectionDocumentation": "Documentation", "storeSectionSource": "Source", + "storeSectionPermissions": "Declared permissions", + "storeSectionDirectory": "Module directory", + "storeSectionPermissionsNone": "(none — pure-computation module)", "storeInstallingButton": "Installing…", "storeNotInstallable": "Not installable", "storeNotInstallableTooltip": "Status \"{status}\" — install path not yet wired.", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 43b8cc2..2266e9a 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1004,6 +1004,24 @@ abstract class AppLocalizations { /// **'Source'** String get storeSectionSource; + /// No description provided for @storeSectionPermissions. + /// + /// In en, this message translates to: + /// **'Declared permissions'** + String get storeSectionPermissions; + + /// No description provided for @storeSectionDirectory. + /// + /// In en, this message translates to: + /// **'Module directory'** + String get storeSectionDirectory; + + /// No description provided for @storeSectionPermissionsNone. + /// + /// In en, this message translates to: + /// **'(none — pure-computation module)'** + String get storeSectionPermissionsNone; + /// No description provided for @storeInstallingButton. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index d9a471a..48d8ff4 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -522,6 +522,15 @@ class AppLocalizationsDe extends AppLocalizations { @override String get storeSectionSource => 'Quelle'; + @override + String get storeSectionPermissions => 'Deklarierte Berechtigungen'; + + @override + String get storeSectionDirectory => 'Modul-Verzeichnis'; + + @override + String get storeSectionPermissionsNone => '(keine — reines Berechnungsmodul)'; + @override String get storeInstallingButton => 'Installiere…'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 92b2376..be45698 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -540,6 +540,15 @@ class AppLocalizationsEn extends AppLocalizations { @override String get storeSectionSource => 'Source'; + @override + String get storeSectionPermissions => 'Declared permissions'; + + @override + String get storeSectionDirectory => 'Module directory'; + + @override + String get storeSectionPermissionsNone => '(none — pure-computation module)'; + @override String get storeInstallingButton => 'Installing…'; diff --git a/lib/main.dart b/lib/main.dart index b9715e9..e06083d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,7 +16,6 @@ import 'pages/approvals.dart'; import 'pages/audit.dart'; import 'pages/doctor.dart'; import 'pages/flows.dart'; -import 'pages/modules.dart'; import 'pages/store.dart'; import 'theme/theme.dart'; import 'theme/tokens.dart'; @@ -26,7 +25,7 @@ import 'widgets/widgets.dart'; /// Studio's own build version. Bump on every UI commit so the /// running app self-identifies — visible in the sidebar header /// and quick-glance proof that you're seeing the current build. -const String kStudioVersion = '0.34.0'; +const String kStudioVersion = '0.35.0'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -146,12 +145,11 @@ class _StudioShellState extends State { selectedIcon: Icons.health_and_safety, page: DoctorPage(), ), - _NavPage( - id: 'modules', - icon: Icons.extension_outlined, - selectedIcon: Icons.extension, - page: ModulesPage(), - ), + // Modules tab dropped in v0.35.0 — installed-module info + // (permissions, on-disk path, uninstall) now lives inside + // the Store's detail sheet, and the Store with the + // "Installed" filter covers the listing role. Cmd+K still + // opens FaiModuleSheet for quick info. _NavPage( id: 'store', icon: Icons.storefront_outlined, diff --git a/lib/pages/store.dart b/lib/pages/store.dart index d435127..69c856d 100644 --- a/lib/pages/store.dart +++ b/lib/pages/store.dart @@ -651,6 +651,21 @@ String _categoryDisplayName(BuildContext ctx, String wire) { /// Same idea for the `published` / `alpha` / `planned` status /// values. Operators read "stable / experimental / coming /// soon"; the wire keeps the strict enum it has always had. +/// Map a permission string (`net:api.example.com`, +/// `fs.read:/etc/fai`, `env:OPENAI_API_KEY`, …) to a glyph the +/// operator can read at a glance. Mirrors the table that used +/// to live in `fai_module_sheet.dart` so the Store detail-sheet +/// can render permissions in the same shape after the Modules +/// page was dropped. +IconData _iconForPermission(String permission) { + if (permission.startsWith('net:')) return Icons.public; + if (permission.startsWith('fs.read:')) return Icons.folder_open; + if (permission.startsWith('fs.write:')) return Icons.edit_note; + if (permission.startsWith('env:')) return Icons.terminal; + if (permission.startsWith('hub:')) return Icons.shield_outlined; + return Icons.lock_outline; +} + String _statusDisplayName(BuildContext ctx, String wire) { final l = AppLocalizations.of(ctx)!; switch (wire) { @@ -1796,6 +1811,31 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> { String? _toast; Future<({String errorKind, String text, String sourceUrl})>? _docsFuture; bool _docsLoaded = false; + /// Live module-info for installed entries: declared + /// permissions and on-disk directory. Pulled lazily so + /// not-installed entries don't run an unnecessary RPC. Stays + /// null when the fetch fails — the corresponding sections + /// just hide rather than show a spinner inside a hover sheet. + ModuleDetail? _moduleDetail; + + @override + void initState() { + super.initState(); + if (widget.item.installed) { + _loadModuleDetail(); + } + } + + Future _loadModuleDetail() async { + try { + final detail = + await HubService.instance.moduleInfo(widget.item.name); + if (!mounted) return; + setState(() => _moduleDetail = detail); + } catch (_) { + // Silent — sections stay hidden, the sheet still works. + } + } Future _install() async { setState(() { @@ -2058,6 +2098,64 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> { ), const SizedBox(height: FaiSpace.lg), ], + // Live-from-hub sections for installed + // modules. Folded in here in v0.35.0 when the + // standalone Modules page got dropped — its + // unique value (declared permissions, on-disk + // directory) belongs in the same surface + // operators already use for everything else + // about a module. + if (item.installed && _moduleDetail != null) ...[ + _SectionHeader(l.storeSectionPermissions), + const SizedBox(height: FaiSpace.sm), + if (_moduleDetail!.permissions.isEmpty) + Text( + l.storeSectionPermissionsNone, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + fontStyle: FontStyle.italic, + ), + ) + else + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final p in _moduleDetail!.permissions) + Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + children: [ + Icon( + _iconForPermission(p), + size: 14, + color: + theme.colorScheme.onSurfaceVariant, + ), + const SizedBox(width: FaiSpace.sm), + SelectableText( + p, + style: FaiTheme.mono( + size: 12, + color: theme.colorScheme.onSurface, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: FaiSpace.lg), + _SectionHeader(l.storeSectionDirectory), + const SizedBox(height: FaiSpace.sm), + SelectableText( + _moduleDetail!.directory, + style: FaiTheme.mono( + size: 11, + color: theme.colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: FaiSpace.lg), + ], if (item.screenshotUrls.isNotEmpty) ...[ _SectionHeader(l.storeSectionScreenshots), const SizedBox(height: FaiSpace.sm), diff --git a/pubspec.yaml b/pubspec.yaml index e957f27..8e4496b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: fai_studio description: "F∆I Studio — desktop GUI for the F∆I hub" publish_to: 'none' -version: 0.34.0 +version: 0.35.0 environment: sdk: ^3.11.0-200.1.beta diff --git a/test/widget_test.dart b/test/widget_test.dart index f9f9cc5..904b079 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -17,7 +17,8 @@ void main() { expect(find.text('F∆I Studio'), findsOneWidget); expect(find.text('Doctor'), findsWidgets); - expect(find.text('Modules'), findsWidgets); + // Modules tab dropped in v0.35.0 — info now lives inside + // the Store detail sheet. expect(find.text('Store'), findsOneWidget); expect(find.text('Flows'), findsOneWidget); expect(find.text('Audit'), findsOneWidget);