Skip to content

refactor(documents-panel): decompose DocumentsPanel#1099

Merged
reachrazamair merged 3 commits into
rcfrom
refactoring
Jun 18, 2026
Merged

refactor(documents-panel): decompose DocumentsPanel#1099
reachrazamair merged 3 commits into
rcfrom
refactoring

Conversation

@reachrazamair

@reachrazamair reachrazamair commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Decomposes the monolithic DocumentsPanel.tsx into a focused DocumentsPanel/ module with a small coordinator shell, render components, hooks, utilities, and a barrel export.

The public import surface is preserved:

  • import { DocumentsPanel } from './DocumentsPanel'
  • import('./DocumentsPanel').DocTreeNode

Changes

  • Replaced the 1,293-line DocumentsPanel.tsx with a 134-line shell.
  • Extracted selector UI, document rows, loop controls, missing-doc warning, task badges, and copy-drag indicator into focused components.
  • Extracted document selection, selector refresh, row actions, and drag reorder behavior into hooks.
  • Added local document tree and count utilities to preserve the existing DocTreeNode.path behavior.
  • Preserved selected document ordering, duplicate/reset rules, drag move/copy behavior, loop controls, task badges, missing-doc handling, and modal layering.
  • Fixed the selector refresh message timer so it now clears after 3 seconds as originally intended.

Tests

Added 41 focused renderer tests covering:

  • document tree helpers
  • count helpers
  • document selection hook
  • selector refresh hook
  • selected-list actions
  • drag reorder/copy behavior
  • selector modal
  • selected document list
  • loop controls
  • public DocumentsPanel wiring

Verification run:

  • npm run test -- src/__tests__/renderer/components/DocumentsPanel src/__tests__/renderer/components/BatchRunnerModal.test.tsx src/__tests__/renderer/constants/modalPriorities.test.ts
  • npm run test -- src/__tests__/renderer/hooks/batch src/__tests__/renderer/hooks/useBatchProcessor.test.ts src/__tests__/cli/services/batch-processor.test.ts src/__tests__/cli/services/playbooks.test.ts src/__tests__/cli/commands/run-playbook.test.ts src/__tests__/main/ipc/handlers/playbooks.test.ts
  • npm run format:check
  • npm run lint
  • npm run lint:eslint
  • npx tsc --noEmit --pretty
  • npm run test

Full suite result:

  • 31,312 passing
  • 108 skipped
  • 0 failing

Manual QA

  • Open Batch Runner from Auto Run and confirm existing selected docs, task counts, and prompt/run controls still render.
  • Open Add Docs, then close by X, Cancel, backdrop, and Escape.
  • Confirm selector inner-content clicks do not close the modal.
  • Select and deselect docs in the flat selector.
  • Use Select All and Deselect All.
  • Add selected docs and confirm ordering is unchanged.
  • In tree mode, expand/collapse folders and select/deselect folders.
  • Verify partial and full folder checkbox states.
  • Refresh selector docs and verify added/removed messages plus auto-clear.
  • Remove normal and missing docs.
  • Enable reset, duplicate a doc, confirm duplicate reset lock, remove duplicate, then disable reset.
  • Drag reorder docs up and down.
  • Ctrl/Meta-drag to copy and confirm reset-on-copy behavior.
  • Toggle loop mode, switch infinity/max, and adjust max loops from 1-25.
  • Load, modify, save, discard, and run a multi-doc playbook.
  • Smoke dark/light themes and long nested filenames.

Summary by CodeRabbit

  • New Features / Improvements
    • Added a complete Documents panel with selector modal (flat list or folder tree), task-count badges, missing-document warnings, loop controls, and “copy-drag” cursor feedback.
    • Enabled drag-and-drop reordering with modifier-assisted copy/duplicate behavior for selected documents.
    • Improved theme import color validation and optional color handling.
  • Tests
    • Added comprehensive Vitest + React Testing Library coverage for the Documents panel, selector modal, list actions, selection state, drag-and-drop reordering, refresh messaging, loop controls, and count/tree utilities.
  • Refactor
    • Split the Documents panel into modular components, hooks, and utilities for better separation of concerns.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 05ebd291-e5e2-4fa1-be6c-5ec397565f7c

