From ea670fd077196d2fb46c9b9478e675e8ff0325fb Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 17 Jun 2026 19:58:11 +0200 Subject: [PATCH] =?UTF-8?q?feat(settings):=20Claude=20category=20=E2=80=94?= =?UTF-8?q?=20new-session=20defaults=20(T-457)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Settings → Claude category sets per-user defaults for NEW sessions: model, effort, and permission mode (generic schema selects persisting app.claude.default*). The pane reads them at spawn — effort flows through the existing --effort flag (SpawnSpec.effort); model and permission mode are sent as control requests right after a fresh (non-resume, non-fork) session starts. 'default'/unset values are no-ops, leaving the CLI's own defaults. The optional "settings changed — apply to current sessions?" prompt is filed as T-470: it needs a custom control (a generic select can't prompt), so it's a clean follow-up on the T-452 escape hatch. Tests: defaultEffortFlag sentinel handling; the category contributes the three default fields. The thin pane-side applySessionDefaults is covered by the gate. Co-Authored-By: Claude Opus 4.8 (1M context) --- .pql/changelog/ticket_history/2026-06.sql | 3 ++ .pql/changelog/tickets/2026-06.sql | 4 ++ CHANGELOG.md | 4 ++ lib/builtin/claude/src/claude_pane.dart | 11 +++++ lib/builtin/claude/src/extension.dart | 45 +++++++++++++++++++ lib/builtin/claude/src/session_defaults.dart | 29 ++++++++++++ .../claude/extension_commands_test.dart | 15 +++++++ .../builtin/claude/session_defaults_test.dart | 36 +++++++++++++++ 8 files changed, 147 insertions(+) create mode 100644 lib/builtin/claude/src/session_defaults.dart create mode 100644 test/builtin/claude/session_defaults_test.dart diff --git a/.pql/changelog/ticket_history/2026-06.sql b/.pql/changelog/ticket_history/2026-06.sql index ebe32416..50099a06 100644 --- a/.pql/changelog/ticket_history/2026-06.sql +++ b/.pql/changelog/ticket_history/2026-06.sql @@ -5915,3 +5915,6 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15TY946689VZK3MNABSMDW', 'status', 'in_progress', 'done', NULL, '2026-06-17 11:43:37', '2026-06-17 11:43:37.615', '2026-06-17 11:43:37.615', NULL, 'a2f67644a293f12da9b0cdf28dc79055', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15W7SBVAD3QR5NVE67PKWR', 'status', 'backlog', 'in_progress', NULL, '2026-06-17 12:08:37', '2026-06-17 12:08:37.084', '2026-06-17 12:08:37.084', NULL, '56dfbd21ec4ea2add7d34c1008d3690b', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15W7SBVAD3QR5NVE67PKWR', 'status', 'in_progress', 'ready', NULL, '2026-06-17 12:09:28', '2026-06-17 12:09:28.013', '2026-06-17 12:09:28.013', NULL, '04a42a6bb8bed47fda6da86038dfb15e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15W7SBVAD3QR5NVE67PKWR', 'status', 'ready', 'in_progress', NULL, '2026-06-17 12:11:21', '2026-06-17 12:11:21.874', '2026-06-17 12:11:21.874', NULL, '20c4f05dc5ffc8a29a0e6c2f94dfaf4b', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDB1VFADV4QK1YG29T658T8C', 'description', NULL, 'Follow-up to T-457. The Claude settings category sets new-session defaults (model/effort/permission) via generic schema selects — those persist + seed NEW sessions but don''t touch running ones. Add the ''Claude settings changes detected — apply to current sessions?'' prompt: on a default change with ≥1 running session, confirm via DialogRouter, then apply to all sessions through the existing builtin.claude/command channel (/model, /effort, /permissions). Needs a custom control (the generic select can''t prompt), so swap the Claude category''s selects for a custom-control widget per D-? / the T-452 escape hatch.', NULL, '2026-06-17 12:21:31', '2026-06-17 12:21:31.456', '2026-06-17 12:21:31.456', NULL, '799b77b6ec7862c856957d948326cffd', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15W7SBVAD3QR5NVE67PKWR', 'status', 'in_progress', 'done', NULL, '2026-06-17 12:21:39', '2026-06-17 12:21:39.311', '2026-06-17 12:21:39.311', NULL, 'dd983e8a5ccfae1b0dea46a8b5e78249', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-06.sql b/.pql/changelog/tickets/2026-06.sql index ea245b30..954dd809 100644 --- a/.pql/changelog/tickets/2026-06.sql +++ b/.pql/changelog/tickets/2026-06.sql @@ -7183,3 +7183,7 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15TY946689VZK3MNABSMDW', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Extensions settings category', 'List installed extensions and surface each extension''s registered settings (ext..* — the extension-owned config pattern from ui-design theme.md); per-extension enable/disable + configure. Reuses the field renderer; extensions register their schema the same way core subsystems do.', 'done', 'medium', NULL, NULL, NULL, '2026-06-16 13:20:41', '2026-06-17 11:43:37.614', NULL, '09f569e8ad465363d95821413ff7d027', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15W7SBVAD3QR5NVE67PKWR', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Claude settings category — mirror the sidebar Config panel', 'Mirror lib/builtin/claude/src/meta_sidebar/config_tab.dart in the settings modal: SETTINGS controls (model / effort / permission mode / output style via SettingControlRow publishing the slash command on the live session, D-6 / T-414) plus carded config lists (Skills / Agents / Commands / Hooks / Permissions / MCP Servers) that open their .md (T-183). Reuse the data the sidebar already loads (ClaudeConfig). Wireframe: docs/design/wireframes/settings/settings-claude.png.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-16 13:20:52', '2026-06-17 12:08:37.084', NULL, 'e7198c66868b8d602168083dcd40dda9', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15W7SBVAD3QR5NVE67PKWR', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Claude settings category — mirror the sidebar Config panel', 'Mirror lib/builtin/claude/src/meta_sidebar/config_tab.dart in the settings modal: SETTINGS controls (model / effort / permission mode / output style via SettingControlRow publishing the slash command on the live session, D-6 / T-414) plus carded config lists (Skills / Agents / Commands / Hooks / Permissions / MCP Servers) that open their .md (T-183). Reuse the data the sidebar already loads (ClaudeConfig). Wireframe: docs/design/wireframes/settings/settings-claude.png.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-16 13:20:52', '2026-06-17 12:09:28.013', NULL, '8c8ad599dcbe3b500f710b5b03b0c1fb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15W7SBVAD3QR5NVE67PKWR', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Claude settings category — mirror the sidebar Config panel', 'Mirror lib/builtin/claude/src/meta_sidebar/config_tab.dart in the settings modal: SETTINGS controls (model / effort / permission mode / output style via SettingControlRow publishing the slash command on the live session, D-6 / T-414) plus carded config lists (Skills / Agents / Commands / Hooks / Permissions / MCP Servers) that open their .md (T-183). Reuse the data the sidebar already loads (ClaudeConfig). Wireframe: docs/design/wireframes/settings/settings-claude.png.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-16 13:20:52', '2026-06-17 12:11:21.874', NULL, 'aa269d123343566d0025e9eee2662ab4', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDB1VFADV4QK1YG29T658T8C', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Claude defaults: ''apply to current sessions?'' prompt on change', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-17 12:21:23.411', '2026-06-17 12:21:23.411', NULL, '924eef1841e09a2a49c1c39d9bfc1331', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FDB1VFADV4QK1YG29T658T8C', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Claude defaults: ''apply to current sessions?'' prompt on change', 'Follow-up to T-457. The Claude settings category sets new-session defaults (model/effort/permission) via generic schema selects — those persist + seed NEW sessions but don''t touch running ones. Add the ''Claude settings changes detected — apply to current sessions?'' prompt: on a default change with ≥1 running session, confirm via DialogRouter, then apply to all sessions through the existing builtin.claude/command channel (/model, /effort, /permissions). Needs a custom control (the generic select can''t prompt), so swap the Claude category''s selects for a custom-control widget per D-? / the T-452 escape hatch.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-17 12:21:23.411', '2026-06-17 12:21:31.456', NULL, '61f297cc5f01d6f6e43e1fa64efd4875', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FD15W7SBVAD3QR5NVE67PKWR', 'task', '06FD07ECS3GK2V7Z2WYYPJHJYC', 'Claude settings category — mirror the sidebar Config panel', 'Mirror lib/builtin/claude/src/meta_sidebar/config_tab.dart in the settings modal: SETTINGS controls (model / effort / permission mode / output style via SettingControlRow publishing the slash command on the live session, D-6 / T-414) plus carded config lists (Skills / Agents / Commands / Hooks / Permissions / MCP Servers) that open their .md (T-183). Reuse the data the sidebar already loads (ClaudeConfig). Wireframe: docs/design/wireframes/settings/settings-claude.png.', 'done', 'medium', NULL, NULL, NULL, '2026-06-16 13:20:52', '2026-06-17 12:21:39.311', NULL, '2f3f27095398a0a0e0240b2e0eac34c2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); diff --git a/CHANGELOG.md b/CHANGELOG.md index a5f4d716..f0329af7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,10 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. toggling extensions arrives with third-party (Lua) support; built-ins stay always-on for now. (T-456) +- **Settings → Claude category.** New-session defaults — model, effort, and + permission mode — seed fresh sessions (effort via `--effort` at spawn; + model and permission applied right after start). (T-457) + ### Changed - **Theme picker relabelled "Theme…".** The ⌘K theme picker's command title diff --git a/lib/builtin/claude/src/claude_pane.dart b/lib/builtin/claude/src/claude_pane.dart index 4532d4cb..ac86bff3 100644 --- a/lib/builtin/claude/src/claude_pane.dart +++ b/lib/builtin/claude/src/claude_pane.dart @@ -16,6 +16,7 @@ import 'conversation_controller.dart'; import 'conversation_view.dart'; import 'model_picker_card.dart'; import 'permission_mode_control.dart'; +import 'session_defaults.dart'; import 'prompt_card.dart'; import 'session_index.dart'; import 'session_naming.dart'; @@ -294,6 +295,11 @@ class _ClaudePaneState extends State { Future _spawn() async { if (!mounted) return; + // New sessions inherit the user's Claude defaults (T-457): effort flows + // through --effort below; model + permission mode are applied post-spawn. + final settings = ClideKernel.of(context).settings; + _effort ??= defaultEffortFlag(settings); + var isNewSession = false; final ipc = _ipc(); if (ipc == null || !ipc.isConnected) { setState(() => _error = 'Daemon not connected.'); @@ -370,11 +376,16 @@ class _ClaudePaneState extends State { return; } if (!mounted) return; + isNewSession = !resume; setState(() => _statusLine = resume ? 'resumed · $_sessionId' : 'new session · $_sessionId'); } _session = managed.session; _conversation = managed.conversation; + // A brand-new session (not a resume or fork) starts on the user's default + // model + permission mode (T-457). Sent as control requests; the CLI + // applies them after init. 'default'/unset values are no-ops. + if (isNewSession) applySessionDefaults(managed.session, settings); // The wire never reports effort — record what this session was spawned // with so the status line / sidebar can show it (T-412). if (_effort != null) managed.session.noteEffort(_effort!); diff --git a/lib/builtin/claude/src/extension.dart b/lib/builtin/claude/src/extension.dart index 2a5595ce..05341979 100644 --- a/lib/builtin/claude/src/extension.dart +++ b/lib/builtin/claude/src/extension.dart @@ -10,7 +10,9 @@ import 'package:clide/builtin/claude/src/claude_session_host.dart'; import 'package:clide/builtin/claude/src/session_orchestrator.dart'; import 'package:clide/builtin/claude/src/pane_context_status.dart'; import 'package:clide/builtin/claude/src/claude_meta_sidebar.dart'; +import 'package:clide/builtin/claude/src/session_defaults.dart'; import 'package:clide/builtin/claude/src/session_index.dart'; +import 'package:clide/builtin/claude/src/stream_json_session.dart' show kEffortLevels, kFallbackModels, kPermissionModes; import 'package:clide/builtin/claude/src/session_storage.dart'; import 'package:clide/builtin/claude/src/ticket_pick_up.dart'; import 'package:clide/builtin/claude/src/transcript_reader.dart' show ImageMessage; @@ -132,6 +134,49 @@ class ClaudeExtension extends ClideExtension { ], ), ), + // Claude settings category (T-457) — defaults applied to NEW sessions + // (the pane reads these keys at spawn). Effort flows through --effort; + // model + permission mode are sent as control requests post-spawn. + SettingsCategoryContribution( + id: 'claude', + category: SettingsCategory( + id: 'claude', + title: 'Claude', + iconName: 'sparkle', + priority: 40, + sections: [ + SettingsSection( + label: 'New session defaults', + fields: [ + SettingsField( + key: kDefaultModelKey, + kind: SettingsFieldKind.select, + label: 'Model', + help: 'Model for new sessions.', + defaultValue: 'default', + options: [for (final m in kFallbackModels) SettingsOption(value: m.value, label: m.displayName)], + ), + SettingsField( + key: kDefaultEffortKey, + kind: SettingsFieldKind.select, + label: 'Effort', + help: 'Reasoning effort for new sessions (applied via --effort at spawn).', + defaultValue: 'high', + options: [for (final l in kEffortLevels) SettingsOption(value: l.value, label: l.displayName)], + ), + SettingsField( + key: kDefaultPermissionModeKey, + kind: SettingsFieldKind.select, + label: 'Permission mode', + help: 'Starting permission mode for new sessions.', + defaultValue: 'default', + options: [for (final p in kPermissionModes) SettingsOption(value: p.value, label: p.displayName)], + ), + ], + ), + ], + ), + ), // T-171: agent roster controls (D-6 CLI/UI parity). // Usage: clide claude.agent.show CommandContribution( diff --git a/lib/builtin/claude/src/session_defaults.dart b/lib/builtin/claude/src/session_defaults.dart new file mode 100644 index 00000000..d26c0d64 --- /dev/null +++ b/lib/builtin/claude/src/session_defaults.dart @@ -0,0 +1,29 @@ +import 'package:clide/builtin/claude/src/stream_json_session.dart'; +import 'package:clide/kernel/kernel.dart'; + +/// Per-user defaults applied to NEW Claude sessions (T-457). Set from the +/// Settings → Claude category; read by the pane when it spawns a session. +/// Effort is applied at spawn (the `--effort` flag — there's no live +/// set_effort); model and permission mode are sent as control requests right +/// after the session starts. +const String kDefaultModelKey = 'app.claude.defaultModel'; +const String kDefaultEffortKey = 'app.claude.defaultEffort'; +const String kDefaultPermissionModeKey = 'app.claude.defaultPermissionMode'; + +/// The default effort for new sessions, or null to let the CLI's own default +/// stand. `'default'` is treated as "no override" too. +String? defaultEffortFlag(SettingsStore settings) { + final v = settings.get(kDefaultEffortKey); + if (v == null || v.isEmpty || v == 'default') return null; + return v; +} + +/// Apply the model + permission-mode defaults to a freshly-spawned [session]. +/// A null/empty/`'default'` value is a no-op — the CLI's own default stands. +/// (Effort is handled at spawn via [defaultEffortFlag], not here.) +void applySessionDefaults(StreamJsonSession session, SettingsStore settings) { + final model = settings.get(kDefaultModelKey); + if (model != null && model.isNotEmpty && model != 'default') session.setModel(model); + final perm = settings.get(kDefaultPermissionModeKey); + if (perm != null && perm.isNotEmpty && perm != 'default') session.setPermissionMode(perm); +} diff --git a/test/builtin/claude/extension_commands_test.dart b/test/builtin/claude/extension_commands_test.dart index 338ba0c6..caf68ab5 100644 --- a/test/builtin/claude/extension_commands_test.dart +++ b/test/builtin/claude/extension_commands_test.dart @@ -6,6 +6,7 @@ library; import 'package:clide/builtin/claude/src/activity_cluster.dart' show kActivityFoldLevelKey; +import 'package:clide/builtin/claude/src/session_defaults.dart' show kDefaultEffortKey, kDefaultModelKey, kDefaultPermissionModeKey; import 'package:clide/builtin/claude/src/claude_config.dart' show activeClaudeConfig; import 'package:clide/builtin/claude/src/extension.dart'; import 'package:clide/builtin/claude/src/session_orchestrator.dart' show activeSessionOrchestrator; @@ -157,4 +158,18 @@ void main() { expect(field.options.map((o) => o.value), containsAll(['none', 'tools', 'thinking', 'everything'])); }); }); + + group('Claude settings category (T-457)', () { + final category = ext.contributions.whereType().firstWhere((c) => c.id == 'claude').category; + + test('contributes new-session default fields for model, effort, permission', () { + expect(category.title, 'Claude'); + final keys = category.sections.expand((s) => s.fields).map((f) => f.key).toSet(); + expect(keys, containsAll([kDefaultModelKey, kDefaultEffortKey, kDefaultPermissionModeKey])); + for (final f in category.sections.expand((s) => s.fields)) { + expect(f.kind, SettingsFieldKind.select); + expect(f.options, isNotEmpty); + } + }); + }); } diff --git a/test/builtin/claude/session_defaults_test.dart b/test/builtin/claude/session_defaults_test.dart new file mode 100644 index 00000000..45087643 --- /dev/null +++ b/test/builtin/claude/session_defaults_test.dart @@ -0,0 +1,36 @@ +import 'dart:io'; + +import 'package:clide/builtin/claude/src/session_defaults.dart'; +import 'package:clide/kernel/kernel.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + late Directory appDir; + late SettingsStore settings; + + setUp(() async { + appDir = await Directory.systemTemp.createTemp('clide_cd_'); + settings = SettingsStore(appDir: appDir); + await settings.load(); + }); + tearDown(() async { + settings.dispose(); + await appDir.delete(recursive: true); + }); + + group('defaultEffortFlag (T-457)', () { + test('null when unset — let the CLI default stand', () { + expect(defaultEffortFlag(settings), isNull); + }); + + test("null for the 'default' sentinel", () async { + await settings.set(kDefaultEffortKey, 'default'); + expect(defaultEffortFlag(settings), isNull); + }); + + test('returns a concrete level', () async { + await settings.set(kDefaultEffortKey, 'high'); + expect(defaultEffortFlag(settings), 'high'); + }); + }); +}