Skip to content

refactor(ui): centralise state via AppContext, fix code quality issues#118

Merged
Mehdi-Bl merged 4 commits into
mainfrom
fix/ui-code-organisation-review
Feb 14, 2026
Merged

refactor(ui): centralise state via AppContext, fix code quality issues#118
Mehdi-Bl merged 4 commits into
mainfrom
fix/ui-code-organisation-review

Conversation

@Mehdi-Bl

@Mehdi-Bl Mehdi-Bl commented Feb 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • App.tsx rewritten from 715-line god component to thin shell + AppContext provider (179 lines)
  • ConfigTab consolidated from 19 useState hooks to single useReducer
  • FileTree selection converted from Array.includes() to Set.has() for O(1) lookups with batch select-all
  • Tab state preserved via CSS hidden class instead of unmount/remount
  • ErrorBanner replaces all alert() calls with dismissible inline UI
  • Shared Spinner component extracted; @heroicons/react dependency removed
  • Window globals (switchToTab, refreshDirectoryTree) and 500ms localStorage polling eliminated
  • SonarQube all 4 reported issues fixed (S3776 cognitive complexity, S7776 Set usage, S7758 codePointAt, S4325 type assertion)

Changes

Area Before After
App.tsx 715 lines, mixed concerns 179 lines, thin shell
State management localStorage polling + window globals React Context + useReducer
Selection perf O(n) Array.includes per check O(1) Set.has + batch handler
Error UX alert() dialogs Inline dismissible banner
Tab switching Unmounts components (loses state) CSS hidden (preserves state)
SonarQube issues 4 open 0 open

Files Changed (16)

New files:

  • src/renderer/context/AppContext.tsx — centralised state provider
  • src/renderer/components/icons/Spinner.tsx — shared spinner component
  • docs/plan/ui_fixes.md — three-agent validated implementation plan

Rewritten:

  • src/renderer/components/App.tsx — thin shell with ErrorBanner
  • src/renderer/components/ConfigTab.tsx — useReducer, context integration
  • src/renderer/components/FileTree.tsx — Set-based selection, batch select, role=treeitem
  • src/renderer/components/SourceTab.tsx — Set props, configContent prop, Spinner

