Skip to content

Add settings sync mode and collapsible subagent sessions#531

Open
ildunari wants to merge 1 commit intoDimillian:mainfrom
ildunari:config_direction
Open

Add settings sync mode and collapsible subagent sessions#531
ildunari wants to merge 1 commit intoDimillian:mainfrom
ildunari:config_direction

Conversation

@ildunari
Copy link

@ildunari ildunari commented Feb 28, 2026

Summary

  • add settings sync mode (app_authoritative default, bidirectional optional)
  • keep app-authoritative behavior by default while supporting bidirectional reconciliation in settings core
  • add sidebar controls for subagent session visibility and per-parent collapse/expand
  • wire new settings through frontend + backend models and UI

Testing

  • npm run typecheck
  • npm run test
  • cd src-tauri && cargo check

Notes

  • commit: 22aaebc
  • preserves existing behavior unless sync mode is explicitly switched to bidirectional

- add app_authoritative vs bidirectional settings sync mode and bidirectional merge behavior in backend settings core
- add showSubagentSessions setting and sidebar collapse controls for subagent thread children
- wire new settings through frontend models/layout and extend tests for settings/thread row behavior

Co-authored-by: Codex <noreply@openai.com>
Copilot AI review requested due to automatic review settings February 28, 2026 20:42
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new settings synchronization mode (default app-authoritative, optional bidirectional reconciliation) and introduces sidebar controls to hide sub-agent sessions globally and collapse/expand sub-agent session trees per parent thread.

Changes:

  • Add syncMode (app_authoritative | bidirectional) to settings models and implement bidirectional merge behavior in the Tauri settings core.
  • Add showSubagentSessions setting and wire it through layout/sidebar to hide sub-agent threads.
  • Add per-parent collapse/expand UI for sub-agent sessions (stateful in the sidebar) and update thread row rendering + styles.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/types.ts Adds SettingsSyncMode and new AppSettings fields (showSubagentSessions, syncMode).
src/styles/sidebar.css Adds styles for the per-thread collapse/expand toggle.
src/features/settings/hooks/useAppSettings.ts Adds defaults + normalization for syncMode and default showSubagentSessions.
src/features/settings/hooks/useAppSettings.test.ts Verifies new defaults (syncMode, showSubagentSessions).
src/features/settings/components/sections/SettingsDisplaySection.tsx Adds UI for toggling sub-agent visibility and selecting settings sync mode.
src/features/settings/components/SettingsView.test.tsx Tests new settings UI interactions.
src/features/layout/hooks/layoutNodes/types.ts Plumbs showSubagentSessions through layout node options.
src/features/layout/hooks/layoutNodes/buildPrimaryNodes.tsx Passes showSubagentSessions into the primary layout.
src/features/app/hooks/useThreadRows.ts Adds support for hiding sub-agent rows and per-parent collapsing; exposes hasChildren.
src/features/app/hooks/useThreadRows.test.tsx Adds coverage for hide + collapse behavior.
src/features/app/components/WorktreeSection.tsx Passes show/hide + collapse options to thread row building and thread list rendering.
src/features/app/components/WorktreeSection.test.tsx Updates test render props for new required showSubagentSessions.
src/features/app/components/ThreadRow.tsx Adds collapse toggle button and chevron icon when a thread has children.
src/features/app/components/ThreadList.tsx Threads through hasChildren, collapse state, and toggle handler.
src/features/app/components/ThreadList.test.tsx Updates fixtures for hasChildren.
src/features/app/components/Sidebar.tsx Maintains per-workspace collapsed thread sets; passes show/hide + collapse through pinned + workspace lists.
src/features/app/components/Sidebar.test.tsx Adds test ensuring sub-agent rows can be hidden.
src/features/app/components/PinnedThreadList.tsx Threads through hasChildren, collapse state, and toggle handler for pinned rows.
src/features/app/components/PinnedThreadList.test.tsx Updates fixtures for hasChildren.
src/App.tsx Wires showSubagentSessions to layout and adds phone UX improvements (new chat action + codex tab restore).
src-tauri/src/types.rs Adds backend showSubagentSessions + SettingsSyncMode to Rust settings model with defaults.
src-tauri/src/shared/settings_core.rs Implements sync-mode gated reconciliation and bidirectional merge behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +54 to +56
if let Ok(disk_settings) = read_settings(settings_path) {
next = merge_bidirectional_settings(previous.clone(), next, disk_settings)?;
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

In bidirectional sync mode, a failure to read the on-disk settings is silently ignored (if let Ok(disk_settings) = read_settings(...)). That means a JSON parse error or other read failure can cause the app to proceed and then overwrite the file with write_settings, potentially discarding a user's manual edits without surfacing any error. Consider propagating the read_settings error (or at least aborting the write) when sync_mode is Bidirectional so users can fix the file instead of losing changes.

Suggested change
if let Ok(disk_settings) = read_settings(settings_path) {
next = merge_bidirectional_settings(previous.clone(), next, disk_settings)?;
}
let disk_settings = read_settings(settings_path)
.map_err(|e| format!("Failed to read settings for bidirectional sync: {}", e))?;
next = merge_bidirectional_settings(previous.clone(), next, disk_settings)?;

Copilot uses AI. Check for mistakes.
Comment on lines 18 to 41
pub(crate) async fn get_app_settings_core(app_settings: &Mutex<AppSettings>) -> AppSettings {
let mut settings = app_settings.lock().await.clone();
if let Ok(Some(collaboration_modes_enabled)) = codex_config::read_collaboration_modes_enabled()
{
settings.collaboration_modes_enabled = collaboration_modes_enabled;
}
if let Ok(Some(steer_enabled)) = codex_config::read_steer_enabled() {
settings.steer_enabled = steer_enabled;
}
if let Ok(Some(unified_exec_enabled)) = codex_config::read_unified_exec_enabled() {
settings.unified_exec_enabled = unified_exec_enabled;
}
if let Ok(Some(apps_enabled)) = codex_config::read_apps_enabled() {
settings.experimental_apps_enabled = apps_enabled;
}
if let Ok(personality) = codex_config::read_personality() {
settings.personality = personality
.as_deref()
.and_then(normalize_personality)
.unwrap_or("friendly")
.to_string();
if matches!(settings.sync_mode, SettingsSyncMode::Bidirectional) {
if let Ok(Some(collaboration_modes_enabled)) = codex_config::read_collaboration_modes_enabled()
{
settings.collaboration_modes_enabled = collaboration_modes_enabled;
}
if let Ok(Some(steer_enabled)) = codex_config::read_steer_enabled() {
settings.steer_enabled = steer_enabled;
}
if let Ok(Some(unified_exec_enabled)) = codex_config::read_unified_exec_enabled() {
settings.unified_exec_enabled = unified_exec_enabled;
}
if let Ok(Some(apps_enabled)) = codex_config::read_apps_enabled() {
settings.experimental_apps_enabled = apps_enabled;
}
if let Ok(personality) = codex_config::read_personality() {
settings.personality = personality
.as_deref()
.and_then(normalize_personality)
.unwrap_or("friendly")
.to_string();
}
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The PR description says existing behavior is preserved unless sync mode is switched to bidirectional, but get_app_settings_core now only pulls values from codex_config when sync_mode is Bidirectional. With the new default (AppAuthoritative), manual edits to the managed Codex config fields will no longer be reflected on reads. If that behavior change is intentional, it may need to be called out explicitly; otherwise consider still reconciling these fields on read in app-authoritative mode (or revising the default).

Copilot uses AI. Check for mistakes.
if let Ok(disk_settings) = read_settings(settings_path) {
next = merge_bidirectional_settings(previous.clone(), next, disk_settings)?;
}
reconcile_managed_config_fields(&previous, &mut next);
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

merge_bidirectional_settings can also overwrite syncMode itself (since it merges all keys when incoming == previous). Because the if matches!(next.sync_mode, Bidirectional) check happens before the merge, reconcile_managed_config_fields will still run even if the merge changes next.sync_mode to AppAuthoritative based on disk. Consider either (a) excluding syncMode from the merge so the mode is always app-driven, or (b) re-checking next.sync_mode after the merge before running bidirectional-only reconciliation logic.

Suggested change
reconcile_managed_config_fields(&previous, &mut next);
if matches!(next.sync_mode, SettingsSyncMode::Bidirectional) {
reconcile_managed_config_fields(&previous, &mut next);
}

Copilot uses AI. Check for mistakes.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22aaebcc46

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +50 to +54
let previous = app_settings.lock().await.clone();
let mut next = settings;

if matches!(next.sync_mode, SettingsSyncMode::Bidirectional) {
if let Ok(disk_settings) = read_settings(settings_path) {

Choose a reason for hiding this comment

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

P2 Badge Rebase bidirectional merge on fresh settings state

This reconciliation path uses previous from the in-memory mutex as the merge baseline, but in bidirectional mode get_app_settings_core overlays Codex-managed values on a clone and does not persist them back into that mutex. If Codex config changes while the settings UI is open, untouched managed fields can be misclassified as user-edited (incoming != previous), which skips reconcile_managed_config_fields and causes the later writes to overwrite newer external config edits. Recomputing the baseline from current persisted/synced values before merge would prevent these lost updates.

Useful? React with 👍 / 👎.

Comment on lines +96 to +99
onClick={(event) => {
event.stopPropagation();
onToggleThreadChildren?.(workspaceId, thread.id);
}}

Choose a reason for hiding this comment

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

P2 Badge Prevent chevron keypress from selecting the thread row

The new collapse chevron stops propagation for mouse clicks, but the parent row still handles Enter/Space in onKeyDown. When a keyboard user activates the chevron, the key event bubbles to the row and also triggers thread selection, so collapsing/expanding unexpectedly opens that thread. The chevron needs keyboard event propagation handling (or the row key handler should ignore descendant targets).

Useful? React with 👍 / 👎.

@ildunari
Copy link
Author

Context from user testing on iOS/macOS sidebar UX:\n\n- Spawned subagent sessions currently always populate under the parent thread, which can create visual noise in large runs.\n- There was no obvious collapse affordance at the parent row level, so users couldn’t quickly hide/show child sessions in place.\n\nThis PR addresses that by:\n1) adding a global setting to show/hide subagent sessions in the sidebar\n2) adding per-parent collapse/expand controls (caret) for subagent children\n3) preserving existing behavior by default unless the user opts in to different settings sync behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants