Skip to content

feat: add auto-expand AI response groups setting#59

Merged
matt1398 merged 1 commit intomatt1398:mainfrom
proxikal:feat/auto-expand-ai-groups-setting
Feb 23, 2026
Merged

feat: add auto-expand AI response groups setting#59
matt1398 merged 1 commit intomatt1398:mainfrom
proxikal:feat/auto-expand-ai-groups-setting

Conversation

@proxikal
Copy link
Contributor

@proxikal proxikal commented Feb 21, 2026

Summary

  • Adds a persistent Auto-expand AI responses toggle in Settings → General (Appearance section)
  • When enabled, all AI response drawers open automatically when loading a transcript — no more clicking each chevron individually
  • Live sessions also auto-expand new AI groups as they arrive, preserving any groups the user manually collapsed earlier in the session
  • Setting survives restarts and /clear because it lives in the on-disk config (~/.claude/claude-devtools-config.json)

Changes

  • ConfigManager.ts / notifications.ts — add autoExpandAIGroups: boolean to GeneralConfig in both the main-process and shared type definitions
  • configValidation.ts — add autoExpandAIGroups to the IPC allowlist with boolean validation
  • sessionDetailSlice.ts — expand all groups on initial load; expand only newly arrived groups on live refresh (bug fixed: snapshot of old group IDs was taken after set() overwrote state, causing the diff to always be empty)
  • GeneralSection.tsx / SafeConfig / useSettingsHandlers.ts — wire up the toggle in Settings
  • test/main/ipc/configValidation.test.ts — two new cases covering the happy path and non-boolean rejection
  • CHANGELOG.md — entry under [Unreleased] > Added

Test plan

  • Enable the setting in Settings → General → Auto-expand AI responses
  • Open any existing transcript — all AI drawers should be expanded immediately
  • Send a new message in an active session — the new AI response drawer should expand automatically
  • Manually collapse a drawer mid-session, then receive another response — only the new drawer expands, the collapsed one stays collapsed
  • Disable the setting — drawers return to collapsed-by-default behavior
  • Restart the app with the setting on — setting persists

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a new "Expand AI responses by default" setting available in the General preferences section. When enabled, all AI response groups are automatically expanded upon opening session transcripts or when receiving new messages during live sessions. The preference persists across application restarts and is disabled by default.

@matt1398
Copy link
Owner

Windows CI is failing, can you take a look?

@proxikal
Copy link
Contributor Author

getting on my windows laptop now, and i'll give it a look.

@coderabbitai coderabbitai bot added bug Something isn't working documentation Improvements or additions to documentation feature request New feature or request labels Feb 22, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

This change adds a new autoExpandAIGroups boolean configuration setting that automatically expands all AI response groups in transcripts or during live sessions when enabled. The feature includes configuration schema updates, validation rules, UI toggle in settings, default value initialization, core auto-expansion logic, and test coverage.

Changes

Cohort / File(s) Summary
Configuration Types & Validation
src/shared/types/notifications.ts, src/main/ipc/configValidation.ts, src/main/services/infrastructure/ConfigManager.ts
Added autoExpandAIGroups: boolean field to AppConfig.general interface; implemented validation logic for the new boolean config key; initialized default value to false in DEFAULT_CONFIG.
Settings UI & Renderer State
src/renderer/components/settings/hooks/useSettingsConfig.ts, src/renderer/components/settings/hooks/useSettingsHandlers.ts, src/renderer/components/settings/sections/GeneralSection.tsx
Updated SafeConfig.general to include autoExpandAIGroups with default handling; added new UI toggle labeled "Expand AI responses by default"; broadened onGeneralToggle signature from specific keys to accept keyof AppConfig['general'].
Auto-expansion Feature
src/renderer/store/slices/sessionDetailSlice.ts
Implemented auto-expansion logic in fetchSessionDetail and refreshSessionInPlace to detect newly arrived AI groups and expand them when appConfig.general.autoExpandAIGroups is enabled, tracking previous group IDs to identify new arrivals.
Tests & Documentation
test/main/ipc/configValidation.test.ts, CHANGELOG.md
Added three validation test cases for general.autoExpandAIGroups (true toggle, false toggle, non-boolean rejection); documented setting in changelog under Unreleased additions.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/renderer/store/slices/sessionDetailSlice.ts (1)

