feat(studio): Cmd+K palette, recent-activity panel, icon/screenshot rendering (v0.21.0)

Three sweeps in a single bundle.

Universal command palette (⌘K / ctrl+K):
- Modal at top-of-window with a single search input. Indexes
  pages, installed modules, store entries, saved flows on
  open; filters client-side as the operator types.
- Keyboard nav (↑/↓ + Enter), grouped sections, hover-to-
  highlight. Hit selection navigates / opens the right
  surface in one keystroke. Closes with Esc.
- Designed to mirror the VSCode / Linear / 1Password
  ergonomics — gives non-CLI operators a "jump anywhere"
  affordance that scales with the number of installed
  modules.

Modules page recent-activity panel:
- Top-of-page strip lists the last 10 install / uninstall
  events from the audit log, color-coded by direction.
  Hidden when no relevant events exist (fresh installs).
  Same locale-unambiguous timestamp format used in audit.

Store detail sheet now renders icons + screenshots:
- `_ModuleIcon` widget loads the explicit `iconUrl` if
  provided, falls back to the category glyph on missing
  URL or load failure (no broken-image rectangle).
- Screenshots strip below the description: 320×200 tiles,
  horizontally-scrollable, click opens full-size via the
  OS handler. Placeholder card on load failure.

StoreItem extended with `iconUrl`, `screenshotUrls`,
`docsUrl` so the new content paths through unchanged.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-07 21:51:47 +02:00
parent fb7352d182
commit 11b0ac97a1
7 changed files with 803 additions and 30 deletions

View file

@ -19,12 +19,13 @@ import 'pages/modules.dart';
import 'pages/store.dart';
import 'theme/theme.dart';
import 'theme/tokens.dart';
import 'widgets/fai_search_palette.dart';
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.20.1';
const String kStudioVersion = '0.21.0';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
@ -189,6 +190,33 @@ class _StudioShellState extends State<StudioShell> {
super.dispose();
}
void _openSearchPalette() {
final hits = <FaiSearchHit>[
for (var i = 0; i < _pages.length; i++)
FaiSearchHit(
label: _pages[i].label,
hint: 'page · ⌘${i + 1}',
icon: _pages[i].icon,
group: 'Pages',
onSelect: () {
Navigator.of(context).pop();
setState(() => _selectedIndex = i);
},
),
FaiSearchHit(
label: 'Settings',
hint: 'hub endpoint, channels, system AI · ⌘;',
icon: Icons.settings_outlined,
group: 'Pages',
onSelect: () {
Navigator.of(context).pop();
FaiSettingsDialog.show(context);
},
),
];
FaiSearchPalette.show(context, staticHits: hits);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@ -212,6 +240,12 @@ class _StudioShellState extends State<StudioShell> {
const _OpenSettingsIntent(),
const SingleActivator(LogicalKeyboardKey.comma, meta: true):
const _OpenSettingsIntent(),
// Universal command palette. Mirrors Cmd+K from VSCode /
// Linear / 1Password operators jump anywhere by typing.
const SingleActivator(LogicalKeyboardKey.keyK, meta: true):
const _OpenSearchIntent(),
const SingleActivator(LogicalKeyboardKey.keyK, control: true):
const _OpenSearchIntent(),
},
child: Actions(
actions: <Type, Action<Intent>>{
@ -227,6 +261,12 @@ class _StudioShellState extends State<StudioShell> {
return null;
},
),
_OpenSearchIntent: CallbackAction<_OpenSearchIntent>(
onInvoke: (_) {
_openSearchPalette();
return null;
},
),
},
child: Focus(
autofocus: true,
@ -285,6 +325,10 @@ class _OpenSettingsIntent extends Intent {
const _OpenSettingsIntent();
}
class _OpenSearchIntent extends Intent {
const _OpenSearchIntent();
}
class _Sidebar extends StatelessWidget {
final int selectedIndex;
final ValueChanged<int> onSelect;