own the open-in-clide command family (T-413)

/permissions, /status, /config, /mcp, /agents, /hooks, /memory, and
/help move from the TUI-only notice catalog to clide-owned commands
with real behavior:

- /permissions <mode> sets the mode over set_permission_mode; bare
  /permissions opens a picker in the interaction zone — the same card
  /model and /effort use (kPermissionModes, bypass last and explicit
  per T-181).
- /status → Claude sidebar Activity tab; /config, /mcp, /agents,
  /hooks → Config tab. The pane activates the claude.meta sidebar tab
  and publishes a meta.tab message; the sidebar subscribes and switches
  its sub-tab — the same MessageBus addressing `clide ui open` uses
  (D-6), so the CLI can drive it too.
- /memory opens the workspace CLAUDE.md via editor.open.
- /help renders a local summary card (clide-owned + advertised
  commands) — the CLI's TUI help doesn't exist headless.

The catalog keeps empty-hint entries for these tokens as safety nets if
they're ever removed from owned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 13:06:10 +02:00
co-authored by Claude Fable 5
parent 1bdd88f4ab
commit 52d90be730
9 changed files with 185 additions and 16 deletions
@@ -133,6 +133,29 @@ void main() {
expect(find.text('Claude environment not loaded.'), findsOneWidget);
});
testWidgets('a meta.tab message switches the sub-tab (T-413 slash navigation)', (tester) async {
await tester.pumpWidget(harness(f, sidebar(stats: stats)));
await tester.pumpAndSettle();
expect(find.text('TODAY'), findsOneWidget); // starts on Activity
// /config (and /mcp, /agents, /hooks) publish this from the Claude pane.
f.services.messages.publish('builtin.claude', 'meta.tab', {'tab': 'config'});
await tester.pump();
await tester.pump();
expect(find.text('Claude environment not loaded.'), findsOneWidget); // Config tab (no env in fixture)
f.services.messages.publish('builtin.claude', 'meta.tab', {'tab': 'activity'});
await tester.pump();
await tester.pump();
expect(find.text('TODAY'), findsOneWidget); // back on Activity
// An unknown tab name is ignored.
f.services.messages.publish('builtin.claude', 'meta.tab', {'tab': 'bogus'});
await tester.pump();
await tester.pump();
expect(find.text('TODAY'), findsOneWidget);
});
testWidgets('a team spawn auto-fronts the Team tab', (tester) async {
await tester.pumpWidget(harness(f, sidebar(stats: stats)));
await tester.pumpAndSettle();
+20 -5
View File
@@ -151,7 +151,21 @@ void main() {
});
test('owned beats everything', () {
for (final t in ['/clear', '/resume', '/fork', '/model opus', '/effort high']) {
for (final t in [
'/clear',
'/resume',
'/fork',
'/model opus',
'/effort high',
'/permissions plan',
'/status',
'/config',
'/mcp',
'/agents',
'/hooks',
'/memory',
'/help',
]) {
expect(routeSlashCommand(t, advertised: advertised), SlashRoute.owned, reason: t);
}
});
@@ -163,13 +177,14 @@ void main() {
});
test('a known TUI-only builtin routes unavailable', () {
for (final t in ['/status', '/permissions', '/doctor', '/login']) {
for (final t in ['/cost', '/doctor', '/login', '/rewind', '/output-style']) {
expect(routeSlashCommand(t, advertised: advertised), SlashRoute.unavailable, reason: t);
}
});
test('an advertised name shadows the TUI-only catalog (a skill named like a builtin forwards)', () {
expect(routeSlashCommand('/status', advertised: ['status']), SlashRoute.forward);
// 'cost' is in the catalog but not owned — advertising it wins.
expect(routeSlashCommand('/cost', advertised: ['cost']), SlashRoute.forward);
});
test('an unknown token forwards (stays literal text downstream)', () {
@@ -179,8 +194,8 @@ void main() {
group('tuiOnlyNotice (T-411)', () {
test('carries the clide-native pointer when the catalog has one', () {
final n = tuiOnlyNotice('status');
expect(n, contains('/status is a Claude Code TUI command'));
final n = tuiOnlyNotice('cost');
expect(n, contains('/cost is a Claude Code TUI command'));
expect(n, contains('Activity tab'));
});