📥 Commits

Reviewing files that changed from the base of the PR and between 87524c0 and 8ebdeaf.

📒 Files selected for processing (2)
  • src/__tests__/renderer/components/CustomThemeBuilder.test.tsx
  • src/renderer/components/CustomThemeBuilder.tsx

📝 Walkthrough

Walkthrough

The monolithic DocumentsPanel.tsx (1,293 lines) is deleted and replaced by a new DocumentsPanel/ module containing shared TypeScript interfaces (types.ts), pure utility functions (documentCounts.ts, documentTree.ts), four custom hooks (useDocumentListActions, useDocumentSelection, useDocumentSelectorRefresh, useDocumentDragReorder), eight subcomponents, a slimmed-down orchestrator, barrel index.ts files, and Vitest test suites covering every unit. CustomThemeBuilder is updated to support optional theme colors.

Changes

DocumentsPanel Modular Refactor

Layer / File(s) Summary
Types, count utilities, and tree utilities
src/renderer/components/DocumentsPanel/types.ts, src/renderer/components/DocumentsPanel/utils/documentCounts.ts, src/renderer/components/DocumentsPanel/utils/documentTree.ts, src/renderer/components/DocumentsPanel/utils/index.ts, src/__tests__/.../utils/documentCounts.test.ts, src/__tests__/.../utils/documentTree.test.ts
Introduces DocTreeNode, DocumentsPanelProps, DocumentSelectorModalProps, and DragHandlers interfaces. Adds seven documentCounts helpers and four documentTree helpers for task-count aggregation, missing-doc detection, folder selection state, and task summation. Utils barrel re-exports both modules. Tests cover all helpers.
Custom hooks: list actions, selection, refresh, and drag-reorder
src/renderer/components/DocumentsPanel/hooks/useDocumentListActions.ts, src/renderer/components/DocumentsPanel/hooks/useDocumentSelection.ts, src/renderer/components/DocumentsPanel/hooks/useDocumentSelectorRefresh.ts, src/renderer/components/DocumentsPanel/hooks/useDocumentDragReorder.ts, src/renderer/components/DocumentsPanel/hooks/index.ts, src/__tests__/.../hooks/use*.test.tsx?
useDocumentListActions provides memoized remove/toggleReset/duplicate/add handlers. useDocumentSelection manages Set-based selection and folder expansion with derived counts. useDocumentSelectorRefresh tracks refreshing state and produces timed delta messages. useDocumentDragReorder implements copy-drag (Ctrl/Meta) with bounding-box insertion-index geometry and a drop-guard ref. Barrel index re-exports all four. Each hook has a dedicated test suite.
Primitive UI components
src/renderer/components/DocumentsPanel/components/TaskCountBadge.tsx, src/renderer/components/DocumentsPanel/components/MissingDocumentsWarning.tsx, src/renderer/components/DocumentsPanel/components/CopyDragIndicator.tsx
TaskCountBadge renders a themed badge with zero/success color states and a loading ellipsis. MissingDocumentsWarning conditionally renders a pluralized skip-alert. CopyDragIndicator renders a fixed circular plus icon at cursorPosition only when isCopyDrag is true.
DocumentRow and DocumentList rendering
src/renderer/components/DocumentsPanel/components/DocumentRow.tsx, src/renderer/components/DocumentsPanel/components/DocumentList.tsx, src/__tests__/.../components/DocumentList.test.tsx
DocumentRow renders a draggable row with DropIndicator overlays, conditional reset/duplicate/remove controls, a Missing badge, and TaskCountBadge; drag is disabled for missing documents. DocumentList maps documents to rows, wires all drag handlers, and adds loop-indicator shapes when loopEnabled. Tests cover empty state, row actions, and loop/copy-drag DOM indicators.
Document selector modal: flat list, tree, footer, and modal
src/renderer/components/DocumentsPanel/components/DocumentSelectorFlatList.tsx, src/renderer/components/DocumentsPanel/components/DocumentSelectorTree.tsx, src/renderer/components/DocumentsPanel/components/DocumentSelectorFooter.tsx, src/renderer/components/DocumentsPanel/components/DocumentSelectorModal.tsx, src/__tests__/.../components/DocumentSelectorModal.test.tsx
DocumentSelectorFlatList renders togglable document buttons with checkmarks and TaskCountBadge. DocumentSelectorTree renders recursive folder/file nodes with full/partial selection indicators and badges. DocumentSelectorFooter renders Cancel and Add buttons with pluralized labels. DocumentSelectorModal composes all three, uses useModalLayer, and conditionally renders tree vs flat vs empty body. Tests cover flat selection, tree toggling, empty state, keyboard close, refresh, and backdrop behavior.
LoopControls component
src/renderer/components/DocumentsPanel/components/LoopControls.tsx, src/__tests__/.../components/LoopControls.test.tsx
LoopControls shows a single-document hint when fewer than two documents are present. For multiple documents it renders a Loop toggle, infinity/max buttons, and a range slider for finite iterations, plus a task-count summary with missing-doc text. Tests verify single-doc hint, loop toggle, max/infinity switching, slider updates, and missing-doc summary.
DocumentsPanel orchestrator and public API
src/renderer/components/DocumentsPanel/DocumentsPanel.tsx, src/renderer/components/DocumentsPanel/index.ts, src/renderer/components/DocumentsPanel/components/index.ts, src/__tests__/.../DocumentsPanel.test.tsx
The new DocumentsPanel wires useDocumentListActions and useDocumentDragReorder, computes missingDocCount/hasMissingDocs/totalTaskCount, and renders DocumentList, LoopControls, MissingDocumentsWarning, conditional DocumentSelectorModal, and CopyDragIndicator. The public index re-exports DocumentsPanel and DocTreeNode. Integration tests verify rendering, the Add Docs selector flow, and reset/loop control wiring.

CustomThemeBuilder Optional Color Support

Layer / File(s) Summary
Color validation and optional color key handling
src/renderer/components/CustomThemeBuilder.tsx, src/__tests__/renderer/components/CustomThemeBuilder.test.tsx
Switches isValidColor to use document.createElement('span').style for validation instead of Option().style. Introduces OPTIONAL_IMPORT_COLOR_KEYS set marking bgTitleBar as optional during theme import. Updates validation to compute required keys by excluding optional ones and validates all configured keys for invalid colors when present in imported data. Test verifies theme import succeeds when window.Option is unavailable.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • RunMaestro/Maestro#1103: Introduces the new bgTitleBar theme token and its wiring, directly related to the CustomThemeBuilder updates that treat it as optional.

Suggested labels

refactor

Poem

🐰 Hoppity-hop, one file was too wide,
I split it to pieces and tidied inside!
A hook for each wiggle, a component per ear,
With tests to make sure every row stays clear.
The warren is tidy — refactor complete,
Type-safe carrots, now isn't that neat? 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "refactor(documents-panel): decompose DocumentsPanel" accurately describes the primary refactoring goal—breaking down a monolithic 1,293-line DocumentsPanel into a modular structure with components, hooks, and utilities.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactoring

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.

@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: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/renderer/components/DocumentsPanel/components/DocumentSelectorModal.tsx`:
- Around line 68-80: The modal container div that starts at line 68 is missing
required accessibility semantics and styling. Add role="dialog" and
aria-modal="true" attributes to the outer div container to properly declare
dialog semantics, and add the select-none class to the className string on the
same container to comply with the repository's styling guidelines for
click-primary modals.

In `@src/renderer/components/DocumentsPanel/components/DocumentSelectorTree.tsx`:
- Around line 68-79: The button element used for the onToggleFolder handler is
missing accessibility attributes required for assistive technology users. Add an
aria-label attribute to provide an accessible name describing the button's
function (for example, indicating it toggles folder expansion), and add an
aria-expanded attribute that reflects the current isExpanded state. This ensures
screen reader users understand the button's purpose and can perceive the current
expansion state of the folder.

In `@src/renderer/components/DocumentsPanel/components/LoopControls.tsx`:
- Around line 33-37: The condition for displaying the loop hint in
LoopControls.tsx only checks for exactly one document using documents.length ===
1, but it should show the hint whenever there are fewer than two documents
(including zero documents). Change the condition from documents.length === 1 to
documents.length < 2 so that users see the guidance message "You can enable
loops with two or more documents" in both cases where loop controls cannot be
enabled due to insufficient documents.

In `@src/renderer/components/DocumentsPanel/hooks/useDocumentDragReorder.ts`:
- Around line 65-66: The `handleDragLeave` function is currently a no-op, which
allows a previously computed `dropTargetIndex` to persist after the user leaves
the droppable area. This can cause `handleDragEnd` to still execute
`performDropOperation()` with a stale drop target. Update the `handleDragLeave`
callback to reset or clear the `dropTargetIndex` state so that leaving the
droppable area properly cancels any pending drop operation and prevents
unintended reordering.

In `@src/renderer/components/DocumentsPanel/hooks/useDocumentSelection.ts`:
- Line 73: The `allSelected` variable on line 73 incorrectly compares set size
against array length, which fails when selectedDocs contains filenames not
present in allDocuments. Instead of checking if selectedDocs.size equals
allDocuments.length, change the logic to verify that every document in
allDocuments is actually included in the selectedDocs set. You need to iterate
through allDocuments and check if each document is present in selectedDocs,
returning true only when all selectable documents are selected regardless of any
extra entries in selectedDocs.

In `@src/renderer/components/DocumentsPanel/hooks/useDocumentSelectorRefresh.ts`:
- Around line 16-25: The handleRefresh function does not handle errors from
onRefresh(), leaving the refreshing state stuck on true if the promise rejects.
Wrap the await onRefresh() call in a try-catch block and ensure
setRefreshing(false) is called in all paths by placing the setTimeout that
resets the state in a finally block, so it executes regardless of whether
onRefresh() succeeds or fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2c7aed4b-4931-49ce-8cf9-1020caa06db8

📥 Commits

Reviewing files that changed from the base of the PR and between b176813 and aacedd1.

📒 Files selected for processing (33)
  • src/__tests__/renderer/components/DocumentsPanel/DocumentsPanel.test.tsx
  • src/__tests__/renderer/components/DocumentsPanel/components/DocumentList.test.tsx
  • src/__tests__/renderer/components/DocumentsPanel/components/DocumentSelectorModal.test.tsx
  • src/__tests__/renderer/components/DocumentsPanel/components/LoopControls.test.tsx
  • src/__tests__/renderer/components/DocumentsPanel/hooks/useDocumentDragReorder.test.tsx
  • src/__tests__/renderer/components/DocumentsPanel/hooks/useDocumentListActions.test.tsx
  • src/__tests__/renderer/components/DocumentsPanel/hooks/useDocumentSelection.test.tsx
  • src/__tests__/renderer/components/DocumentsPanel/hooks/useDocumentSelectorRefresh.test.tsx
  • src/__tests__/renderer/components/DocumentsPanel/utils/documentCounts.test.ts
  • src/__tests__/renderer/components/DocumentsPanel/utils/documentTree.test.ts
  • src/renderer/components/DocumentsPanel.tsx
  • src/renderer/components/DocumentsPanel/DocumentsPanel.tsx
  • src/renderer/components/DocumentsPanel/components/CopyDragIndicator.tsx
  • src/renderer/components/DocumentsPanel/components/DocumentList.tsx
  • src/renderer/components/DocumentsPanel/components/DocumentRow.tsx
  • src/renderer/components/DocumentsPanel/components/DocumentSelectorFlatList.tsx
  • src/renderer/components/DocumentsPanel/components/DocumentSelectorFooter.tsx
  • src/renderer/components/DocumentsPanel/components/DocumentSelectorModal.tsx
  • src/renderer/components/DocumentsPanel/components/DocumentSelectorTree.tsx
  • src/renderer/components/DocumentsPanel/components/LoopControls.tsx
  • src/renderer/components/DocumentsPanel/components/MissingDocumentsWarning.tsx
  • src/renderer/components/DocumentsPanel/components/TaskCountBadge.tsx
  • src/renderer/components/DocumentsPanel/components/index.ts
  • src/renderer/components/DocumentsPanel/hooks/index.ts
  • src/renderer/components/DocumentsPanel/hooks/useDocumentDragReorder.ts
  • src/renderer/components/DocumentsPanel/hooks/useDocumentListActions.ts
  • src/renderer/components/DocumentsPanel/hooks/useDocumentSelection.ts
  • src/renderer/components/DocumentsPanel/hooks/useDocumentSelectorRefresh.ts
  • src/renderer/components/DocumentsPanel/index.ts
  • src/renderer/components/DocumentsPanel/types.ts
  • src/renderer/components/DocumentsPanel/utils/documentCounts.ts
  • src/renderer/components/DocumentsPanel/utils/documentTree.ts
  • src/renderer/components/DocumentsPanel/utils/index.ts
💤 Files with no reviewable changes (1)
  • src/renderer/components/DocumentsPanel.tsx

Comment thread src/renderer/components/DocumentsPanel/components/LoopControls.tsx Outdated
Comment thread src/renderer/components/DocumentsPanel/hooks/useDocumentDragReorder.ts Outdated
Comment thread src/renderer/components/DocumentsPanel/hooks/useDocumentSelection.ts Outdated
@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown

Greptile Summary

This PR decomposes a 1,293-line monolithic DocumentsPanel.tsx into a focused module with a coordinator shell, sub-components, hooks, and utilities, while preserving the original public import surface. It also fixes a timer bug in the selector refresh flow where prevDocCount being held in state (rather than a ref) caused every setPrevDocCount call to re-trigger the effect cleanup and immediately cancel the 3-second message timer.

  • All drag-reorder, copy-drag, loop controls, missing-doc warnings, and task-badge rendering have been split into their own dedicated files with matching tests.
  • A new useDocumentSelectorRefresh hook replaces the state-based prevDocCount with prevDocCountRef, so the auto-clear timer now survives the count update without being cancelled on re-render.
  • handleAddSelectedDocs preserves existing document ordering (including duplicates) and appends only net-new files, matching the original behavior.

Confidence Score: 4/5

The refactor faithfully preserves the original behaviour including the drag deduplication guard, reset-lock rules, and modal layer registration; the timer fix is a genuine improvement.

All findings are non-blocking style issues: dead code in the refresh hook, a missing select-none on the click-driven modal root, and a duplicated SVG helper. No behavioral regressions were identified; the 41 tests added cover the critical paths.

useDocumentSelectorRefresh.ts (dead else branch), DocumentSelectorModal.tsx (missing select-none), DocumentSelectorFlatList.tsx / DocumentSelectorTree.tsx (duplicated CheckMark).

Important Files Changed

Filename Overview
src/renderer/components/DocumentsPanel/hooks/useDocumentSelectorRefresh.ts Fixes the timer-cancel regression by switching prevDocCount from state to a ref; contains one unreachable else branch (diff === 0 can never be true inside the prevDocCountRef.current !== allDocumentsLength guard).
src/renderer/components/DocumentsPanel/hooks/useDocumentDragReorder.ts Faithful extraction of drag/drop logic with correct deduplication via dropPerformedRef; handleDragLeave is an intentional no-op (consistent with original) to avoid flicker as cursor moves between rows.
src/renderer/components/DocumentsPanel/hooks/useDocumentListActions.ts Clean extraction of remove/toggle/duplicate/add actions; handleAddSelectedDocs correctly preserves ordering and deduplication against existing filenames.
src/renderer/components/DocumentsPanel/hooks/useDocumentSelection.ts Clean extraction of doc/folder selection logic; allSelected, totalTaskCount, and selectedTaskCount are derived correctly with useMemo.
src/renderer/components/DocumentsPanel/components/DocumentSelectorModal.tsx Correct useModalLayer wiring and onCloseRef pattern; missing select-none on the root container per CLAUDE.md click-driven modal guidelines.
src/renderer/components/DocumentsPanel/components/DocumentSelectorFlatList.tsx Correct flat-list rendering; CheckMark SVG is duplicated verbatim from DocumentSelectorTree.tsx instead of being shared.
src/renderer/components/DocumentsPanel/components/DocumentSelectorTree.tsx Correct recursive tree rendering with folder expand/collapse and partial-selection indicators; CheckMark SVG is duplicated from DocumentSelectorFlatList.tsx.
src/renderer/components/DocumentsPanel/components/DocumentRow.tsx Faithful extraction of per-row rendering with correct drop-indicator placement and reset/duplicate button visibility logic.
src/renderer/components/DocumentsPanel/DocumentsPanel.tsx Clean coordinator shell; correctly composes all extracted hooks and components with no behavioral change.
src/renderer/components/DocumentsPanel/utils/documentCounts.ts Correct utility functions for task counts, missing docs, and reset-lock checks.
src/renderer/components/DocumentsPanel/utils/documentTree.ts Correct recursive file-listing and folder selection-state helpers.
src/renderer/components/DocumentsPanel/index.ts Barrel export preserves the original public surface (DocumentsPanel + DocTreeNode type).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    BP[BatchRunnerModal] -->|props| DP[DocumentsPanel coordinator]

    DP --> UDR[useDocumentDragReorder]
    DP --> UDLA[useDocumentListActions]

    DP --> DL[DocumentList]
    DP --> LC[LoopControls]
    DP --> MDW[MissingDocumentsWarning]
    DP --> DSM[DocumentSelectorModal]
    DP --> CDI[CopyDragIndicator]

    DL --> DR[DocumentRow xN]

    DSM --> UDS[useDocumentSelection]
    DSM --> UDSR[useDocumentSelectorRefresh]
    DSM --> DSFL[DocumentSelectorFlatList]
    DSM --> DSTL[DocumentSelectorTree]
    DSM --> DSF[DocumentSelectorFooter]

    UDR -->|draggedId, dropTargetIndex, handlers| DL
    UDLA -->|handleAddSelectedDocs| DSM
    UDLA -->|handleRemove, handleToggleReset, handleDuplicate| DL

    subgraph utils
        DC[documentCounts.ts]
        DT[documentTree.ts]
    end

    UDS --> DC
    UDS --> DT
    DR --> DC
    DSTL --> DT
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    BP[BatchRunnerModal] -->|props| DP[DocumentsPanel coordinator]

    DP --> UDR[useDocumentDragReorder]
    DP --> UDLA[useDocumentListActions]

    DP --> DL[DocumentList]
    DP --> LC[LoopControls]
    DP --> MDW[MissingDocumentsWarning]
    DP --> DSM[DocumentSelectorModal]
    DP --> CDI[CopyDragIndicator]

    DL --> DR[DocumentRow xN]

    DSM --> UDS[useDocumentSelection]
    DSM --> UDSR[useDocumentSelectorRefresh]
    DSM --> DSFL[DocumentSelectorFlatList]
    DSM --> DSTL[DocumentSelectorTree]
    DSM --> DSF[DocumentSelectorFooter]

    UDR -->|draggedId, dropTargetIndex, handlers| DL
    UDLA -->|handleAddSelectedDocs| DSM
    UDLA -->|handleRemove, handleToggleReset, handleDuplicate| DL

    subgraph utils
        DC[documentCounts.ts]
        DT[documentTree.ts]
    end

    UDS --> DC
    UDS --> DT
    DR --> DC
    DSTL --> DT
Loading

Reviews (1): Last reviewed commit: "refactor(documents-panel): decompose Doc..." | Re-trigger Greptile

Comment on lines +36 to +42
message = 'No changes';
}
setRefreshMessage(message);
prevDocCountRef.current = allDocumentsLength;

const timer = setTimeout(() => setRefreshMessage(null), 3000);
return () => clearTimeout(timer);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Unreachable else branch (diff === 0)

The outer guard prevDocCountRef.current !== allDocumentsLength already ensures diff is never zero, so else { message = 'No changes'; } can never execute. This dead branch was inherited from the original code (where it was equally unreachable) and can simply be removed — a no-change refresh will never enter this if block at all, so refreshMessage stays null as expected.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +54 to +57
<div
className="fixed inset-0 bg-black/50 flex items-center justify-center z-[10000]"
onClick={onClose}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Missing select-none on click-driven modal root

Per the CLAUDE.md guidelines, click-driven modals (buttons, list rows, toggles) should have select-none on their root container to prevent accidental drag-selection during normal interactions. The document selector is entirely click-driven — the document list rows, checkboxes, refresh button, and footer actions are all click targets. This was also missing in the original monolithic component; extracting it into a standalone component is a good moment to add it.

Suggested change
<div
className="fixed inset-0 bg-black/50 flex items-center justify-center z-[10000]"
onClick={onClose}
>
<div
className="fixed inset-0 bg-black/50 flex items-center justify-center z-[10000] select-none"
onClick={onClose}
>

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +13 to +24
function CheckMark() {
return (
<svg className="w-3 h-3 text-white" viewBox="0 0 12 12" fill="none">
<path
d="M2 6L5 9L10 3"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 CheckMark SVG component duplicated

The same CheckMark SVG is defined verbatim in DocumentSelectorTree.tsx (line 30-38). CLAUDE.md's DRY guidance says to extract shared helpers rather than copy them. Both files are siblings in components/, so extracting this into a small shared file (e.g., components/CheckMark.tsx) or into the barrel index would remove the duplication.

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@pedramamini

Copy link
Copy Markdown
Collaborator

Thanks for this, @reachrazamair! This is a genuinely clean decomposition. The coordinator shell is tidy, the public import surface is preserved, and the 41 new tests plus a green full suite give real confidence that behavior was kept intact. The prevDocCount to prevDocCountRef timer fix is a nice catch.

I went through both AI reviews (Greptile and CodeRabbit) and verified each finding against the code. Most of what they flagged is faithfully-preserved original behavior, so I would not touch those. But a few items genuinely line up with this repo's own conventions and with the maintainability goal of this PR, so I would like those addressed before merge:

Please address:

  1. select-none on the selector modal root (DocumentSelectorModal.tsx). The selector is click-driven (rows, checkboxes, refresh, footer actions), so per our CLAUDE.md modal guideline the root container should carry select-none (with select-text on any content subtree if needed). Both bots flagged this, and since you are freshly extracting the modal, it is the right moment to add it.

  2. Deduplicate the CheckMark SVG. It is copied verbatim into both DocumentSelectorFlatList.tsx and DocumentSelectorTree.tsx. CLAUDE.md calls duplication the Internal Logging #1 maintenance burden, and since this PR is specifically about maintainability, extracting it into a small shared components/CheckMark.tsx (or the components barrel) fits the spirit of the change.

  3. Drop the dead else branch in useDocumentSelectorRefresh.ts (lines 35-37). The outer guard prevDocCountRef.current !== allDocumentsLength means diff is never 0, so the 'No changes' branch is unreachable. It is inherited from the original, but this is a good moment to remove it.

Worth considering (your call):

  • handleRefresh in useDocumentSelectorRefresh.ts leaves refreshing stuck on true if onRefresh() rejects. A try { await onRefresh(); } finally { setTimeout(...) } would keep the UI from wedging on a failed refresh.
  • allSelected (useDocumentSelection.ts:73) compares set size to array length. Switching to allDocuments.length > 0 && allDocuments.every((f) => selectedDocs.has(f)) matches what toggleFolderSelection already does on line 58 and is robust to selected-but-missing docs.

Please skip (these preserve original behavior):

  • The handleDragLeave no-op (useDocumentDragReorder.ts). Greptile is right that this is the intentional anti-flicker behavior from the original. Clearing dropTargetIndex here would be a behavior change, not a fix.
  • The LoopControls documents.length === 1 condition. This matches the original; < 2 would be a behavior change. Leave it unless you intend that as a separate UX tweak.
  • The aria-* suggestions are fine to add if you like, but they are not blockers.

There are no merge conflicts, so once those land I am happy to approve. Thanks again for the careful work and the thorough manual QA list.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/renderer/components/DocumentsPanel/hooks/useDocumentSelectorRefresh.ts (1)

39-40: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Remove unreachable 'No changes' branch.

The outer guard at line 32 (prevDocCountRef.current !== allDocumentsLength) ensures diff is always non-zero when this code executes. The diff === 0 case can never be reached, making this dead code.

🧹 Proposed fix to remove dead code
 			if (diff > 0) {
 				message = `Found ${diff} new document${diff === 1 ? '' : 's'}`;
-			} else if (diff < 0) {
+			} else {
 				message = `${Math.abs(diff)} document${Math.abs(diff) === 1 ? '' : 's'} removed`;
-			} else {
-				message = 'No changes';
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/components/DocumentsPanel/hooks/useDocumentSelectorRefresh.ts`
around lines 39 - 40, The else branch at lines 39-40 that sets message to 'No
changes' is unreachable dead code because the outer guard condition at line 32
(prevDocCountRef.current !== allDocumentsLength) ensures the diff is always
non-zero when this code path executes. Remove the entire else branch and the 'No
changes' assignment, keeping only the necessary logic for handling the non-zero
diff case. Ensure the remaining if-else structure (if any) remains logically
sound.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/renderer/components/DocumentsPanel/hooks/useDocumentSelectorRefresh.ts`:
- Around line 39-40: The else branch at lines 39-40 that sets message to 'No
changes' is unreachable dead code because the outer guard condition at line 32
(prevDocCountRef.current !== allDocumentsLength) ensures the diff is always
non-zero when this code path executes. Remove the entire else branch and the 'No
changes' assignment, keeping only the necessary logic for handling the non-zero
diff case. Ensure the remaining if-else structure (if any) remains logically
sound.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 855c335d-64b1-4d63-894c-37ef8f4e8f27

📥 Commits

Reviewing files that changed from the base of the PR and between aacedd1 and 87524c0.

📒 Files selected for processing (12)
  • src/__tests__/renderer/components/DocumentsPanel/components/DocumentSelectorModal.test.tsx
  • src/__tests__/renderer/components/DocumentsPanel/components/LoopControls.test.tsx
  • src/__tests__/renderer/components/DocumentsPanel/hooks/useDocumentDragReorder.test.tsx
  • src/__tests__/renderer/components/DocumentsPanel/hooks/useDocumentSelection.test.tsx
  • src/__tests__/renderer/components/DocumentsPanel/hooks/useDocumentSelectorRefresh.test.tsx
  • src/renderer/components/DocumentsPanel/components/DocumentSelectorModal.tsx
  • src/renderer/components/DocumentsPanel/components/DocumentSelectorTree.tsx
  • src/renderer/components/DocumentsPanel/components/LoopControls.tsx
  • src/renderer/components/DocumentsPanel/hooks/useDocumentDragReorder.ts
  • src/renderer/components/DocumentsPanel/hooks/useDocumentSelection.ts
  • src/renderer/components/DocumentsPanel/hooks/useDocumentSelectorRefresh.ts
  • src/renderer/components/DocumentsPanel/types.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/tests/renderer/components/DocumentsPanel/hooks/useDocumentSelectorRefresh.test.tsx
  • src/tests/renderer/components/DocumentsPanel/hooks/useDocumentSelection.test.tsx
  • src/renderer/components/DocumentsPanel/hooks/useDocumentSelection.ts
  • src/renderer/components/DocumentsPanel/types.ts
  • src/renderer/components/DocumentsPanel/components/DocumentSelectorModal.tsx
  • src/tests/renderer/components/DocumentsPanel/hooks/useDocumentDragReorder.test.tsx
  • src/tests/renderer/components/DocumentsPanel/components/DocumentSelectorModal.test.tsx
  • src/renderer/components/DocumentsPanel/components/DocumentSelectorTree.tsx
  • src/renderer/components/DocumentsPanel/components/LoopControls.tsx

@reachrazamair

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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