589-610: Nit: Redundant reassignment on line 592.

oldGroupIds is immediately assigned from prevGroupIds, which is already a const in scope. You can use prevGroupIds directly in the filter predicate.

♻️ Simplified diff
       // Auto-expand newly arrived AI groups if the setting is enabled.
       // Uses prevGroupIds snapshotted before set() so the diff is accurate.
       if (get().appConfig?.general?.autoExpandAIGroups) {
-        const oldGroupIds = prevGroupIds;
         const newGroupIds = newConversation.items
           .filter(
             (item) =>
               item.type === 'ai' &&
-              !oldGroupIds.has((item as { type: 'ai'; group: { id: string } }).group.id)
+              !prevGroupIds.has((item as { type: 'ai'; group: { id: string } }).group.id)
           )
           .map((item) => (item as { type: 'ai'; group: { id: string } }).group.id);

The overall differential expansion logic is correct — it properly preserves user-collapsed groups while auto-expanding only newly arrived AI responses during live sessions. Based on learnings: "Use tabUISlice to maintain independent UI state per tab with expandedAIGroupIds... keyed by tabId to prevent UI state from one tab affecting another."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/store/slices/sessionDetailSlice.ts` around lines 589 - 610,
Remove the redundant local assignment oldGroupIds = prevGroupIds and use
prevGroupIds directly in the filter predicate; update the filter/map logic that
computes newGroupIds to reference prevGroupIds instead of oldGroupIds so the
behavior of the auto-expand block (get().appConfig?.general?.autoExpandAIGroups,
latestAllTabs, expandAIGroupForTab) is unchanged but without the unnecessary
reassignment.
src/renderer/components/settings/sections/GeneralSection.tsx (1)

30-30: Consider narrowing the type to only boolean keys of AppConfig['general'].

The current signature accepts any key from AppConfig['general'], but value is typed as boolean. Since AppConfig['general'] includes non-boolean properties (theme, defaultTab, claudeRootPath), this allows invalid calls like onGeneralToggle('theme', true) to pass type checking.

♻️ Proposed type-safe alternative
+type BooleanGeneralKeys = {
+  [K in keyof AppConfig['general']]: AppConfig['general'][K] extends boolean ? K : never;
+}[keyof AppConfig['general']];
+
 interface GeneralSectionProps {
   readonly safeConfig: SafeConfig;
   readonly saving: boolean;
-  readonly onGeneralToggle: (key: keyof AppConfig['general'], value: boolean) => void;
+  readonly onGeneralToggle: (key: BooleanGeneralKeys, value: boolean) => void;
   readonly onThemeChange: (value: 'dark' | 'light' | 'system') => void;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/settings/sections/GeneralSection.tsx` at line 30, The
onGeneralToggle signature accepts any key of AppConfig['general'] but the value
is always boolean, allowing invalid calls for non-boolean fields; narrow the key
type to only those keys whose AppConfig['general'] value is boolean by changing
the type of the first parameter to the extracted boolean keys (e.g. use a
conditional mapped type like { [K in keyof AppConfig['general']]:
AppConfig['general'][K] extends boolean ? K : never }[keyof
AppConfig['general']] or Extract<keyof AppConfig['general'], ... boolean
check>), keeping the value as boolean so onGeneralToggle only accepts boolean
properties of AppConfig['general'] (referencing onGeneralToggle and
AppConfig['general']).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/utils/sessionExporter.ts`:
- Around line 26-28: Replace the regex-based thousands separator in the return
expression (currently using
Math.round(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')) with a
loop-based formatter: compute rounded = Math.round(n), convert to string, handle
negative sign if present, then iterate characters from the end building a new
string and insert a comma every three digits before reversing/combining to
produce the final formatted string; update the return in sessionExporter.ts to
use this new loop-based routine so the function avoids the ReDoS-vulnerable
regex while preserving correct formatting and negatives.

---

Nitpick comments:
In `@src/renderer/components/settings/sections/GeneralSection.tsx`:
- Line 30: The onGeneralToggle signature accepts any key of AppConfig['general']
but the value is always boolean, allowing invalid calls for non-boolean fields;
narrow the key type to only those keys whose AppConfig['general'] value is
boolean by changing the type of the first parameter to the extracted boolean
keys (e.g. use a conditional mapped type like { [K in keyof
AppConfig['general']]: AppConfig['general'][K] extends boolean ? K : never
}[keyof AppConfig['general']] or Extract<keyof AppConfig['general'], ... boolean
check>), keeping the value as boolean so onGeneralToggle only accepts boolean
properties of AppConfig['general'] (referencing onGeneralToggle and
AppConfig['general']).

In `@src/renderer/store/slices/sessionDetailSlice.ts`:
- Around line 589-610: Remove the redundant local assignment oldGroupIds =
prevGroupIds and use prevGroupIds directly in the filter predicate; update the
filter/map logic that computes newGroupIds to reference prevGroupIds instead of
oldGroupIds so the behavior of the auto-expand block
(get().appConfig?.general?.autoExpandAIGroups, latestAllTabs,
expandAIGroupForTab) is unchanged but without the unnecessary reassignment.

@proxikal proxikal force-pushed the feat/auto-expand-ai-groups-setting branch 2 times, most recently from 5a3a86c to 93b515a Compare February 23, 2026 00:13
- Add toggle in settings to auto-expand AI response groups
- Auto-expand new AI groups on live session refresh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/renderer/store/slices/sessionDetailSlice.ts (2)

566-572: Consider using a type guard for cleaner type narrowing.

The filter already checks item.type === 'ai', but the subsequent map still requires a type assertion. A small type guard could simplify this pattern throughout the file.

♻️ Optional: Extract a type guard helper
+const isAIItem = (item: { type: string }): item is { type: 'ai'; group: { id: string } } =>
+  item.type === 'ai';
+
 const prevGroupIds = new Set(
   (latestState.conversation?.items ?? [])
-    .filter((item) => item.type === 'ai')
-    .map((item) => (item as { type: 'ai'; group: { id: string } }).group.id)
+    .filter(isAIItem)
+    .map((item) => item.group.id)
 );

As per coding guidelines: "Use TypeScript type guards with isXxx naming convention for runtime type checking."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/store/slices/sessionDetailSlice.ts` around lines 566 - 572,
Introduce a TypeScript type guard named isAIItem (e.g., function isAIItem(item):
item is { type: 'ai'; group: { id: string } }) and use it to replace the
filter+type assertion when building prevGroupIds: call
(latestState.conversation?.items ?? []).filter(isAIItem).map(i => i.group.id).
Update any other similar patterns in this file to use the same isAIItem guard so
you can remove the manual (item as ...) assertions and achieve cleaner type
narrowing.

592-613: Redundant variable assignment on line 595.

oldGroupIds is assigned directly from prevGroupIds without modification. Consider using prevGroupIds directly to reduce noise.

♻️ Suggested simplification
       // Auto-expand newly arrived AI groups if the setting is enabled.
       // Uses prevGroupIds snapshotted before set() so the diff is accurate.
       if (get().appConfig?.general?.autoExpandAIGroups) {
-        const oldGroupIds = prevGroupIds;
         const newGroupIds = newConversation.items
           .filter(
             (item) =>
               item.type === 'ai' &&
-              !oldGroupIds.has((item as { type: 'ai'; group: { id: string } }).group.id)
+              !prevGroupIds.has((item as { type: 'ai'; group: { id: string } }).group.id)
           )
           .map((item) => (item as { type: 'ai'; group: { id: string } }).group.id);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/store/slices/sessionDetailSlice.ts` around lines 592 - 613, The
assignment oldGroupIds = prevGroupIds is redundant; update the code to use
prevGroupIds directly in the filter (remove oldGroupIds variable) where
newGroupIds is computed, keeping the rest of the logic unchanged (references:
prevGroupIds, newConversation.items, latestAllTabs, expandAIGroupForTab,
get().appConfig.general.autoExpandAIGroups). Ensure you only replace oldGroupIds
with prevGroupIds in that block and remove the unused declaration.
CHANGELOG.md (1)

11-12: Remove extra blank line for consistency.

Two blank lines appear after the new entry, while the rest of the changelog uses single blank lines between entries.

🧹 Proposed fix
 - `general.autoExpandAIGroups` setting: automatically expands all AI response groups when opening a transcript or when new AI responses arrive in a live session. Defaults to off. Stored in the on-disk config so it persists across restarts.
-

 - Strict IPC input validation guards for project/session/subagent/search limits.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` around lines 11 - 12, Remove the extra blank line after the new
changelog entry in CHANGELOG.md so entries use a single blank line separator;
locate the latest release/entry block (the newly added entry) and delete one of
the two consecutive newline lines immediately following it to match the rest of
the file's formatting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Line 10: The CHANGELOG entry for the setting general.autoExpandAIGroups is
ambiguous about live session behavior; update the text so it clearly states that
when opening a transcript the setting expands all AI response groups, but in
live sessions it only auto-expands newly arriving AI response groups and does
not override groups the user has manually collapsed. Locate the
`general.autoExpandAIGroups` line in CHANGELOG.md and rewrite that sentence to
explicitly differentiate the two scenarios (transcript open vs live arrivals)
and note persistence in on-disk config.

---

Nitpick comments:
In `@CHANGELOG.md`:
- Around line 11-12: Remove the extra blank line after the new changelog entry
in CHANGELOG.md so entries use a single blank line separator; locate the latest
release/entry block (the newly added entry) and delete one of the two
consecutive newline lines immediately following it to match the rest of the
file's formatting.

In `@src/renderer/store/slices/sessionDetailSlice.ts`:
- Around line 566-572: Introduce a TypeScript type guard named isAIItem (e.g.,
function isAIItem(item): item is { type: 'ai'; group: { id: string } }) and use
it to replace the filter+type assertion when building prevGroupIds: call
(latestState.conversation?.items ?? []).filter(isAIItem).map(i => i.group.id).
Update any other similar patterns in this file to use the same isAIItem guard so
you can remove the manual (item as ...) assertions and achieve cleaner type
narrowing.
- Around line 592-613: The assignment oldGroupIds = prevGroupIds is redundant;
update the code to use prevGroupIds directly in the filter (remove oldGroupIds
variable) where newGroupIds is computed, keeping the rest of the logic unchanged
(references: prevGroupIds, newConversation.items, latestAllTabs,
expandAIGroupForTab, get().appConfig.general.autoExpandAIGroups). Ensure you
only replace oldGroupIds with prevGroupIds in that block and remove the unused
declaration.

## [Unreleased]

### Added
- `general.autoExpandAIGroups` setting: automatically expands all AI response groups when opening a transcript or when new AI responses arrive in a live session. Defaults to off. Stored in the on-disk config so it persists across restarts.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clarify the live session behavior for accuracy.

The current wording "automatically expands all AI response groups when opening a transcript or when new AI responses arrive in a live session" could be misinterpreted to mean ALL groups expand in both scenarios. According to the PR objectives, in live sessions only newly arriving AI groups expand while preserving groups the user manually collapsed.

Consider rewording for clarity:

📝 Proposed rewording
-- `general.autoExpandAIGroups` setting: automatically expands all AI response groups when opening a transcript or when new AI responses arrive in a live session. Defaults to off. Stored in the on-disk config so it persists across restarts.
+- `general.autoExpandAIGroups` setting: automatically expands all AI response groups when opening a transcript. In live sessions, newly arriving AI responses expand automatically while preserving manually collapsed groups. Defaults to off. Stored in the on-disk config so it persists across restarts.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- `general.autoExpandAIGroups` setting: automatically expands all AI response groups when opening a transcript or when new AI responses arrive in a live session. Defaults to off. Stored in the on-disk config so it persists across restarts.
- `general.autoExpandAIGroups` setting: automatically expands all AI response groups when opening a transcript. In live sessions, newly arriving AI responses expand automatically while preserving manually collapsed groups. Defaults to off. Stored in the on-disk config so it persists across restarts.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` at line 10, The CHANGELOG entry for the setting
general.autoExpandAIGroups is ambiguous about live session behavior; update the
text so it clearly states that when opening a transcript the setting expands all
AI response groups, but in live sessions it only auto-expands newly arriving AI
response groups and does not override groups the user has manually collapsed.
Locate the `general.autoExpandAIGroups` line in CHANGELOG.md and rewrite that
sentence to explicitly differentiate the two scenarios (transcript open vs live
arrivals) and note persistence in on-disk config.

@matt1398 matt1398 merged commit dc38c79 into matt1398:main Feb 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation feature request New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants