Skip to content

feat: unified tab system hardening, window chrome settings, file explorer icons#418

Merged
reachraza merged 3 commits intomainfrom
0.15.0-polish
Feb 20, 2026
Merged

feat: unified tab system hardening, window chrome settings, file explorer icons#418
reachraza merged 3 commits intomainfrom
0.15.0-polish

Conversation

@pedramamini
Copy link
Collaborator

@pedramamini pedramamini commented Feb 19, 2026

Summary

  • Unified tab system hardening — Extracted buildUnifiedTabs() and ensureInUnifiedTabOrder() into shared tabHelpers.ts, fixing ghost-tab bugs where tabs existed in state but were invisible in the TabBar due to missing unifiedTabOrder entries. All tab mutation paths (open, close, reopen, restore, transfer) now consistently maintain the invariant.
  • Window chrome settings — Added native OS titlebar toggle and auto-hide menu bar as user-configurable settings (Settings → Encore), with corresponding BrowserWindow integration and tests.
  • File explorer icons — Merged community contribution (feat: add material-style icon mapping in file explorer #360) adding Material-style file/folder icon mappings to the file explorer panel.
  • Documentation — Updated ARCHITECTURE.md and CLAUDE-PATTERNS.md with the unifiedTabOrder critical invariant, correct type names, and updated key files table.

Changes

Tab System (tabHelpers.ts, useTabHandlers.ts, tabStore.ts, App.tsx)

  • New shared utilities: buildUnifiedTabs(session), ensureInUnifiedTabOrder(session, ref)
  • handleOpenFileTab now repairs unifiedTabOrder when selecting an existing file tab
  • reopenUnifiedClosedTab repairs order for both AI and file tab duplicate paths
  • selectUnifiedTabs in tabStore replaced with shared buildUnifiedTabs
  • New AI tabs included in unifiedTabOrder on creation

Window Chrome (settingsStore.ts, useSettings.ts, SettingsModal.tsx, window-manager.ts)

  • useNativeTitleBar and autoHideMenuBar settings with IPC round-trip
  • BrowserWindow applies settings on creation
  • Settings UI in Encore section with restart-required notice

File Explorer Icons (FileExplorerPanel.tsx, theme.tsx)

  • Material-style icon color mappings for 50+ file extensions
  • Folder icon support with theme-aware colors

Tests

  • tabHelpers.test.ts — 180 lines covering buildUnifiedTabs and ensureInUnifiedTabOrder
  • window-manager.test.ts — native titlebar and autoHideMenuBar tests
  • utils.test.ts and FileExplorerPanel.test.tsx — supporting tests

Test plan

  • npm run lint passes
  • npm run test passes
  • Manual: verify tab operations (open, close, reopen, drag-reorder) maintain correct tab bar state
  • Manual: toggle native titlebar in Settings → Encore, restart, verify window chrome changes
  • Manual: verify file explorer shows colored icons for various file types

Summary by CodeRabbit

  • New Features

    • Window Chrome settings: toggles for native title bar and auto-hide menu bar in Settings.
  • Improvements

    • Unified tab model: consistent tab ordering, better handling of mixed AI/file tabs.
    • Session restoration now preserves file preview tabs alongside AI tabs.
    • UI adapts layout/title-bar rendering when native title bar is enabled.
  • Bug Fixes

    • Fewer invisible/orphaned tabs—tabs are repaired/kept in sync to ensure visibility.

Add native title bar and auto-hide menu bar toggles under Settings >
Display > Window Chrome (implements PR #300 and #293 as proper
settings). Both are read as early settings before window creation
and conditionally applied to BrowserWindow options.

Extract buildUnifiedTabs and ensureInUnifiedTabOrder into tabHelpers
as shared utilities, replacing duplicate implementations in
useTabHandlers and tabStore. Fix orphaned tab repair in
reopenUnifiedClosedTab and include file tabs in session restoration
fallback for unifiedTabOrder.
…nces

The unified tab system had no documentation about its critical invariant:
every tab must exist in both its data array and unifiedTabOrder, or it
becomes a ghost tab (content renders but no tab visible in TabBar).
Added the invariant, correct/wrong code examples, and documented the
shared utilities (buildUnifiedTabs, ensureInUnifiedTabOrder). Fixed
stale field name and key file references in ARCHITECTURE.md.
@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

Refactors tab management into public utilities (buildUnifiedTabs, ensureInUnifiedTabOrder) and hooks (useTabHandlers), promotes unifiedTabOrder as the canonical tab order, renames closedUnifiedTabHistory -> unifiedClosedTabHistory, and adds window chrome settings (useNativeTitleBar, autoHideMenuBar) across main/renderer, store, UI, and tests.

Changes

Cohort / File(s) Summary
Documentation & Architecture
ARCHITECTURE.md, CLAUDE-PATTERNS.md
Documented unified tab model with unifiedTabOrder as source of truth, added invariant/guidance, renamed closedUnifiedTabHistoryunifiedClosedTabHistory, and described new tab helper modules.
Tab Utilities & Tests
src/renderer/utils/tabHelpers.ts, src/__tests__/renderer/utils/tabHelpers.test.ts
Added buildUnifiedTabs(session) and ensureInUnifiedTabOrder(order, type, id) to construct/repair unified tab lists; tests added/extended to validate ordering and orphan handling.
Tab Hooks & Store Integration
src/renderer/hooks/tabs/useTabHandlers.ts, src/renderer/stores/tabStore.ts, src/renderer/hooks/session/useSessionRestoration.ts
Replaced inline unified-tab construction with buildUnifiedTabs, used ensureInUnifiedTabOrder when opening/restoring tabs, and updated session restoration to include file preview tabs in unifiedTabOrder.
Settings Store & Persistence
src/renderer/stores/settingsStore.ts, src/renderer/hooks/settings/useSettings.ts
Added useNativeTitleBar and autoHideMenuBar to settings store, persistence, and hook return shape with corresponding setters.
UI: Settings & App
src/renderer/components/SettingsModal.tsx, src/renderer/App.tsx
Settings modal UI added toggles for window chrome; App.tsx consumes useNativeTitleBar for title-bar rendering and appends new tabs to unifiedTabOrder when creating AI tabs.
Main Process: Window Manager & Bootstrap
src/main/app-lifecycle/window-manager.ts, src/main/index.ts, src/main/stores/utils.ts
WindowManager and bootstrap flow accept useNativeTitleBar and autoHideMenuBar; BrowserWindow options conditional on these flags; early settings include the new fields.
Tests: Main & Stores
src/__tests__/main/app-lifecycle/window-manager.test.ts, src/__tests__/main/stores/utils.test.ts
Updated tests to expect new boolean fields useNativeTitleBar and autoHideMenuBar in bootstrap/WindowManager options and store shapes.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant User
participant App
participant useTabHandlers
participant tabHelpers
participant tabStore
User->>App: request open file tab
App->>useTabHandlers: handleOpenFileTab(path)
useTabHandlers->>tabStore: read activeSession / existing tabs
useTabHandlers->>tabHelpers: ensureInUnifiedTabOrder(order,'file',id)
tabHelpers-->>useTabHandlers: newOrder
useTabHandlers->>tabStore: update session (filePreviewTabs + unifiedTabOrder)
tabStore-->>App: updated unified tabs (selectUnifiedTabs via buildUnifiedTabs)
App->>User: render updated TabBar

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main changes: unified tab system improvements, window chrome settings, and file explorer icons. It is concise and specific enough for scanning PR history.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 0.15.0-polish

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

@greptile-apps
Copy link

greptile-apps bot commented Feb 19, 2026

Greptile Summary

This PR systematically fixes ghost-tab bugs in the unified tab system by ensuring unifiedTabOrder consistency across all tab operations.

Key improvements:

  • Extracted buildUnifiedTabs() and ensureInUnifiedTabOrder() into shared tabHelpers.ts to centralize tab list construction and orphan repair
  • Fixed critical paths: handleOpenFileTab now repairs existing file tabs, reopenUnifiedClosedTab handles both AI and file duplicate detection, new AI tabs are added to unifiedTabOrder on creation
  • Added native OS titlebar toggle and auto-hide menu bar settings with proper IPC round-trip and BrowserWindow integration
  • Updated documentation with the unifiedTabOrder critical invariant and correct architectural guidance
  • Added 180 lines of comprehensive tests covering edge cases like orphaned tabs and duplicate detection

Implementation quality:

  • Follows DRY principle by consolidating duplicate 37-line buildUnifiedTabs logic from tabStore.ts and useTabHandlers.ts
  • Zero-cost repair via ensureInUnifiedTabOrder (returns same reference when no fix needed)
  • Includes safety net: buildUnifiedTabs appends orphaned tabs to prevent data loss
  • Window chrome settings properly read as early settings before window creation

Confidence Score: 5/5

  • Safe to merge - comprehensive testing, systematic fix for known bugs, no breaking changes
  • Score reflects thorough implementation with defensive programming (orphan safety net), comprehensive test coverage (180+ new test lines covering edge cases), proper abstraction (shared helpers), and excellent documentation. All tab mutation paths now maintain the invariant consistently.
  • No files require special attention - all changes are well-tested and follow established patterns

Important Files Changed

Filename Overview
src/renderer/utils/tabHelpers.ts Added buildUnifiedTabs() and ensureInUnifiedTabOrder() helpers to centralize unified tab list construction and repair orphaned tabs
src/renderer/hooks/tabs/useTabHandlers.ts Replaced inline buildUnifiedTabs logic with shared helper; added ensureInUnifiedTabOrder() to handleOpenFileTab for ghost-tab prevention
src/renderer/stores/tabStore.ts Replaced 37-line inline buildUnifiedTabs implementation in selectUnifiedTabs with shared helper from tabHelpers.ts
src/renderer/App.tsx Added unifiedTabOrder update when creating new AI tabs; conditionally hide custom title bar when useNativeTitleBar is enabled
src/renderer/stores/settingsStore.ts Added useNativeTitleBar and autoHideMenuBar settings with setters and loading logic
src/renderer/components/SettingsModal.tsx Added "Window Chrome" section with toggles for native title bar and auto-hide menu bar in Encore settings
src/main/app-lifecycle/window-manager.ts Added useNativeTitleBar and autoHideMenuBar parameters; conditionally set titleBarStyle and autoHideMenuBar on BrowserWindow
src/tests/renderer/utils/tabHelpers.test.ts Added 180 lines of tests for buildUnifiedTabs and ensureInUnifiedTabOrder, including orphaned tab repair scenarios
ARCHITECTURE.md Documented unifiedTabOrder critical invariant and added key files table with tabHelpers.ts, useTabHandlers.ts, tabStore.ts
CLAUDE-PATTERNS.md Added comprehensive "Multi-Tab Agents & Unified Tab System" section explaining unifiedTabOrder invariant with code examples

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Start[Tab Operation: Open/Close/Reopen/Restore] --> CheckType{Operation Type?}
    
    CheckType -->|New AI Tab| CreateAI[createTab in tabHelpers.ts]
    CreateAI --> AddAIToOrder[Add tab to aiTabs AND unifiedTabOrder]
    AddAIToOrder --> Success[Tab visible in TabBar]
    
    CheckType -->|Open File Tab| OpenFile[handleOpenFileTab in useTabHandlers.ts]
    OpenFile --> FileExists{Tab exists?}
    FileExists -->|Yes| Repair[ensureInUnifiedTabOrder]
    FileExists -->|No| CreateFile[Create new FilePreviewTab]
    CreateFile --> AddFileToOrder[Add to filePreviewTabs AND unifiedTabOrder]
    Repair --> Success
    AddFileToOrder --> Success
    
    CheckType -->|Reopen Closed Tab| Reopen[reopenUnifiedClosedTab in tabHelpers.ts]
    Reopen --> DuplicateCheck{Duplicate exists?}
    DuplicateCheck -->|Yes| RepairDup[ensureInUnifiedTabOrder for existing tab]
    DuplicateCheck -->|No| RestoreTab[Restore tab data with new ID]
    RestoreTab --> InsertOrder[Insert at original position in unifiedTabOrder]
    RepairDup --> Success
    InsertOrder --> Success
    
    CheckType -->|Session Restore| SessionRestore[useSessionRestoration.ts]
    SessionRestore --> BuildOrder[Build unifiedTabOrder from AI + file tabs]
    BuildOrder --> Success
    
    Success --> Render[buildUnifiedTabs builds final tab list]
    Render --> TabBar[TabBar renders all tabs correctly]
    
    TabBar --> Orphan{Orphaned tabs?}
    Orphan -->|Yes| Append[buildUnifiedTabs appends as safety net]
    Orphan -->|No| Done[All tabs visible]
    Append --> Done
Loading

Last reviewed commit: 2a46999

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/__tests__/main/app-lifecycle/window-manager.test.ts (1)

144-146: Tests updated correctly, but coverage for enabled window chrome settings is missing.

All tests pass useNativeTitleBar: false and autoHideMenuBar: false. Consider adding test cases that verify:

  1. useNativeTitleBar: true omits titleBarStyle from BrowserWindow options
  2. autoHideMenuBar: true includes autoHideMenuBar: true in options

This would ensure the conditional logic in createWindow is exercised for both branches.

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

In `@src/__tests__/main/app-lifecycle/window-manager.test.ts` around lines 144 -
146, Add test cases for createWindow to exercise both branches of the window
chrome conditionals: create one test that calls createWindow with
useNativeTitleBar: true and asserts the constructed BrowserWindow options do not
contain titleBarStyle, and another test that calls createWindow with
autoHideMenuBar: true and asserts the BrowserWindow options include
autoHideMenuBar: true; locate where current tests stub BrowserWindow
construction (the code that inspects the options passed when createWindow is
invoked) and extend it to cover these two scenarios so the conditional logic
around useNativeTitleBar and autoHideMenuBar is asserted.
src/renderer/utils/tabHelpers.ts (1)

27-61: Consider defensive checks for consistency with file patterns.

Other functions in this file (e.g., closeTab, reopenClosedTab) guard against undefined session properties. For a "hardening" PR, adding similar guards here would provide robustness against partial session state during initialization or corruption recovery.

🛡️ Optional defensive check
 export function buildUnifiedTabs(session: Session): UnifiedTab[] {
+	if (!session) return [];
 	const { aiTabs, filePreviewTabs, unifiedTabOrder } = session;
 
-	const aiTabMap = new Map(aiTabs.map((tab) => [tab.id, tab]));
-	const fileTabMap = new Map(filePreviewTabs.map((tab) => [tab.id, tab]));
+	const aiTabMap = new Map((aiTabs || []).map((tab) => [tab.id, tab]));
+	const fileTabMap = new Map((filePreviewTabs || []).map((tab) => [tab.id, tab]));
 
 	const result: UnifiedTab[] = [];
 
 	// Follow unified order for tabs that have entries
-	for (const ref of unifiedTabOrder) {
+	for (const ref of unifiedTabOrder || []) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/utils/tabHelpers.ts` around lines 27 - 61, buildUnifiedTabs
assumes session.aiTabs, session.filePreviewTabs, and session.unifiedTabOrder are
defined; add defensive guards at the start of buildUnifiedTabs to default any of
those undefined/null properties to empty arrays (e.g., const aiTabs =
session.aiTabs ?? []; const filePreviewTabs = session.filePreviewTabs ?? [];
const unifiedTabOrder = session.unifiedTabOrder ?? [];), then proceed using
those local variables when creating aiTabMap, fileTabMap and iterating the order
so the function tolerates partial/corrupt session state without throwing.
🤖 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/components/SettingsModal.tsx`:
- Around line 2559-2651: The two custom toggle buttons in SettingsModal (the
button toggling useNativeTitleBar using setUseNativeTitleBar/useNativeTitleBar
and the button toggling autoHideMenuBar using
setAutoHideMenuBar/autoHideMenuBar) need explicit focus handling: add
tabIndex={0} (or tabIndex={-1} if you intend them not focusable) and include the
"outline-none" class on each button so they follow renderer focus guidelines; if
either toggle should auto-focus, add a ref callback like ref={(el) =>
el?.focus()} to that button. Ensure you update both the native title bar button
and the auto-hide menu bar button.

---

Nitpick comments:
In `@src/__tests__/main/app-lifecycle/window-manager.test.ts`:
- Around line 144-146: Add test cases for createWindow to exercise both branches
of the window chrome conditionals: create one test that calls createWindow with
useNativeTitleBar: true and asserts the constructed BrowserWindow options do not
contain titleBarStyle, and another test that calls createWindow with
autoHideMenuBar: true and asserts the BrowserWindow options include
autoHideMenuBar: true; locate where current tests stub BrowserWindow
construction (the code that inspects the options passed when createWindow is
invoked) and extend it to cover these two scenarios so the conditional logic
around useNativeTitleBar and autoHideMenuBar is asserted.

In `@src/renderer/utils/tabHelpers.ts`:
- Around line 27-61: buildUnifiedTabs assumes session.aiTabs,
session.filePreviewTabs, and session.unifiedTabOrder are defined; add defensive
guards at the start of buildUnifiedTabs to default any of those undefined/null
properties to empty arrays (e.g., const aiTabs = session.aiTabs ?? []; const
filePreviewTabs = session.filePreviewTabs ?? []; const unifiedTabOrder =
session.unifiedTabOrder ?? [];), then proceed using those local variables when
creating aiTabMap, fileTabMap and iterating the order so the function tolerates
partial/corrupt session state without throwing.

… tab guards

Add tabIndex and outline-none to Window Chrome toggle buttons for keyboard
accessibility. Add defensive null guards to buildUnifiedTabs for robustness
during partial session state. Add test coverage for useNativeTitleBar and
autoHideMenuBar enabled branches.
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

🤖 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/tabHelpers.ts`:
- Around line 68-76: ensureInUnifiedTabOrder lacks a null/undefined guard for
its unifiedTabOrder parameter and will throw if called with undefined (unlike
buildUnifiedTabs). Update ensureInUnifiedTabOrder to defensively handle
unifiedTabOrder being null/undefined: treat undefined as an empty array, then
perform the existing existence check and append logic (so callers like
session.unifiedTabOrder can be passed safely). Keep the function signature but
internally coalesce unifiedTabOrder to [] before using .some and returning the
new array.

Comment on lines +68 to +76
export function ensureInUnifiedTabOrder(
unifiedTabOrder: UnifiedTabRef[],
type: 'ai' | 'file',
id: string
): UnifiedTabRef[] {
const exists = unifiedTabOrder.some((ref) => ref.type === type && ref.id === id);
if (exists) return unifiedTabOrder;
return [...unifiedTabOrder, { type, id }];
}
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

Missing null guard for unifiedTabOrder parameter.

Unlike buildUnifiedTabs which defensively handles undefined arrays, this function will throw a TypeError if unifiedTabOrder is null/undefined. The call sites at lines 767 and 836 pass session.unifiedTabOrder directly, which could be undefined during migration or partial state scenarios.

🛡️ Proposed fix to add defensive null handling
 export function ensureInUnifiedTabOrder(
 	unifiedTabOrder: UnifiedTabRef[],
 	type: 'ai' | 'file',
 	id: string
 ): UnifiedTabRef[] {
+	const order = unifiedTabOrder || [];
-	const exists = unifiedTabOrder.some((ref) => ref.type === type && ref.id === id);
-	if (exists) return unifiedTabOrder;
-	return [...unifiedTabOrder, { type, id }];
+	const exists = order.some((ref) => ref.type === type && ref.id === id);
+	if (exists) return order;
+	return [...order, { type, id }];
 }
📝 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
export function ensureInUnifiedTabOrder(
unifiedTabOrder: UnifiedTabRef[],
type: 'ai' | 'file',
id: string
): UnifiedTabRef[] {
const exists = unifiedTabOrder.some((ref) => ref.type === type && ref.id === id);
if (exists) return unifiedTabOrder;
return [...unifiedTabOrder, { type, id }];
}
export function ensureInUnifiedTabOrder(
unifiedTabOrder: UnifiedTabRef[],
type: 'ai' | 'file',
id: string
): UnifiedTabRef[] {
const order = unifiedTabOrder || [];
const exists = order.some((ref) => ref.type === type && ref.id === id);
if (exists) return order;
return [...order, { type, id }];
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/utils/tabHelpers.ts` around lines 68 - 76,
ensureInUnifiedTabOrder lacks a null/undefined guard for its unifiedTabOrder
parameter and will throw if called with undefined (unlike buildUnifiedTabs).
Update ensureInUnifiedTabOrder to defensively handle unifiedTabOrder being
null/undefined: treat undefined as an empty array, then perform the existing
existence check and append logic (so callers like session.unifiedTabOrder can be
passed safely). Keep the function signature but internally coalesce
unifiedTabOrder to [] before using .some and returning the new array.

@reachraza reachraza merged commit 2f6c80d into main Feb 20, 2026
1 check passed
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

Comments