Updated:

  • src/main/index.ts — SonarQube fixes (cognitive complexity, Set, codePointAt, type assertion)
  • src/renderer/components/ProcessedTab.tsx — Spinner component
  • src/types/global.d.ts — removed window global declarations
  • tests/unit/components/*.test.tsx — updated for Set-based props, context mocks, ErrorBanner

Test plan

  • Unit tests: 318/318 pass
  • Stress tests: 3/3 pass (IPC latency benchmarks, no degradation)
  • E2E tests: 4/4 pass (Playwright)
  • Lint: 0 warnings (ESLint + Prettier + markdownlint)
  • SonarQube: 0 open issues
  • Build: webpack + electron-builder success
  • Manual smoke test: verify tab switching preserves state
  • Manual smoke test: verify ErrorBanner displays and dismisses

Summary by Sourcery

Refactor the renderer UI to centralize application state in a new React context, improve file selection performance and accessibility, and replace blocking alerts with inline error handling while resolving SonarQube findings.

New Features:

  • Introduce an AppContext provider and hook to manage shared UI state, configuration, file selections, and processing flow across tabs.
  • Add an inline ErrorBanner component to display and dismiss application errors without using modal alerts.
  • Add a reusable Spinner icon component for consistent loading indicators across the UI.

Bug Fixes:

  • Ensure tab switching no longer drops tab-local UI state by hiding inactive tabs with CSS instead of unmounting components.
  • Resolve all reported SonarQube issues by replacing includes-based membership checks with Sets, using codePointAt for slash detection, avoiding unnecessary type assertions, and reducing cognitive complexity in tree walking logic.
  • Prevent invalid file and folder selections outside the authorized root directory during batch and individual selection operations.

Enhancements:

  • Simplify App.tsx into a thin shell that delegates state and business logic to the AppContext provider while keeping tabs mounted to preserve local state.
  • Refactor ConfigTab to use a consolidated useReducer-based form state, integrate it with AppContext for root path selection and tab switching, and prevent circular auto-save updates.
  • Update SourceTab and FileTree to use Set-based file and folder selections with optional batch selection for more efficient select-all behavior and improved keyboard/a11y semantics.
  • Refine directory tree building logic in the main process with extracted helpers, safer symlink and boundary checks, and shared sorting.
  • Remove reliance on window globals and localStorage polling for cross-component coordination, using React state and context instead.

Build:

  • Remove the unused @heroicons/react dependency from the project to slim the bundle and dependency graph.

Documentation:

  • Add a UI and code organization fix plan documenting the architectural issues, phased refactor strategy, and success criteria for the renderer UI overhaul.

Tests:

  • Update unit tests to work with Set-based selection props, new context-driven behavior, FileTree accessibility changes, and ErrorBanner-driven error handling.
  • Extend FileTree tests to cover the new batch select-all handler and role-based queries for tree items.
  • Adjust ConfigTab and SourceTab tests to use context-driven directory selection and explicit configContent props.

Chores:

  • Remove obsolete window global type declarations from global.d.ts now that tab switching and tree refresh are handled via context.

Summary by CodeRabbit

  • New Features

    • Batch file and folder selection for faster multi-item operations.
    • App-wide provider: persistent context driving UI state and actions.
  • Improvements

    • Reusable spinner for consistent loading indicators.
    • Better keyboard & ARIA support in the file tree; selection counts updated.
    • In-UI error banner replaces alert popups; tabs preserve internal state.
  • Chores

    • Removed unused icon library dependency.
    • Updated Vault agent token/paths configuration.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@sourcery-ai

sourcery-ai Bot commented Feb 14, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors the renderer UI to centralize shared state in a new AppContext, improve selection performance with Set-based state and batch operations, preserve tab state via CSS-based visibility, replace alert dialogs with an inline ErrorBanner, extract a shared Spinner component, and address several SonarQube and code-quality issues in both renderer and main process code.

Sequence diagram for SourceTab analyze flow via AppContext

sequenceDiagram
  actor User
  participant SourceTab
  participant AppContext
  participant ElectronAPI as electronAPI
  participant MainProcess
  participant ErrorBanner

  User->>SourceTab: click Process selected files
  SourceTab->>AppContext: handleAnalyze()
  AppContext->>AppContext: collect selectedFiles as array
  AppContext->>AppContext: validate rootPath and selectedFiles
  alt no rootPath or no valid files
    AppContext->>AppContext: set appError
    AppContext-->>SourceTab: return undefined
    SourceTab->>ErrorBanner: renders error from context
  else valid input
    AppContext->>ElectronAPI: analyzeRepository({rootPath, configContent, selectedFiles})
    ElectronAPI->>MainProcess: ipcMain.handle analyzeRepository
    MainProcess-->>ElectronAPI: AnalyzeRepositoryResult
    ElectronAPI-->>AppContext: AnalyzeRepositoryResult
    AppContext->>AppContext: update analysisResultRef
    AppContext->>AppContext: derive ProcessingOptions from configContent
    AppContext->>ElectronAPI: processRepository({rootPath, filesInfo, options})
    ElectronAPI->>MainProcess: ipcMain.handle processRepository
    MainProcess-->>ElectronAPI: ProcessRepositoryResult
    ElectronAPI-->>AppContext: ProcessRepositoryResult
    AppContext->>AppContext: set processedResult
    AppContext->>AppContext: set activeTab = processed
    AppContext-->>SourceTab: resolve Promise
    SourceTab->>ProcessedTab: rendered due to activeTab
  end
Loading

Sequence diagram for FileTree batch select-all using Set-based state

sequenceDiagram
  actor User
  participant FileTree
  participant AppContext

  User->>FileTree: toggle Select all checkbox
  FileTree->>FileTree: compute allPaths = {files[], folders[]}
  alt onBatchSelect provided
    FileTree->>AppContext: onBatchSelect(files, folders, isSelected)
    alt isSelected = true
      AppContext->>AppContext: add valid files to selectedFiles Set
      AppContext->>AppContext: add valid folders to selectedFolders Set
    else isSelected = false
      AppContext->>AppContext: delete files from selectedFiles Set
      AppContext->>AppContext: delete folders from selectedFolders Set
    end
  else fallback per-item
    loop each file in files
      FileTree->>AppContext: handleFileSelect(filePath, isSelected)
    end
    loop each folder in folders
      FileTree->>AppContext: handleFolderSelect(folderPath, isSelected)
    end
  end
  AppContext-->>FileTree: updated selectedFiles, selectedFolders
  FileTree->>FileTree: recompute select-all checked state via Set.size
Loading

Class diagram for centralised AppContext state and UI components

classDiagram
  class AppProvider {
    +AppProviderProps props
    -TabId activeTab
    -string rootPath
    -DirectoryTreeItem[] directoryTree
    -Set~string~ selectedFiles
    -Set~string~ selectedFolders
    -AnalyzeRepositoryResult? analysisResultRef
    -ProcessRepositoryResult? processedResult
    -ProcessingOptions processingOptions
    -string configContent
    -AppError? appError
    +AppProvider(props)
  }

  class AppContext {
    <<context>>
    +TabId activeTab
    +string rootPath
    +DirectoryTreeItem[] directoryTree
    +Set~string~ selectedFiles
    +Set~string~ selectedFolders
    +ProcessRepositoryResult? processedResult
    +string configContent
    +ProcessingOptions processingOptions
    +AppError? appError
    +switchTab(tab: TabId) void
    +selectDirectory() Promise~void~
    +refreshDirectoryTree() Promise~void~
    +updateConfig(config: string) void
    +handleFileSelect(filePath: string, isSelected: boolean) void
    +handleFolderSelect(folderPath: string, isSelected: boolean) void
    +handleBatchSelect(files: string[], folders: string[], isSelected: boolean) void
    +handleAnalyze() Promise~AnalyzeRepositoryResult?|undefined~
    +handleRefreshProcessed() Promise~ProcessRepositoryResult?|null~
    +handleSaveOutput() Promise~void~
    +dismissError() void
  }

  class useApp {
    <<hook>>
    +useApp() AppContextValue
  }

  class AppContent {
    +AppContent()
    -TabId activeTab
    -string rootPath
    -DirectoryTreeItem[] directoryTree
    -Set~string~ selectedFiles
    -Set~string~ selectedFolders
    -ProcessRepositoryResult? processedResult
    -string configContent
    +render() JSX.Element
  }

  class ErrorBanner {
    +ErrorBanner()
    -AppError? appError
    -dismissError() void
    +render() JSX.Element|null
  }

  class ConfigTab {
    +ConfigTabProps props
    -ConfigFormState formState
    -boolean isSaved
    -string[] providerValidationErrors
    -ProviderConnectionResult? providerTestResult
    +saveConfig() void
    +handleTestProviderConnection() Promise~void~
    +render() JSX.Element
  }

  class ConfigFormState {
    +boolean useCustomExcludes
    +boolean useCustomIncludes
    +boolean useGitignore
    +boolean enableSecretScanning
    +boolean excludeSuspiciousFiles
    +boolean includeTreeView
    +boolean showTokenCount
    +ExportFormat exportFormat
    +string fileExtensions
    +string excludePatterns
    +ProviderId|'' providerId
    +string providerModel
    +string providerApiKey
    +string providerBaseUrl
  }

  class SourceTab {
    +SourceTabProps props
    -boolean isAnalyzing
    -boolean isCalculating
    -boolean showTokenCount
    -TokenCache tokenCache
    +onDirectorySelect() Promise~void~
    +onFileSelect(filePath: string, isSelected: boolean) void
    +onFolderSelect(folderPath: string, isSelected: boolean) void
    +onBatchSelect(files: string[], folders: string[], isSelected: boolean) void
    +onAnalyze() Promise~unknown~
    +onRefreshTree() Promise~void~
    +render() JSX.Element
  }

  class FileTree {
    +FileTreeProps props
    -DirectoryTreeItem[] items
    -Set~string~ selectedFiles
    -Set~string~ selectedFolders
    +onFileSelect(filePath: string, isSelected: boolean) void
    +onFolderSelect(folderPath: string, isSelected: boolean) void
    +onBatchSelect(files: string[], folders: string[], isSelected: boolean) void
    +render() JSX.Element
  }

  class ProcessedTab {
    +ProcessedTabProps props
    -boolean isRefreshing
    +onSave() Promise~void~
    +onRefresh() Promise~void~
    +render() JSX.Element
  }

  class Spinner {
    +string? className
    +string? data-testid
    +Spinner(props: SpinnerProps)
    +render() SVGElement
  }

  class ProcessingOptions {
    +boolean showTokenCount
    +boolean includeTreeView
    +ExportFormat exportFormat
  }

  class AppError {
    +string message
    +number timestamp
  }

  AppProvider --> AppContext : provides
  useApp --> AppContext : consumes
  AppProvider --> AppContent : wraps
  AppContent --> ErrorBanner : renders
  AppContent --> ConfigTab : renders
  AppContent --> SourceTab : renders
  AppContent --> ProcessedTab : renders

  ConfigTab --> useApp : uses rootPath, selectDirectory, switchTab
  ConfigTab --> ConfigFormState : manages via useReducer

  SourceTab --> FileTree : renders
  SourceTab --> Spinner : uses
  SourceTab --> useApp : uses configContent via props from context

  ProcessedTab --> Spinner : uses

  FileTree --> useApp : indirect via handlers passed from context

  AppContext ..> ProcessingOptions : uses
  AppContext ..> AppError : uses
Loading

File-Level Changes

Change Details Files
Centralized application state and tab handling into a React context, simplifying App.tsx into a thin shell and removing window globals/localStorage polling.
  • Introduced AppProvider/useApp context exposing tab, path, selection, config, processing and error state plus actions such as switchTab, selectDirectory, refreshDirectoryTree, and analyze/refresh/save handlers.
  • Rewrote App.tsx to render ErrorBanner and tab content using context, keeping tabs mounted and toggling visibility with CSS classes instead of conditional rendering.
  • Migrated global helpers (ensureError, path boundary checks, directory refresh, analyze/process flows) from App.tsx into the context layer and replaced window.switchToTab/refreshDirectoryTree with context methods.
  • Persisted config/rootPath via localStorage only inside the context and removed 500ms polling and custom window events used for cross-component sync.
src/renderer/context/AppContext.tsx
src/renderer/components/App.tsx
src/types/global.d.ts
tests/unit/components/app.test.tsx
Reworked ConfigTab to use a reducer-driven form model and integrate with AppContext for folder selection and navigation.
  • Replaced numerous useState hooks with a single useReducer-based ConfigFormState that mirrors YAML config fields, including provider settings.
  • Hydrated form state from the YAML config prop and saved back via a guarded yaml.stringify to avoid circular updates and no-op writes.
  • Switched folder selection to useApp (rootPath, selectDirectory, switchTab) instead of duplicating directory picking logic and localStorage/event-based syncing.
  • Updated provider connection testing to use reducer-managed state and preserved existing validation while resetting feedback on field changes.
src/renderer/components/ConfigTab.tsx
tests/unit/components/config-tab.test.tsx
Optimized file/folder selection by switching to Set-based state and adding batch selection support, while improving FileTree accessibility.
  • Changed selectedFiles/selectedFolders props and internal state from arrays to Set and replaced Array.includes with Set.has for O(1) membership checks.
  • Extended FileTree with an optional onBatchSelect callback that select-all uses to perform a single batch update when available, falling back to per-item callbacks otherwise.
  • Updated SourceTab to accept Set-based selections, derive arrays only where needed (e.g., token counting), and wire through onBatchSelect and onRefreshTree props from context.
  • Adjusted FileTree markup to use div role='treeitem' and role='tree' containers for better accessibility, moving click/keyboard handlers onto non-button rows and updating tests to query treeitem roles.
src/renderer/components/FileTree.tsx
src/renderer/components/SourceTab.tsx
tests/unit/components/file-tree.test.tsx
tests/unit/components/source-tab.test.tsx
tests/unit/components/app.test.tsx
Improved error and loading UX with an inline ErrorBanner and shared Spinner component, removing alert() calls and duplicated SVGs.
  • Introduced an ErrorBanner in App that reads appError from context and allows dismissing via dismissError, rendering above tab content instead of using window.alert dialogs.
  • Refactored error handling in context handlers (analyze, refreshProcessed, saveOutput) to set appError messages instead of alerting, and updated tests to assert on banner content.
  • Extracted a reusable Spinner icon component and replaced inline loading SVGs in SourceTab and ProcessedTab; removed the unused @heroicons/react dependency.
  • Updated tests to accommodate the new error and loading UX, including asserting on error banner messages instead of alert calls and handling tabs that remain mounted but hidden via CSS.
src/renderer/components/App.tsx
src/renderer/context/AppContext.tsx
src/renderer/components/SourceTab.tsx
src/renderer/components/ProcessedTab.tsx
src/renderer/components/icons/Spinner.tsx
tests/unit/components/app.test.tsx
package.json
Addressed SonarQube and code-quality issues in the main process around provider ID validation, string handling, and directory walking.
  • Replaced an array of SUPPORTED_PROVIDER_IDS with a Set and updated isSupportedProviderId to use Set.has for faster lookups and clearer intent.
  • Switched stripTrailingSlashes to use String.codePointAt instead of charCodeAt for trailing slash detection, satisfying the relevant rule.
  • Refactored directory traversal logic by extracting processEntry and sortTreeItems helpers, simplifying walkDirectory and reducing cognitive complexity and duplication.
  • Tightened types around fs.lstatSync usage and maintained path-boundary and symlink checks to ensure directory trees stay within the authorized root.
src/main/index.ts
Added documentation of the UI refactor plan and ensured tests and type declarations align with the new architecture.
  • Documented the multi-phase UI and state-management refactor plan, including goals, tasks, and success criteria for context, ConfigTab, FileTree, and error handling changes.
  • Removed obsolete window global declarations (switchToTab, refreshDirectoryTree) from global.d.ts now that navigation and refresh go through AppContext.
  • Updated various unit tests to use Set-based selectedFiles/selectedFolders props, context-aware behaviors, and role-based queries for FileTree treeitems.
docs/plan/ui_fixes.md
src/types/global.d.ts
tests/unit/components/*.test.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Feb 14, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@Mehdi-Bl has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 47 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Centralizes renderer state into a new AppContext, migrates selection arrays to Sets across UI, consolidates main-process directory traversal with a processEntry helper, adds a reusable Spinner component, and updates configuration handling to a reducer pattern; also removes one dependency and tweaks vault env config.

Changes

Cohort / File(s) Summary
App Context & Root UI
src/renderer/context/AppContext.tsx, src/renderer/components/App.tsx
Add AppProvider and useApp hook; move global UI state and actions into context; App now composes with AppProvider/DarkModeProvider and reads state/actions from context.
Config UI (reducer-driven)
src/renderer/components/ConfigTab.tsx
Replace many useState hooks with a useReducer form (SET_FIELD, LOAD_FROM_CONFIG); load/save YAML via reducer; integrate with AppContext (rootPath, selectDirectory, switchTab).
Selection & Tree UI API
src/renderer/components/FileTree.tsx, src/renderer/components/SourceTab.tsx
Switch selectedFiles/selectedFolders from arrays to Set; add optional onBatchSelect and onRefreshTree callbacks; change treeitem rendering/ARIA and update selection logic to use Set.has/size.
Spinner component & usage
src/renderer/components/icons/Spinner.tsx, src/renderer/components/ProcessedTab.tsx, src/renderer/components/SourceTab.tsx
Introduce Spinner component and replace inline SVG spinners with in ProcessedTab and SourceTab.
Main process traversal refactor
src/main/index.ts
Centralize per-entry logic into processEntry helper; use Set for SUPPORTED_PROVIDER_IDS and Set.has for validation; replace charCodeAt with codePointAt; add shared sortTreeItems comparator.
Type surface changes
src/types/global.d.ts
Remove window.switchToTab and window.refreshDirectoryTree properties; update imported types from ipc.
Tests
tests/unit/components/*
Update tests to mock useApp and AppContext usage, adapt to Set-based selections, batch selection behavior, treeitem role changes, and Spinner usage; replace window.alert assertions with in-UI error banner checks.
Deps & Env
package.json, vault-agent-env.hcl
Remove @heroicons/react from dependencies; hardcode vault token/sink paths and add Data.token fallback checks for SONAR_TOKEN and DTRACK_API_KEY.

Sequence Diagram(s)

sequenceDiagram
  participant User as User (UI)
  participant App as App (React)
  participant Context as AppContext
  participant Electron as ElectronBridge
  participant Main as Main Process
  participant FS as Filesystem

  User->>App: click Analyze / Select folder
  App->>Context: dispatch action / call useApp API
  Context->>Electron: analyzeRepository / processRepository
  Electron->>Main: IPC -> handle analyze/process
  Main->>FS: traverse directory (processEntry per item)
  FS-->>Main: directory items / st_mode / symlink info
  Main-->>Electron: traversal result (directoryTree)
  Electron-->>Context: return directoryTree / processed results
  Context-->>App: update state (directoryTree, processedResult)
  App-->>User: render updated tree / results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I hopped through code and stitched the streams,

Sets tucked in burrows, reducers chase dreams,
Context gathers crumbs from every tab,
Spinner spins stories, no more ad-hoc drab,
A joyful hop — the app hums, tidy and fab!

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'refactor(ui): centralise state via AppContext, fix code quality issues' clearly and concisely describes the main changes: UI refactoring using AppContext for state management and code quality improvements. It aligns with the primary objective of centralizing state management and addressing SonarQube issues.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/ui-code-organisation-review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Review Summary by Qodo

Refactor UI state management with AppContext, fix SonarQube issues, and improve performance

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation

Grey Divider

Walkthroughs

Description
• **Refactored App.tsx** from 715-line god component to thin shell (179 lines) with centralized
  state management via AppContext provider
• **Consolidated state management** in ConfigTab from 19 useState hooks to single useReducer
  with context integration
• **Improved selection performance** in FileTree by converting from Array.includes() O(n) to
  Set.has() O(1) lookups with batch select-all support
• **Preserved tab state** using CSS hidden class instead of unmount/remount pattern
• **Replaced alert() dialogs** with dismissible ErrorBanner component for better UX
• **Extracted shared Spinner component** to eliminate code duplication across SourceTab and
  ProcessedTab
• **Eliminated window globals** (switchToTab, refreshDirectoryTree) and 500ms localStorage
  polling in favor of React Context data flow
• **Fixed all 4 SonarQube issues**: cognitive complexity (S3776), Set usage (S7776), codePointAt
  for Unicode (S7758), type assertion (S4325)
• **Added semantic HTML** with role='treeitem' and aria-selected attributes for accessibility
• **Updated all unit tests** (318/318 passing) for Set-based props, context mocks, and error banner
  assertions
Diagram
flowchart LR
  A["God Component<br/>App.tsx<br/>715 lines"] -->|"Extract state"| B["AppContext<br/>Centralized state"]
  C["19 useState<br/>ConfigTab"] -->|"Consolidate"| D["useReducer<br/>+ Context"]
  E["Array.includes<br/>O(n) selection"] -->|"Convert to Set"| F["Set.has<br/>O(1) lookup"]
  G["alert() dialogs"] -->|"Replace with"| H["ErrorBanner<br/>Dismissible UI"]
  I["Inline SVG<br/>Spinners"] -->|"Extract"| J["Spinner<br/>Component"]
  K["Window globals<br/>+ localStorage"] -->|"Eliminate"| B
  B --> L["4 SonarQube<br/>issues fixed"]
Loading

Grey Divider

File Changes

1. src/main/index.ts 🐞 Bug fix +67/-59

SonarQube fixes and directory walk refactoring

• Removed unnecessary type assertion as fs.Stats | undefined from lstatFn(itemPath) call
• Converted SUPPORTED_PROVIDER_IDS from array to Set<ProviderId> for O(1) lookups
• Updated provider validation to use Set.has() instead of Array.includes()
• Changed charCodeAt() to codePointAt() for proper Unicode handling in stripTrailingSlashes()
• Extracted processEntry() and sortTreeItems() helper functions to reduce cognitive complexity
 in walkDirectory()

src/main/index.ts


2. src/types/global.d.ts Refactoring +1/-3

Remove window global type declarations

• Removed switchToTab window global declaration
• Removed refreshDirectoryTree window global declaration
• Eliminated window mutation pattern in favor of React Context

src/types/global.d.ts


3. src/renderer/components/App.tsx Refactoring +147/-682

Refactor App to thin shell with AppContext

• Refactored from 715-line god component to thin shell (179 lines)
• Extracted all state management to AppContext provider
• Replaced conditional tab rendering with CSS hidden class to preserve component state
• Added ErrorBanner component to replace alert() calls with dismissible inline UI
• Removed localStorage polling, window global mutations, and custom event listeners

src/renderer/components/App.tsx


View more (13)
4. src/renderer/components/ConfigTab.tsx Refactoring +154/-277

Consolidate state with useReducer and AppContext

• Consolidated 19 useState hooks into single useReducer with ConfigFormState and
 ConfigFormAction
• Integrated useApp() hook to consume rootPath, selectDirectory, and switchTab from context
• Removed duplicate folder selection logic and localStorage polling
• Added guard in saveConfig() to prevent circular updates when YAML content unchanged
• Simplified provider field handling via setField() dispatcher

src/renderer/components/ConfigTab.tsx


5. src/renderer/context/AppContext.tsx ✨ Enhancement +639/-0

Create centralized AppContext for state management

• New file: centralized state provider managing activeTab, rootPath, directoryTree,
 selectedFiles/selectedFolders (as Set), configContent, processedResult
• Provides actions: switchTab(), selectDirectory(), refreshDirectoryTree(), updateConfig(),
 handleFileSelect(), handleFolderSelect(), handleBatchSelect(), handleAnalyze(),
 handleRefreshProcessed(), handleSaveOutput()
• Includes error state management with appError and dismissError()
• Eliminates localStorage polling and window globals via React data flow

src/renderer/context/AppContext.tsx


6. src/renderer/components/SourceTab.tsx ✨ Enhancement +27/-62

Update to Set-based selection and shared Spinner

• Updated selectedFiles and selectedFolders props from arrays to Set<string> for O(1) lookups
• Added configContent prop to read config directly instead of from localStorage
• Added onBatchSelect prop for efficient select-all operations
• Added onRefreshTree prop to call context action instead of window global
• Replaced inline SVG spinners with shared Spinner component
• Updated selection iteration to use Set methods instead of array operations

src/renderer/components/SourceTab.tsx


7. src/renderer/components/icons/Spinner.tsx ✨ Enhancement +32/-0

Extract shared Spinner icon component

• New file: extracted shared spinner SVG component used across SourceTab and ProcessedTab
• Accepts className and data-testid props for customization
• Replaces 3 duplicated inline SVG spinner definitions

src/renderer/components/icons/Spinner.tsx


8. docs/plan/ui_fixes.md 📝 Documentation +245/-0

Add comprehensive UI fixes implementation plan

• New file: comprehensive three-agent validated implementation plan documenting 15 findings
• Organized into 5 phases: State Architecture, ConfigTab Refactor, FileTree Performance, UI Polish,
 Type Safety & Security
• Includes root cause analysis, task breakdown, validation criteria, and phase dependencies
• Tracks fixes for god component, window globals, localStorage polling, O(n) selection, alert() UX,
 and SonarQube issues

docs/plan/ui_fixes.md


9. tests/unit/components/file-tree.test.tsx 🧪 Tests +58/-26

Update FileTree tests for Set-based selection

• Updated selectedFiles and selectedFolders test props from arrays to Set instances
• Added mockBatchSelect mock function and onBatchSelect prop to test calls
• Updated role queries from button to treeitem for file rows (fixes nested interactive element
 issue)
• Added new test case for batch select handler when provided
• Updated assertions to verify Set.has() behavior instead of array operations

tests/unit/components/file-tree.test.tsx


10. tests/unit/components/config-tab.test.tsx 🧪 Tests +19/-8

Update ConfigTab tests for AppContext integration

• Added mock for useApp() hook from AppContext providing rootPath, selectDirectory,
 switchTab
• Removed window.alert mock (no longer used)
• Updated folder selection test to verify context action calls instead of localStorage/window
 globals
• Updated assertions to check mockSelectDirectory and mockSwitchTab calls

tests/unit/components/config-tab.test.tsx


11. tests/unit/components/source-tab.test.tsx 🧪 Tests +5/-2

Update SourceTab tests for Set-based props

• Updated selectedFiles and selectedFolders test props from arrays to Set instances
• Added configContent prop to SourceTab render call
• Added onBatchSelect and onRefreshTree mock props
• Updated test setup to match new component prop interface

tests/unit/components/source-tab.test.tsx


12. src/renderer/components/FileTree.tsx ✨ Enhancement +32/-21

Convert FileTree selection to Set-based with accessibility improvements

• Converted selectedFiles and selectedFolders props from string[] arrays to Set<string> for
 O(1) lookup performance
• Replaced Array.includes() calls with Set.has() method in selection checks
• Added semantic HTML attributes (role='treeitem', role='tree', aria-selected) for
 accessibility
• Introduced onBatchSelect callback prop to handle select-all operations efficiently
• Changed button element to div with keyboard event handling for proper tree item semantics

src/renderer/components/FileTree.tsx


13. tests/unit/components/app.test.tsx 🧪 Tests +13/-21

Update tests for Set-based selection and error banner

• Updated mock SourceTab component to use selectedFiles.size instead of selectedFiles.length
 for Set compatibility
• Changed PropTypes validation for selectedFiles from array to any to accommodate Set type
• Removed window.alert mock setup and replaced alert-based error test with error banner DOM
 assertion
• Updated error handling test to wait for error banner text instead of alert dialog
• Simplified test assertions to verify active tab state via data-active attribute

tests/unit/components/app.test.tsx


14. src/renderer/components/ProcessedTab.tsx ✨ Enhancement +3/-20

Extract spinner SVG to shared Spinner component

• Extracted inline SVG spinner markup into dedicated Spinner component import
• Replaced 20-line SVG spinner code with single <Spinner className='-ml-1 mr-2 h-4 w-4' /> call
• Maintains identical visual behavior while improving code reusability and maintainability

src/renderer/components/ProcessedTab.tsx


15. src/renderer/components/icons/Spinner.tsx ✨ Enhancement +32/-0

Create reusable Spinner component for loading states

• New shared spinner component with customizable className and data-testid props
• Encapsulates animated SVG spinner markup with default styling (h-4 w-4)
• Supports test identification via optional data-testid attribute
• Eliminates code duplication across components using loading spinners

src/renderer/components/icons/Spinner.tsx


16. package.json Additional files +0/-1

...

package.json


Grey Divider

Qodo Logo

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @Mehdi-Bl, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant architectural refactor to centralize state management using React Context, enhancing the application's maintainability, performance, and user experience. It addresses several code quality issues, streamlines UI interactions, and improves the efficiency of file selection and error reporting across the application.

Highlights

  • State Management Centralization: Centralized application state management by introducing a new AppContext provider, transforming App.tsx from a large 'god component' into a thin shell. This eliminates reliance on localStorage polling and global window functions for state synchronization.
  • ConfigTab Refactor: Refactored ConfigTab to use a single useReducer hook for managing its form state, significantly reducing the number of useState hooks (from 19) and addressing circular auto-save issues.
  • FileTree Performance & Accessibility: Improved FileTree selection performance by converting selectedFiles and selectedFolders from arrays to Set objects, enabling O(1) lookups. Also enhanced accessibility by replacing nested interactive elements with appropriate ARIA roles.
  • Error Handling & UI: Replaced all alert() calls with a dismissible inline ErrorBanner component, providing a more integrated and user-friendly error display.
  • Component Reusability & Dependency Cleanup: Extracted a shared Spinner component for consistent loading indicators and removed the unused @heroicons/react dependency.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • docs/plan/ui_fixes.md
    • Added a detailed UI & Code Organisation Fix Plan document outlining the refactoring strategy and identified issues.
  • package-lock.json
    • Removed @heroicons/react dependency.
  • package.json
    • Removed @heroicons/react dependency from project dependencies.
  • src/main/index.ts
    • Updated lstatResult type assertion for improved type safety.
    • Converted SUPPORTED_PROVIDER_IDS from an array to a Set for efficient lookups.
    • Replaced charCodeAt with codePointAt for Unicode-safe character handling.
    • Refactored directory walking logic by extracting processEntry and sortTreeItems helper functions to improve cognitive complexity.
  • src/renderer/components/App.tsx
    • Rewritten to be a thin shell component, delegating state management and logic to AppContext.
    • Removed numerous useState and useEffect hooks, localStorage polling, and global window functions (switchToTab, refreshDirectoryTree).
    • Integrated AppProvider and useApp hook for centralized state access.
    • Introduced ErrorBanner component for displaying dismissible inline errors.
  • src/renderer/components/ConfigTab.tsx
    • Refactored state management from multiple useState hooks to a single useReducer for config form state.
    • Removed localStorage polling and direct calls to global window functions for directory selection and tab switching.
    • Added a guard to saveConfig to prevent circular auto-save effects.
    • Updated input fields to interact with the new useReducer state.
  • src/renderer/components/FileTree.tsx
    • Updated selectedFiles and selectedFolders props to use Set<string> for O(1) lookup performance.
    • Modified selection logic to utilize Set.has() instead of Array.includes().
    • Introduced onBatchSelect prop to handle bulk selection updates efficiently.
    • Changed the outer interactive element for tree items from <button> to <div role='treeitem'> to improve accessibility and semantic correctness.
  • src/renderer/components/ProcessedTab.tsx
    • Replaced inline SVG spinner with the new reusable Spinner component.
  • src/renderer/components/SourceTab.tsx
    • Updated selectedFiles and selectedFolders props to use Set<string>.
    • Modified token calculation logic to work with Set for selected files.
    • Added configContent, onBatchSelect, and onRefreshTree props to align with AppContext.
    • Replaced inline SVG spinner with the new reusable Spinner component.
    • Updated clear selection logic to iterate over Set objects.
  • src/renderer/components/icons/Spinner.tsx
    • Added a new reusable Spinner React component, encapsulating the SVG for loading indicators.
  • src/renderer/context/AppContext.tsx
    • Added a new AppContext and AppProvider to centralize application-wide state (active tab, root path, directory tree, selections, config, processed results, errors).
    • Implemented useApp hook for convenient access to centralized state and actions.
    • Moved core logic for directory selection, file/folder selection, analysis, processing, and error handling into the context.
    • Managed localStorage interactions for config and root path within the context.
  • src/types/global.d.ts
    • Removed switchToTab and refreshDirectoryTree from global window declarations, as these functionalities are now managed by AppContext.
  • tests/unit/components/app.test.tsx
    • Updated mock for SourceTab to reflect the change of selectedFiles from array to Set.
    • Removed window.alert mock and updated error handling assertions to expect the ErrorBanner component.
    • Adjusted tests to verify tab visibility via CSS classes instead of conditional rendering.
  • tests/unit/components/config-tab.test.tsx
    • Introduced mock for useApp from AppContext to provide rootPath, selectDirectory, and switchTab.
    • Removed mocks for window.alert and window.switchToTab.
    • Updated assertions for folder selection to confirm calls to AppContext functions.
  • tests/unit/components/file-tree.test.tsx
    • Updated test props to pass Set objects for selectedFiles and selectedFolders.
    • Added mock for onBatchSelect to test batch selection behavior.
    • Adjusted queries and assertions to account for the div with role='treeitem' replacing button for tree items.
  • tests/unit/components/source-tab.test.tsx
    • Updated test props to pass Set objects for selectedFiles and selectedFolders.
    • Added configContent, onBatchSelect, and onRefreshTree to mock props.
    • Updated assertions related to selected file counts to use Set.size.
Activity
  • The pull request author, Mehdi-Bl, has implemented a comprehensive refactoring based on a detailed UI & Code Organisation Fix Plan.
  • All unit tests (318/318), stress tests (3/3), and E2E tests (4/4) are passing.
  • Linting checks (ESLint + Prettier + markdownlint) show zero warnings.
  • All 4 previously reported SonarQube issues (S3776 cognitive complexity, S7776 Set usage, S7758 codePointAt, S4325 type assertion) have been fixed.
  • The build process (webpack + electron-builder) is successful.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • AppContext.tsx is taking on a lot of responsibilities (error handling, config persistence, path utils, selection logic, IPC orchestration); consider extracting reusable helpers (e.g., path boundary utilities, error helpers, and maybe selection helpers) into separate modules to keep the provider focused and easier to maintain.
  • In SourceTab, the token-counting effect relies on a derived selectedFilesArray but suppresses the dependency lint rule; you could avoid the eslint-disable by memoizing selectedFilesArray with useMemo and using that in the effect dependencies to keep the logic explicit and safer to maintain.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- AppContext.tsx is taking on a lot of responsibilities (error handling, config persistence, path utils, selection logic, IPC orchestration); consider extracting reusable helpers (e.g., path boundary utilities, error helpers, and maybe selection helpers) into separate modules to keep the provider focused and easier to maintain.
- In SourceTab, the token-counting effect relies on a derived `selectedFilesArray` but suppresses the dependency lint rule; you could avoid the eslint-disable by memoizing `selectedFilesArray` with `useMemo` and using that in the effect dependencies to keep the logic explicit and safer to maintain.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@github-actions

github-actions Bot commented Feb 14, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

  • package-lock.json

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This is an excellent and comprehensive refactoring of the UI state management. Centralizing state in AppContext and removing the "god component" pattern in App.tsx is a huge improvement for maintainability and clarity. The switch to useReducer in ConfigTab, the performance optimizations using Set in FileTree, and the improved UX with the ErrorBanner and state preservation on tab switching are all fantastic changes. The code quality is high, and the accompanying test updates are thorough. I've found one minor opportunity for performance improvement.

Comment thread src/renderer/components/SourceTab.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@docs/plan/ui_fixes.md`:
- Around line 3-4: Update the test count in the doc line that reads "Three-agent
validated review (Claude Opus 4.6, Codex GPT-5.3, Gemini). All findings
confirmed against source. Lint: PASS, Tests: PASS (35 suites, 317 tests)." to
reflect the actual number reported in the PR (change "317 tests" to "318 tests")
so the plan and PR description match exactly.

In `@src/renderer/components/ConfigTab.tsx`:
- Around line 384-387: handleFolderSelect unconditionally calls
switchTab('source') even when selectDirectory() is a no-op on cancel; update
selectDirectory in AppContext (e.g., return Promise<boolean> or
Promise<string|null> from the selectDirectory function in AppContext.tsx) to
indicate whether a directory was selected, then modify handleFolderSelect to
await selectDirectory and only call switchTab('source') when the returned value
indicates success (true or a non-null path) to prevent switching tabs on cancel.

In `@src/renderer/components/FileTree.tsx`:
- Around line 248-264: The selectAllChecked memo can return true incorrectly
when selectedFiles.size equals totalFiles but contains stale paths; update the
logic in selectAllChecked to always validate against the current tree by
collecting current file paths (using collectFilePaths(items, validFilePaths))
and then verifying that every path in validFilePaths exists in selectedFiles
(short-circuiting on the first missing file) instead of relying on
selectedFiles.size; keep the dependency array [items, selectedFiles, totalFiles]
and ensure the function returns true only when all current file paths are
present in selectedFiles.

In `@src/renderer/context/AppContext.tsx`:
- Around line 232-259: The updater passed to setActiveTab inside switchTab
contains side effects (localStorage access, yaml.parse, setProcessingOptions,
setProcessedResult, and modifying analysisResultRef) which must be moved outside
the state-updater to keep the updater pure; refactor switchTab to read the
current tab from a stable ref (e.g., activeTabRef) or from the outer scoped
activeTab state to perform the prevTab === tab guard, then if the tab actually
changes call setActiveTab(tab) and run the side effects (read localStorage,
parse YAML, call setProcessingOptions with computed values using
normalizeExportFormat, and when tab === 'source' clear analysisResultRef.current
and call setProcessedResult(null)); ensure the updater passed to setActiveTab
only returns the new tab value and does not perform any I/O or state-setting
side effects.
🧹 Nitpick comments (7)
src/main/index.ts (1)

563-612: processEntry cleanly centralizes per-item logic.

The extraction reduces duplication and cognitive complexity. One observation:

Lines 575–581: All symlinks are skipped, but the warning only logs for symlinks pointing outside the root. Symlinks within the root are silently dropped — this could surprise users whose projects rely on in-tree symlinks. Consider logging at debug level for the in-root case too, or adding a brief inline comment explaining the intent.

Optional: clarify silent symlink skip
       if (isSymbolicLink) {
         const resolvedSymlinkPath = resolveRealPath(itemPath);
         if (!isPathWithinRoot(authorizedDirPath, resolvedSymlinkPath)) {
           console.warn(`Skipping symlink outside current root directory: ${itemPath}`);
         }
+        // All symlinks are excluded from the tree to avoid traversal cycles and path confusion.
         return null;
       }
tests/unit/components/file-tree.test.tsx (1)

191-222: Missing test coverage for batch deselect path.

The batch handler test (Line 166) only verifies the select case (true). The "deselects all" test (Line 191) doesn't pass onBatchSelect, so the batch deselect path (onBatchSelect(…, …, false)) is never exercised.

Consider adding a test that passes onBatchSelect with all items pre-selected, clicks "Select All" to deselect, and asserts mockBatchSelect was called with false.

src/renderer/components/App.tsx (1)

12-31: ErrorBanner: clean and accessible inline component.

Consider extracting it to its own file (e.g., components/ErrorBanner.tsx) for reusability and testability, especially since the PR summary mentions it as a distinct feature. This is optional given its small size.

src/renderer/components/SourceTab.tsx (2)

84-84: selectedFilesArray is recomputed on every render.

The spread [...selectedFiles] runs on each render, even when selectedFiles hasn't changed. Consider wrapping in useMemo to avoid unnecessary allocations during unrelated re-renders (e.g., isAnalyzing or isCalculating state changes).

Proposed fix
- const selectedFilesArray = [...selectedFiles];
+ const selectedFilesArray = useMemo(() => [...selectedFiles], [selectedFiles]);

You'd need to add useMemo to the existing import on Line 1.


361-370: "Clear selection" button uses per-item calls instead of batch.

The onBatchSelect prop is available but the clear button still iterates individually over both Sets. This misses the optimization that onBatchSelect provides (single state update vs N+M updates).

Proposed fix
  onClick={() => {
-   for (const filePath of selectedFiles) onFileSelect(filePath, false);
-   for (const folderPath of selectedFolders) onFolderSelect(folderPath, false);
+   onBatchSelect([...selectedFiles], [...selectedFolders], false);
    setTotalTokens(0);
src/renderer/context/AppContext.tsx (2)

467-479: Duplicated processing-options-from-config parsing logic.

The same YAML→ProcessingOptions extraction appears in handleAnalyze (Lines 467–479), handleRefreshProcessed (Lines 528–540), and switchTab (Lines 236–247). Consider extracting a shared helper:

const parseProcessingOptions = (configStr: string | null): ProcessingOptions => {
  const defaults: ProcessingOptions = { showTokenCount: true, includeTreeView: false, exportFormat: 'markdown' };
  if (!configStr) return defaults;
  try {
    const config = (yaml.parse(configStr) || {}) as ConfigObject;
    return {
      showTokenCount: config.show_token_count !== false,
      includeTreeView: config.include_tree_view === true,
      exportFormat: normalizeExportFormat(config.export_format),
    };
  } catch { return defaults; }
};

Also applies to: 528-540


304-396: Nested helper functions (findFolder, getAllSubFolders, getAllFiles) inside useCallback.

These tree-traversal helpers are recreated whenever rootPath or directoryTree changes. Since they're pure functions (aside from the rootPath closure for boundary checks), consider extracting them to module-level functions that accept rootPath as a parameter. This improves readability and testability.

Comment thread docs/plan/ui_fixes.md Outdated
Comment thread src/renderer/components/ConfigTab.tsx
Comment thread src/renderer/components/FileTree.tsx
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Feb 14, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (4) 📎 Requirement gaps (0)

Grey Divider


Action required

1. ErrorBanner shows raw Error.message 📘 Rule violation ⛨ Security
Description
User-facing errors render appError.message which is built from underlying exception messages,
potentially exposing internal details. This violates the secure error handling requirement to keep
end-user errors generic and reserve details for internal logs.
Code

src/renderer/components/App.tsx[R18-23]

+    <div className='flex items-center justify-between bg-red-50 dark:bg-red-900/30 border-b border-red-200 dark:border-red-800 px-4 py-2 text-sm text-red-800 dark:text-red-200'>
+      <span>{appError.message}</span>
+      <button
+        onClick={dismissError}
+        className='ml-4 shrink-0 text-red-600 dark:text-red-300 hover:text-red-800 dark:hover:text-red-100'
+        aria-label='Dismiss error'
Evidence
PR Compliance ID 4 requires user-facing errors to be generic, but the UI renders appError.message
directly and the context sets it using underlying exception messages (e.g.,
${processedError.message}), which can leak internal implementation details.

Rule 4: Generic: Secure Error Handling
src/renderer/components/App.tsx[18-23]
src/renderer/context/AppContext.tsx[499-503]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
User-facing error UI currently displays raw exception messages (via `appError.message`), which can leak internal details.
## Issue Context
Compliance requires generic end-user messages while preserving detailed diagnostics in internal logs.
## Fix Focus Areas
- src/renderer/components/App.tsx[18-23]
- src/renderer/context/AppContext.tsx[167-173]
- src/renderer/context/AppContext.tsx[499-503]
- src/renderer/context/AppContext.tsx[557-560]
- src/renderer/context/AppContext.tsx[576-580]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. handleFolderSelect switches tab always 📘 Rule violation ⛯ Reliability
Description
After clicking Select Folder, ConfigTab switches to the Source tab even if directory selection is
cancelled or fails. This is an unhandled edge case that can lead to confusing UI state (Source tab
with no root selected).
Code

src/renderer/components/ConfigTab.tsx[R385-386]

+    await selectDirectory();
+    switchTab('source');
Evidence
PR Compliance ID 3 requires explicit handling of null/empty/boundary cases; here the code
unconditionally switches tabs regardless of whether selectDirectory() actually selected a path.

Rule 3: Generic: Robust Error Handling and Edge Case Management
src/renderer/components/ConfigTab.tsx[385-386]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
ConfigTab switches to the Source tab even if the user cancels the directory picker.
## Issue Context
`selectDirectory()` may result in no-op when the dialog is cancelled; in that case, switching tabs is an edge-case bug and violates robust edge-case handling expectations.
## Fix Focus Areas
- src/renderer/components/ConfigTab.tsx[385-386]
- src/renderer/context/AppContext.tsx[271-285]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 3. Autosave triggers on typing 🐞 Bug ⛯ Reliability
Description
ConfigTab’s autosave effect is intended to run only for checkbox/select changes, but it will also
run for textarea/provider typing because the effect depends on saveConfig, and saveConfig is
recreated on any formState change. This can cause frequent YAML stringify + onConfigChange while
editing, and can also reset controlled textarea values via the LOAD_FROM_CONFIG effect.
Code

src/renderer/components/ConfigTab.tsx[R219-227]

const saveConfig = useCallback(() => {
 try {
   let config: ConfigObject;
   try {
-        // Parse the current config
     config = yaml.parse(configContent) as ConfigObject;
-        // If parsing returns null or undefined, use empty object
     if (!config) {
       config = {};
     }
Evidence
saveConfig depends on the entire formState, so its identity changes on every field edit. Because
the autosave useEffect depends on saveConfig, it will re-run even when only text fields
change—despite the comment claiming otherwise. When onConfigChange fires, configContent changes
and triggers the LOAD_FROM_CONFIG effect, which can overwrite the controlled textarea values
mid-edit.

src/renderer/components/ConfigTab.tsx[206-321]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
ConfigTab autosave re-triggers on text edits because the effect depends on `saveConfig`, and `saveConfig` depends on the entire `formState`. This causes YAML stringify + config propagation while typing and can lead to controlled textarea value resets when `configContent` updates.
### Issue Context
The comment says autosave is only for checkbox/select changes and text fields save on blur/button, but there are no `onBlur` saves and the dependency structure defeats the intended behavior.
### Fix Focus Areas
- src/renderer/components/ConfigTab.tsx[206-321]
- src/renderer/components/ConfigTab.tsx[718-761]
### Suggested approach
1. Refactor `saveConfig` to accept an explicit state argument: `saveConfig(nextState: ConfigFormState)` and make it `useCallback` depend only on `configContent` and `onConfigChange`.
2. In the autosave effect, call `saveConfig(formState)` but ensure the effect deps include only the checkbox/select fields (and **not** `saveConfig` changing due to text edits). If needed, keep a stable `saveConfig` using `useCallback` + arguments.
3. Add `onBlur={() =&amp;amp;amp;gt; saveConfig(formState)}` to the textareas (or remove the comment if button-only is desired).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Removed config error UI 📘 Rule violation ✓ Correctness
Description
Config update failures no longer show any user-visible error message, which can leave users unaware
that their configuration changes failed. This reduces graceful degradation and actionable feedback
for error cases.
Code

src/renderer/components/ConfigTab.tsx[352]

-      alert('Error updating configuration. Please check the YAML syntax.');
Evidence
PR Compliance ID 3 requires meaningful error handling and actionable context; the PR removed the
only user-facing feedback (alert(...)) for config update failures without replacing it with an
equivalent inline/contextual error.

Rule 3: Generic: Robust Error Handling and Edge Case Management
src/renderer/components/ConfigTab.tsx[352-352]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
User-visible feedback for config update failures was removed, leaving only console logging.
## Issue Context
The app now has a centralized error banner pattern; ConfigTab should report YAML/config write errors through it (or show an inline validation/error block).
## Fix Focus Areas
- src/renderer/components/ConfigTab.tsx[301-305]
- src/renderer/context/AppContext.tsx[167-173]
- src/renderer/context/AppContext.tsx[51-72]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. console.warn logs full paths 📘 Rule violation ⛨ Security
Description
New warning logs include full filesystem paths (e.g., itemPath), which can reveal sensitive local
information such as usernames/home directories. Logs are also unstructured strings, reducing safe
auditability.
Code

src/main/index.ts[R576-585]

+        const resolvedSymlinkPath = resolveRealPath(itemPath);
+        if (!isPathWithinRoot(authorizedDirPath, resolvedSymlinkPath)) {
+          console.warn(`Skipping symlink outside current root directory: ${itemPath}`);
+        }
+        return null;
+      }
+
+      if (!isPathWithinRoot(authorizedDirPath, itemPath)) {
+        console.warn(`Skipping path outside current root directory: ${itemPath}`);
+        return null;
Evidence
PR Compliance ID 5 requires logs to avoid sensitive data and be structured; the added warnings
output raw absolute paths in free-form strings, which may contain PII-like data (usernames) and is
not structured for auditing.

Rule 5: Generic: Secure Logging Practices
src/main/index.ts[576-585]
src/renderer/context/AppContext.tsx[288-290]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Logs currently emit full filesystem paths in unstructured strings, which can leak sensitive local info (e.g., usernames in paths) and makes auditing harder.
## Issue Context
Compliance requires logs to avoid sensitive data and be structured for monitoring/auditing.
## Fix Focus Areas
- src/main/index.ts[576-585]
- src/renderer/context/AppContext.tsx[288-290]
- src/renderer/context/AppContext.tsx[439-443]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Directory tree fetched twice 🐞 Bug ➹ Performance
Description
When selecting a folder from ConfigTab, the tree is fetched once inside selectDirectory() and then
fetched again by the config→source transition effect that calls refreshDirectoryTree(). This
doubles IPC work and can add noticeable latency on large repositories.
Code

src/renderer/context/AppContext.tsx[R261-267]

+  // When switching from config to source, refresh tree with latest config
+  const prevTabRef = useRef<TabId>('config');
+  useEffect(() => {
+    if (prevTabRef.current === 'config' && activeTab === 'source' && rootPath) {
+      appWindow.electronAPI?.resetGitignoreCache?.();
+      refreshDirectoryTree();
+    }
Evidence
selectDirectory() calls getDirectoryTree(dirPath, configContent) directly. Then switching from
config to source triggers an effect that always calls refreshDirectoryTree() (which itself calls
getDirectoryTree(rootPath, configContent)), resulting in back-to-back tree fetches for the same
root/config.

src/renderer/components/ConfigTab.tsx[384-387]
src/renderer/context/AppContext.tsx[220-229]
src/renderer/context/AppContext.tsx[261-269]
src/renderer/context/AppContext.tsx[271-284]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Folder selection from ConfigTab causes two back-to-back directory tree fetches.
### Issue Context
- `selectDirectory()` already fetches the tree.
- The config→source effect unconditionally refreshes the tree again.
### Fix Focus Areas
- src/renderer/context/AppContext.tsx[220-230]
- src/renderer/context/AppContext.tsx[261-269]
- src/renderer/context/AppContext.tsx[271-285]
- src/renderer/components/ConfigTab.tsx[384-387]
### Suggested approach options
1. Track last successful tree fetch params in a ref (rootPath + configContent hash/version) and have the config→source effect call `refreshDirectoryTree()` only if params changed.
2. Add a `skipNextSourceRefreshRef` that `selectDirectory()` sets when it already fetched a fresh tree, and the config→source effect consumes/clears.
3. Move the “refresh on config→source” logic into the ConfigTab save/switch flow so it only triggers when config actually changed (not on folder selection).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

✅ 7. Side effects in updater 🐞 Bug ⛯ Reliability
Description
switchTab performs side effects (localStorage parse and
setProcessingOptions/setProcessedResult) inside the setActiveTab functional updater. This is a
React anti-pattern and can lead to duplicated work under StrictMode dev double-invocation, making
tab transitions harder to reason about.
Code

src/renderer/context/AppContext.tsx[R232-259]

+  const switchTab = useCallback((tab: TabId) => {
+    setActiveTab((prevTab) => {
+      if (prevTab === tab) return prevTab;
+
+      try {
+        const savedConfig = localStorage.getItem('configContent');
+        if (savedConfig) {
+          const config = (yaml.parse(savedConfig) || {}) as ConfigObject;
+          if (!config.include_extensions) config.include_extensions = [];
+          if (!config.exclude_patterns) config.exclude_patterns = [];
+          setProcessingOptions({
+            showTokenCount: config.show_token_count !== false,
+            includeTreeView: config.include_tree_view === true,
+            exportFormat: normalizeExportFormat(config.export_format),
+          });
+        }
+      } catch (error) {
+        console.error('Error parsing config when changing tabs:', error);
+      }
+
+      if (tab === 'source') {
+        analysisResultRef.current = null;
+        setProcessedResult(null);
+      }
+
+      return tab;
+    });
+  }, []);
Evidence
The functional updater passed to setActiveTab includes side-effectful state setters
(setProcessingOptions, setProcessedResult) and IO (localStorage/YAML parse). Updaters are
expected to be pure to keep state transitions predictable.

src/renderer/context/AppContext.tsx[232-259]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`switchTab` runs side effects inside the `setActiveTab` functional updater.
### Issue Context
React may invoke updater functions more than once in dev StrictMode; purity improves predictability and reduces duplicated work.
### Fix Focus Areas
- src/renderer/context/AppContext.tsx[232-259]
### Suggested approach
- Change `switchTab` to `setActiveTab(tab)` (with early-return guard outside).
- Use a `useEffect` keyed on `activeTab` (and previous tab if needed) to perform:
- parsing config and setting processingOptions
- clearing processed state when entering specific tabs
- Alternatively, remove `processingOptions` if unused outside context, and only compute options in `handleAnalyze`/`handleRefreshProcessed`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread src/renderer/components/App.tsx
Comment thread src/renderer/components/ConfigTab.tsx Outdated
Comment thread src/renderer/components/ConfigTab.tsx Outdated
@Mehdi-Bl Mehdi-Bl force-pushed the fix/ui-code-organisation-review branch from 4dfb545 to 06cb22d Compare February 14, 2026 04:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@src/main/index.ts`:
- Around line 563-612: processEntry currently returns null for every symbolic
link even when the symlink resolves inside the authorized root; change the logic
so you only skip symlinks whose resolved path is outside the root by using
resolveRealPath(resolvedSymlinkPath) + isPathWithinRoot(authorizedDirPath,
resolvedSymlinkPath) and only return null for out-of-root targets, otherwise
proceed to treat the symlink target as the entry (use the resolved path for
subsequent readPathStats/walkFn calls and rely on visitedDirectoryRealPaths to
avoid cycles); if the original early-return was intentional for
defence-in-depth, add a clear comment above the symlink handling explaining that
choice instead of silently dropping all symlinks.

In `@src/renderer/components/ConfigTab.tsx`:
- Around line 178-193: initialFormState sets includeTreeView: true which
conflicts with AppContext's default processingOptions.includeTreeView: false
causing a brief UI flash; update initialFormState.includeTreeView to false (or
align AppContext.processingOptions.includeTreeView to true) so both defaults
match, and verify the form state and AppContext (symbols: initialFormState,
includeTreeView, AppContext, processingOptions.includeTreeView,
LOAD_FROM_CONFIG) are consistent to prevent the checkbox showing a different
state before LOAD_FROM_CONFIG runs.

In `@src/renderer/context/AppContext.tsx`:
- Around line 563-612: The symlink branch in processEntry currently always
returns null after resolving a symlink even when the symlink target lives inside
the repo; change it so the early return is conditional: resolve the symlink
target (using the existing resolved/target variables), compute its real path,
and if the targetRealPath is inside the rootRealPath proceed to process that
target (and use visitedDirectoryRealPaths to avoid cycles) but if it's outside
the root log/warn and then return null; alternatively, if skipping all symlinks
was intended, replace the dead warn-only branch with a clear comment explaining
that design decision and keep the unconditional return — reference processEntry
and visitedDirectoryRealPaths to locate the change.
- Around line 528-540: handleRefreshProcessed currently reads config from
localStorage (localStorage.getItem('configContent')) which can be stale or
sanitized; change it to use the React state configContent (same source used by
handleAnalyze) when building ProcessingOptions. Locate handleRefreshProcessed
and replace the localStorage parse with parsing yaml.parse(configContent || '')
(with the same fallback/normalize logic you use elsewhere), preserve the
try/catch and ensure options.showTokenCount, options.includeTreeView and
options.exportFormat are set with normalizeExportFormat; then call
setProcessingOptions(options) as before so the in-memory state is the single
source of truth.

In `@vault-agent-env.hcl`:
- Line 21: Update the HCL template to document and/or harden the fallback
behavior: add a clear comment above the contents line that references the
specific keys used (.Data.data.SONAR_TOKEN, .Data.data.sonar_token and the
generic .Data.data.token) and explain that .Data.data.token is a legacy generic
fallback that can silently return unrelated secrets (also mention the
DTRACK_API_KEY usage at the other similar block), and either remove the generic
`.Data.data.token` fallback or set error_on_missing_key = true if you want
strict failures; include the same comment/changes for the second instance that
references DTRACK_API_KEY so future maintainers know the expected Vault key
layout and the risks of the generic `token` key.
🧹 Nitpick comments (7)
vault-agent-env.hcl (1)

4-4: Hardcoded paths reduce portability across environments.

Replacing the previous environment-variable references (VAULT_TOKEN_FILE, VAULT_AGENT_TOKEN_SINK_FILE) with hardcoded paths ties this config to a specific devcontainer layout (/home/vscode/…). If this file is only consumed inside the devcontainer that's acceptable, but it will silently break in any other environment (CI runner, production host, different container image with a non-vscode user).

Consider restoring the env-var indirection or at least documenting the assumption prominently.

Also applies to: 10-10

src/renderer/context/AppContext.tsx (2)

330-360: Quadratic array growth in recursive helpers getAllSubFolders / getAllFiles.

Lines 337 and 355 use spread ([...files, ...getAllFiles(item)]) inside a loop, which copies the entire accumulated array on every directory entry. For a tree with n files and d nested directories this is O(n·d) allocations instead of O(n).

Suggested refactor: push into a single array
     const getAllFiles = (folder: DirectoryTreeItem): string[] => {
       if (!folder.children) return [];
-      let files: string[] = [];
+      const files: string[] = [];
       for (const item of folder.children ?? []) {
         if (item.type === 'file') {
           if (isPathWithinRootBoundary(item.path, rootPath)) {
             files.push(item.path);
           }
         } else if (item.type === 'directory') {
-          files = [...files, ...getAllFiles(item)];
+          files.push(...getAllFiles(item));
         }
       }
       return files;
     };

Apply the same pattern to getAllSubFolders (Line 337).


220-230: refreshDirectoryTree has appWindow (globalThis) in its dependency array.

appWindow is assigned as globalThis (Line 164) and never changes. Including it in the useCallback dependency array is harmless but misleading — it suggests the callback would be recreated if the window reference changed, which can't happen. The same applies to selectDirectory (Line 285) and the prevTabRef effect (Line 269).

src/renderer/components/App.tsx (1)

12-31: ErrorBanner: consider auto-dismiss or staleness handling.

The banner relies solely on manual dismiss. If an error is shown while the user is on a different tab (tabs stay mounted now), it may go unnoticed or persist indefinitely. The AppError.timestamp field is already available — you could use it to auto-dismiss after a timeout or visually fade stale errors.

src/renderer/components/SourceTab.tsx (2)

84-84: selectedFilesArray is spread from the Set on every render.

[...selectedFiles] runs on every render regardless of whether selectedFiles changed. For large selections this is wasteful. Wrapping it in useMemo scopes the work to actual changes:

Suggested fix
-  const selectedFilesArray = [...selectedFiles];
+  const selectedFilesArray = useMemo(() => [...selectedFiles], [selectedFiles]);

(Add useMemo to the existing React import on Line 1.)


361-365: "Clear selection" bypasses onBatchSelect, falling back to per-item deselect.

handleSelectAllToggle in FileTree already short-circuits to onBatchSelect for select/deselect-all. Here the clear button manually iterates both Sets and fires individual handlers. For consistency and performance, consider using onBatchSelect:

Suggested fix
              onClick={() => {
-               for (const filePath of selectedFiles) onFileSelect(filePath, false);
-               for (const folderPath of selectedFolders) onFolderSelect(folderPath, false);
+               onBatchSelect([...selectedFiles], [...selectedFolders], false);
                setTotalTokens(0);
tests/unit/components/app.test.tsx (1)

106-106: Consider PropTypes.instanceOf(Set) instead of PropTypes.any.

PropTypes.any silently accepts wrong types (e.g., a plain array), which could mask regressions in the prop contract. Since the production code now passes a Set, a tighter check would catch accidental reversions.

Suggested change
-    selectedFiles: PropTypes.any.isRequired,
+    selectedFiles: PropTypes.instanceOf(Set).isRequired,

Comment thread src/main/index.ts
Comment thread src/renderer/components/ConfigTab.tsx
Comment thread src/renderer/context/AppContext.tsx
Comment thread src/renderer/context/AppContext.tsx
Comment thread vault-agent-env.hcl
- Replace 715-line App.tsx god component with thin shell + AppContext provider
- Introduce useReducer in ConfigTab (19 useState → single reducer)
- Convert selection arrays to Set<string> for O(1) lookups in FileTree
- Add batch select handler for O(n) select-all instead of N state updates
- Fix nested interactive elements: outer <button> → <div role="treeitem">
- Add role="tree" container for a11y compliance
- Replace alert() calls with inline ErrorBanner component
- Preserve tab state via CSS hidden class instead of conditional unmounting
- Extract shared Spinner component, remove @heroicons/react dependency
- Remove window.switchToTab / window.refreshDirectoryTree global mutations
- Remove 500ms localStorage polling interval
- Pipe configContent as prop to SourceTab to prevent stale reads
- Fix show_token_count default mismatch (=== true → !== false)
- Fix SonarQube S3776: extract processEntry helper to reduce walkDirectory
  cognitive complexity from 19 to within allowed limit
- Fix SonarQube S7776: SUPPORTED_PROVIDER_IDS array → Set with .has()
- Fix SonarQube S7758: charCodeAt → codePointAt
- Fix SonarQube S4325: remove unnecessary type assertion
- Update vault-agent-env.hcl template paths and secret fallbacks

All tests pass (318 unit, 3 stress, 4 E2E). Lint 0 warnings. SonarQube 0 issues.
@Mehdi-Bl Mehdi-Bl force-pushed the fix/ui-code-organisation-review branch from 06cb22d to 476dc00 Compare February 14, 2026 04:38
@Mehdi-Bl Mehdi-Bl enabled auto-merge (squash) February 14, 2026 06:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/renderer/context/AppContext.tsx`:
- Around line 235-258: The switchTab callback currently reads the serialized
config from localStorage via localStorage.getItem('configContent') instead of
using the in-memory React state; change switchTab to use the configContent state
value (the same state used by handleAnalyze) when building ProcessingOptions
(parse yaml.parse(configContent) if configContent is a YAML string or use the
already-parsed object) and remove the localStorage read; also add configContent
to switchTab's dependency array so the callback is recreated when state changes;
keep existing error handling and ensure you still respect sanitized api_key
handling elsewhere.
- Around line 176-214: The save-effect currently writes the placeholder '#
Loading configuration...' to localStorage on mount; update the effect that calls
localStorage.setItem('configContent', sanitizeConfigForStorage(configContent))
to skip saving when configContent is the initial placeholder (or while an
"isInitialMount" ref is true), e.g., add a guard checking configContent !== '#
Loading configuration...' (or use a useRef isInitialMount to ignore the first
render) before calling sanitizeConfigForStorage and localStorage.setItem;
reference the useEffect that depends on [configContent], the configContent
state, and the sanitizeConfigForStorage helper to locate and modify the code.
🧹 Nitpick comments (6)
src/renderer/components/App.tsx (1)

120-164: Tab panels preserve state correctly via CSS hidden class.

Using absolute inset-0 with a hidden class toggle is a solid approach to keep all tab components mounted while only showing the active one. This preserves scroll position, form state, and selection state across tab switches.

One minor observation: each tab panel has an identical set of base classes. If these grow, consider extracting a small helper or constant to reduce duplication.

♻️ Optional: extract shared tab panel classes
+const tabPanelBaseClass =
+  'absolute inset-0 overflow-y-auto bg-white dark:bg-gray-800 p-4 text-gray-900 dark:text-gray-100 transition-colors duration-200';
+
 <div
   role='tabpanel'
   id='tabpanel-config'
   aria-labelledby='tab-config'
-  className={`absolute inset-0 overflow-y-auto bg-white dark:bg-gray-800 p-4 text-gray-900 dark:text-gray-100 transition-colors duration-200 ${activeTab === 'config' ? '' : 'hidden'}`}
+  className={`${tabPanelBaseClass} ${activeTab === 'config' ? '' : 'hidden'}`}
 >
src/renderer/components/SourceTab.tsx (1)

84-84: selectedFilesArray is spread on every render.

[...selectedFiles] creates a new array on each render. It's used only inside the token-calculation effect, where it's correctly gated by the selectedFiles dependency. This is fine for typical selection sizes, but for very large repositories (thousands of files), consider memoizing with useMemo:

♻️ Optional: memoize the derived array
-  const selectedFilesArray = [...selectedFiles];
+  const selectedFilesArray = React.useMemo(() => [...selectedFiles], [selectedFiles]);
src/renderer/context/AppContext.tsx (1)

306-398: handleFolderSelect inlines three recursive helpers — consider extracting.

findFolder, getAllSubFolders, and getAllFiles are re-created on every invocation. They're also generally useful tree utilities. Extracting them as standalone functions (outside the component) would improve readability and allow reuse. The rootPath dependency can be passed as a parameter.

This isn't urgent given the current scope, but it would reduce the cognitive complexity of this already large provider.

src/renderer/components/ConfigTab.tsx (1)

138-140: SET_FIELD action value type is loosely typed at the reducer level.

ConfigFormAction's SET_FIELD allows value: ConfigFormState[keyof ConfigFormState], which is a union of all possible field types (boolean | string | ExportFormat | ProviderId | ''). This means the reducer itself won't catch a mismatch like setting a boolean field to a string. The generic setField helper at Line 396 correctly constrains this at call sites, but a direct dispatch({ type: 'SET_FIELD', field: 'useGitignore', value: 'oops' }) would compile without error.

This is a minor type-safety gap since all dispatches go through setField. No action needed now, but worth noting if dispatch is ever exposed directly.

src/renderer/components/FileTree.tsx (2)

83-99: Dual-focusable elements: the treeitem div and the nested checkbox are both in tab order.

With tabIndex={0} on the outer div and the <input type="checkbox"> also natively focusable, keyboard users will encounter two tab stops per tree item. For large trees this significantly increases tab-stop count. Consider tabIndex={-1} on the checkbox (since the div already handles keyboard selection via Enter/Space) or manage focus with roving tabindex on the tree container.

This isn't a blocker — the current behavior is functional — but it's a common accessibility pattern to avoid redundant tab stops in composite widgets like trees.


218-236: getAllPaths uses recursive spread — fine for typical trees.

result.files.push(...subPaths.files) in a recursive function could theoretically hit argument-count limits for Function.prototype.apply on extremely flat trees with hundreds of thousands of files. In practice this is unlikely for directory trees, but if you ever need to harden this, a simple for...of push loop or Array.prototype.concat assignment would avoid the spread.

Comment thread src/renderer/context/AppContext.tsx
Comment thread src/renderer/context/AppContext.tsx Outdated
@Mehdi-Bl

Copy link
Copy Markdown
Contributor Author

Validation update on review threads (post-b651fcc):

  • SourceTab clear-selection performance: already using onBatchSelect([...selectedFiles], [...selectedFolders], false).
  • ConfigTab cancel edge case: handleFolderSelect now switches tab only when selectDirectory() returns true.
  • ConfigTab autosave typing concern: saveConfig is argument-based and stable; autosave effect depends only on checkbox/select fields.
  • ConfigTab/AppContext tree-view default mismatch: aligned (includeTreeView: false in initial form state).
  • AppContext config source-of-truth: switchTab and handleRefreshProcessed now parse in-memory configContent, not localStorage.
  • Initial placeholder overwrite: guarded with INITIAL_CONFIG_PLACEHOLDER check before persisting config.
  • Symlink behavior in main process: intentionally kept strict skip-all policy and documented inline for clarity.
  • vault-agent-env.hcl: added explicit comments for intentional hardcoded local paths (required for local Sonar bootstrap) and documented legacy token fallback precedence.

One stale thread references docs/plan/ui_fixes.md; that file is not in this PR’s changed-file set, so no codepath in this PR can apply that suggestion.

@sonarqubecloud

Copy link
Copy Markdown

@Mehdi-Bl Mehdi-Bl merged commit 6522117 into main Feb 14, 2026
25 checks passed
@Mehdi-Bl Mehdi-Bl deleted the fix/ui-code-organisation-review branch February 14, 2026 07:26
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.

1 participant