Skip to content

Migrate file browser to modular component construction with parent and child API access.#11

Merged
vishalpalaniappan merged 65 commits intomainfrom
modular-filebrowser
Mar 24, 2026
Merged

Migrate file browser to modular component construction with parent and child API access.#11
vishalpalaniappan merged 65 commits intomainfrom
modular-filebrowser

Conversation

@vishalpalaniappan
Copy link
Copy Markdown
Owner

@vishalpalaniappan vishalpalaniappan commented Mar 20, 2026

This PR migrates the file browser component to use the same component structure used in PR #10.

Please see that PR for details on the structure. In terms of this component, the api exposes functions to add a file tree and to select a node in it. The component also accepts a callback which is invoked when the user selects a file from the UI.

This PR also settles on a file structure for the file browser and mirrors that structure in editor because the file browser nodes will get dropped in the editor. It also cleans up the UI of both components and makes some other minor functional changes like setting a UID in the reducer and prefixing keys with state UID so there are no clashes.

Validation Performed

Created storybook story that demonstrate the use of the file browser. Tested file select works, file select callback works and collapse tree works. Also imported component into workbench and verified that it works.

Summary by CodeRabbit

  • New Features

    • Revamped File Browser with a driven tree view, selectable nodes, draggable rows, and a new sample workspace.
  • Improvements

    • Editor: improved layout/resizing, tab insertion/activation behavior, updated tab visuals, and editor display tweaks (minimap, word wrap, read-only).
  • Removals

    • Viewer UI and associated stories removed.
  • Chores

    • Package version bumped and Storybook fixtures updated.

@vishalpalaniappan vishalpalaniappan marked this pull request as draft March 20, 2026 17:31
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaced Viewer (components, styles, stories) and removed its exports; refactored FileBrowser into a forwardRef + reducer + context-driven implementation with new Tree/TreeNode draggable UI; Editor tab IDs moved from id→uid and addTab API changed; Monaco instances gained ResizeObserver/layout handling; stories and fixtures updated; package version bumped and two dependencies removed.

Changes

Cohort / File(s) Summary
FileBrowser: core & state
src/components/FileBrowser/FileBrowser.jsx, src/components/FileBrowser/FileBrowserContext.js, src/components/FileBrowser/FileBrowserReducer.js, src/components/FileBrowser/helper.js, src/components/FileBrowser/FileBrowser.scss
Added FileBrowserContext; rewrote FileBrowser to forwardRef + useReducer; added reducer/initialState (uid, ADD_FILE_TREE, SELECT_NODE, RESET_STATE); removed helper selectNode; stylesheet simplified to scrollable dark container.
FileBrowser: tree UI
src/components/FileBrowser/Tree/Tree.jsx, src/components/FileBrowser/TreeNode/TreeNode.jsx, src/components/FileBrowser/TreeNode/TreeNode.scss
Added Tree component and TreeNode/TreeNodePreview with draggable payloads (includes preview), click → SELECT_NODE; new node row styles and indent/icons.
Editor: tabs, reducer, API & styling
src/components/Editor/Editor.jsx, src/components/Editor/EditorReducer.js, src/components/Editor/Editor.scss, src/components/Editor/Tabs/.../Tabs.jsx, src/components/Editor/Tabs/Tabs.scss, src/components/Editor/Tabs/Tab/Tab.jsx, src/components/Editor/Tabs/Tab/Tab.scss, src/components/Editor/Tabs/Gutter/Gutter.jsx
Switched tab identity to uid; addTab now accepts (tab, index); removed getPreviewElement; added RESET_STATE; reducer updated for uid-based add/select/close/move; Tab prop names/markup and CSS/class renames; DnD types updated.
Monaco editor instances & layout
src/components/Editor/MonacoInstance/MonacoInstance.jsx, src/components/Editor/MonacoInstance/MonacoInstance.scss
Wrapped editor in .editor-container; added containerRef + ResizeObserver with rAF-throttled editor.layout(); explicit layout on mount and after content updates; adjusted editor options (minimap, padding, wordWrap, readOnly, automaticLayout:false).
Viewer removal
src/components/Viewer/**, src/components/Viewer/index.js, src/index.js
Removed Viewer component, its Tabs/Tab/Monaco modules, styles, stories, and the Viewer re-export from package entrypoint.
Stories & fixtures
src/stories/Editor.stories.js, src/stories/FileBrowser.stories.js, src/stories/Viewer.stories.js, src/stories/data/FileBrowser/*, src/stories/FileBrowserStories.scss
Refactored stories to use new FileBrowser forwardRef API and manual drag-preview overlay; removed DragOverlay usage; replaced FileBrowser fixtures with workspace_sample.json; removed Viewer stories and some fixtures.
Package manifest
package.json
Bumped version 0.0.7-dev0.0.18-dev; removed bootstrap and react-bootstrap from dependencies (kept react-bootstrap-icons).

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer / Story
  participant FileBrowser as FileBrowser (forwardRef)
  participant Reducer as FileBrowserReducer
  participant Tree as Tree
  participant Node as TreeNode
  participant App as Consumer (onSelectFile)

  Dev->>FileBrowser: ref.addFileTree(tree)
  FileBrowser->>Reducer: dispatch(ADD_FILE_TREE, tree)
  Reducer-->>FileBrowser: state { flattenedTree, collapsedTree }
  FileBrowser->>Tree: provide state via FileBrowserContext
  Tree->>Node: render nodes from state.collapsedTree
  Dev->>Node: click file node
  Node->>FileBrowser: dispatch(SELECT_NODE, node)
  FileBrowser->>Reducer: dispatch(SELECT_NODE, node)
  Reducer-->>FileBrowser: state { selectedNode }
  FileBrowser->>App: effect triggers onSelectFile(selectedNode) if file
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through code and nested each tree,
Context in my pouch, a reducer for me,
Tabs learned new uids, editors stretch and align,
Stories trimmed leaves and sample files shine,
A little rabbit cheers: tidy and fine.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Migrate file browser to modular component construction with parent and child API access' directly reflects the main change: converting FileBrowser to a modular, ref-based architecture with reducer-driven state and exposed imperative APIs, mirroring the Editor component structure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 modular-filebrowser

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/components/FileBrowser/FileBrowserContext.js (1)

1-2: Add an explicit default and guarded access pattern for the context.

FileBrowserContext is not yet used in the codebase. When consumers are added, using createContext() with no explicit default can produce hard-to-debug runtime failures if a consumer is mounted outside its provider. Prefer createContext(null) paired with a useFileBrowserContext() hook that throws a clear error if context is missing.

Proposed refactor
-import { createContext } from "react";
-export const FileBrowserContext = createContext();
+import { createContext, useContext } from "react";
+
+export const FileBrowserContext = createContext(null);
+
+export function useFileBrowserContext() {
+  const context = useContext(FileBrowserContext);
+  if (context === null) {
+    throw new Error("useFileBrowserContext must be used within FileBrowserContext.Provider");
+  }
+  return context;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/FileBrowser/FileBrowserContext.js` around lines 1 - 2, Change
FileBrowserContext to createContext(null) and add/export a guarding hook
useFileBrowserContext that calls React.useContext(FileBrowserContext) and throws
a clear error (e.g., "useFileBrowserContext must be used within
FileBrowserProvider") when the returned value is null or undefined; update any
exports so consumers import the hook instead of using
useContext(FileBrowserContext) directly to ensure safe access outside a
provider.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/FileBrowser/FileBrowserContext.js`:
- Around line 1-2: Change FileBrowserContext to createContext(null) and
add/export a guarding hook useFileBrowserContext that calls
React.useContext(FileBrowserContext) and throws a clear error (e.g.,
"useFileBrowserContext must be used within FileBrowserProvider") when the
returned value is null or undefined; update any exports so consumers import the
hook instead of using useContext(FileBrowserContext) directly to ensure safe
access outside a provider.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 11909c81-b276-4f3d-a300-fbf3b528c1d6

📥 Commits

Reviewing files that changed from the base of the PR and between d83179a and 2ea880f.

📒 Files selected for processing (1)
  • src/components/FileBrowser/FileBrowserContext.js

@vishalpalaniappan vishalpalaniappan marked this pull request as ready for review March 24, 2026 16:14
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (1)
src/components/FileBrowser/helper.js (1)

4-8: ⚠️ Potential issue | 🟠 Major

Reset the whole tree without mutating caller-owned nodes.

setDefaultCollapsed() now returns the same array after mutating only the first level. If the same tree object is reused, nested folders can keep stale collapsed flags across reloads/resets because children are never normalized here.

Proposed fix
 export const setDefaultCollapsed = (tree) => {
-    tree.forEach((node) => {
-        node.collapsed = false;
-    });
-    return tree;
+    return tree.map((node) => ({
+        ...node,
+        collapsed: false,
+        children: node.children ? setDefaultCollapsed(node.children) : node.children,
+    }));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/FileBrowser/helper.js` around lines 4 - 8, setDefaultCollapsed
currently mutates the input array and only the top-level nodes; change it to
return a new tree with no caller-owned mutations by mapping each node to a new
object and recursively normalizing its children, ensuring every node gets
collapsed: false. Implement a pure function (e.g., inside setDefaultCollapsed)
that for each node creates a shallow copy with collapsed set to false and, if
node.children exists, sets children to the result of the same function called
recursively, then return the new array rather than mutating the original.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/Editor/EditorReducer.js`:
- Around line 11-19: The ADD_TAB reducer branch is relying solely on tab.uid but
callers may still send legacy { id } shape; modify the ADD_TAB handling in
EditorReducer.js to validate and normalize incoming tab objects by mapping
tab.id -> tab.uid when uid is missing, and generate a safe fallback uid (e.g.,
from tab.title or a UUID) if both are absent before searching state.tabs; ensure
you update the variables used here (action.payload.tab, tab.uid) so the existing
existence check and return of activeTab use the normalized uid and avoid
undefined keys flowing into Tabs.jsx.

In `@src/components/Editor/MonacoInstance/MonacoInstance.jsx`:
- Around line 50-63: The editorOptions object in MonacoInstance.jsx contains a
duplicate key scrollBeyondLastLine (declared in editorOptions), which causes the
latter value to override the former; remove the redundant entry so
scrollBeyondLastLine is defined only once in the editorOptions constant (keep
whichever value is intended) to avoid silent overrides and confusion.

In `@src/components/FileBrowser/FileBrowser.jsx`:
- Around line 31-35: The useEffect in FileBrowser.jsx calls onSelectFile
unguarded and will throw if the prop is omitted; update the effect (the
useEffect that checks state.selectedNode && state.selectedNode.type === "file")
to first verify onSelectFile is a function (e.g. typeof onSelectFile ===
"function" or use optional chaining) before invoking it so the component safely
handles consumers that don't pass the prop.
- Around line 37-39: selectNode currently dispatches the whole node object but
the reducer and imperative calls expect a UID string (it compares
action.payload.uid); change selectNode in FileBrowser.jsx to accept a uid string
and dispatch { type: "SELECT_NODE", payload: { uid } } (or normalize payload to
always have a uid property) so that calls from FileBrowser.stories.js and the
reducer match; update the useCallback signature to (uid) => ... and ensure any
internal callers pass uid not node.

In `@src/components/FileBrowser/FileBrowserReducer.js`:
- Around line 15-23: The ADD_FILE_TREE reducer branch mutates the incoming
action.payload because flattenTree and setDefaultCollapsed write node.level and
node.collapsed and then you return tree: action.payload; fix by cloning the
payload before mutating it (or make the helpers pure). Specifically, in the
ADD_FILE_TREE case around flattenTree(action.payload) create an immutable copy
of action.payload (e.g., deep clone via structuredClone or equivalent) and pass
that clone into flattenTree and setDefaultCollapsed, then keep returning tree:
action.payload or tree: clonedPayload depending on desired semantics so the
original parent-owned tree is not mutated; reference functions: ADD_FILE_TREE
branch, flattenTree, setDefaultCollapsed, collapseTree, and the action.payload
variable.

In `@src/components/FileBrowser/Tree/Tree.jsx`:
- Around line 19-23: The effect watching state.collapsedTree only calls drawTree
when length > 0, so previously rendered nodes remain when the tree becomes
empty; update the useEffect in Tree.jsx to handle the empty case by calling
drawTree([]) or otherwise clearing the rendered rows (e.g., set the nodes state
to an empty array) when state.collapsedTree is null/undefined or has length ===
0, keeping the existing drawTree call for non-empty collapsedTree.

In `@src/stories/FileBrowser.stories.js`:
- Around line 82-87: The onDragStart handler is calling the removed ref method
getPreviewElement via fileBrowserRef.current.getPreviewElement(...) which will
throw; remove that call and instead use the preview already present on
event.active.data.current.preview to setDragPreviewLabel and continue to
setDragging(true); update the onDragStart function (references: onDragStart,
fileBrowserRef, getPreviewElement, setDragPreviewLabel, setDragging,
event.active.data.current.preview) to stop relying on the stale ref method and
consume the preview from the event payload.

---

Outside diff comments:
In `@src/components/FileBrowser/helper.js`:
- Around line 4-8: setDefaultCollapsed currently mutates the input array and
only the top-level nodes; change it to return a new tree with no caller-owned
mutations by mapping each node to a new object and recursively normalizing its
children, ensuring every node gets collapsed: false. Implement a pure function
(e.g., inside setDefaultCollapsed) that for each node creates a shallow copy
with collapsed set to false and, if node.children exists, sets children to the
result of the same function called recursively, then return the new array rather
than mutating the original.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e0c4164c-7e72-4456-b995-ec30e86a15fb

📥 Commits

Reviewing files that changed from the base of the PR and between 2ea880f and e45511a.

📒 Files selected for processing (35)
  • package.json
  • src/components/Editor/Editor.jsx
  • src/components/Editor/Editor.scss
  • src/components/Editor/EditorReducer.js
  • src/components/Editor/MonacoInstance/MonacoInstance.jsx
  • src/components/Editor/MonacoInstance/MonacoInstance.scss
  • src/components/Editor/Tabs/Gutter/Gutter.jsx
  • src/components/Editor/Tabs/Tab/Tab.jsx
  • src/components/Editor/Tabs/Tab/Tab.scss
  • src/components/Editor/Tabs/Tabs.jsx
  • src/components/Editor/Tabs/Tabs.scss
  • src/components/FileBrowser/FileBrowser.jsx
  • src/components/FileBrowser/FileBrowser.scss
  • src/components/FileBrowser/FileBrowserReducer.js
  • src/components/FileBrowser/Tree/Tree.jsx
  • src/components/FileBrowser/Tree/Tree.scss
  • src/components/FileBrowser/TreeNode/TreeNode.jsx
  • src/components/FileBrowser/TreeNode/TreeNode.scss
  • src/components/FileBrowser/helper.js
  • src/components/Viewer/MonacoInstance/MonacoInstance.jsx
  • src/components/Viewer/Tabs/Tab/Tab.jsx
  • src/components/Viewer/Tabs/Tab/Tab.scss
  • src/components/Viewer/Tabs/Tabs.jsx
  • src/components/Viewer/Tabs/Tabs.scss
  • src/components/Viewer/Viewer.jsx
  • src/components/Viewer/Viewer.scss
  • src/components/Viewer/index.js
  • src/index.js
  • src/stories/Editor.stories.js
  • src/stories/FileBrowser.stories.js
  • src/stories/FileBrowserStories.scss
  • src/stories/Viewer.stories.js
  • src/stories/data/FileBrowser/Tree1.json
  • src/stories/data/FileBrowser/Tree2.json
  • src/stories/data/FileBrowser/workspace_sample.json
💤 Files with no reviewable changes (13)
  • src/components/Viewer/Tabs/Tabs.scss
  • src/index.js
  • src/components/Viewer/Tabs/Tab/Tab.scss
  • src/components/Viewer/index.js
  • src/stories/FileBrowserStories.scss
  • src/components/Viewer/Viewer.scss
  • src/stories/data/FileBrowser/Tree1.json
  • src/stories/data/FileBrowser/Tree2.json
  • src/stories/Viewer.stories.js
  • src/components/Viewer/MonacoInstance/MonacoInstance.jsx
  • src/components/Viewer/Tabs/Tab/Tab.jsx
  • src/components/Viewer/Viewer.jsx
  • src/components/Viewer/Tabs/Tabs.jsx
✅ Files skipped from review due to trivial changes (4)
  • src/components/Editor/Tabs/Gutter/Gutter.jsx
  • src/components/Editor/MonacoInstance/MonacoInstance.scss
  • src/components/FileBrowser/TreeNode/TreeNode.scss
  • src/stories/data/FileBrowser/workspace_sample.json

Comment thread src/components/Editor/EditorReducer.js
Comment thread src/components/Editor/MonacoInstance/MonacoInstance.jsx
Comment thread src/components/FileBrowser/FileBrowser.jsx
Comment thread src/components/FileBrowser/FileBrowser.jsx
Comment thread src/components/FileBrowser/FileBrowserReducer.js
Comment thread src/components/FileBrowser/Tree/Tree.jsx
Comment thread src/stories/FileBrowser.stories.js
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
src/components/Editor/EditorReducer.js (1)

12-15: ⚠️ Potential issue | 🟠 Major

Normalize tab identity before dedupe/insert.

Line 14 assumes tab.uid always exists. Legacy callers sending { id } (or malformed payloads) will still produce undefined identity and unstable keys downstream.

Proposed fix
         case "ADD_TAB": {
-            const { tab, index } = action.payload;
+            const { tab, index } = action.payload ?? {};
+            const normalizedTab = tab?.uid
+                ? tab
+                : (tab?.id ? { ...tab, uid: tab.id } : null);
+            if (!normalizedTab) {
+                console.error("ADD_TAB requires a tab with a stable uid.");
+                return state;
+            }

-            const tabInfo = state.tabs.find(obj => obj.uid === tab.uid);
+            const tabInfo = state.tabs.find(obj => obj.uid === normalizedTab.uid);
             if (tabInfo) {
                 console.warn(`Tab with id ${tabInfo.uid} already exists`);
                 return {
                     ...state,
                     activeTab: tabInfo
                 };
             }
             
             // Insert tab at specific location if it was provided.
             let tabs =  [...state.tabs];
             if (index !== undefined && index < state.tabs.length) {
-                tabs.splice(index, 0, tab);
+                tabs.splice(index, 0, normalizedTab);
             } else {
-                tabs.push(tab);
+                tabs.push(normalizedTab);
             }

             return {
                 ...state,
                 tabs: tabs,
-                activeTab: tab
+                activeTab: normalizedTab
             };
         }

Also applies to: 25-29, 33-35

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

In `@src/components/Editor/EditorReducer.js` around lines 12 - 15, Normalize the
incoming tab identity before any find/dedupe/insert operations: instead of
assuming action.payload.tab.uid exists, compute a single canonical id (e.g.,
const canonicalId = tab.uid || tab.id || tab._id) and use that canonicalId when
searching state.tabs (the tabInfo lookup), when comparing for dedupe/insert, and
when creating new tab entries in the reducer (the other occurrences you
flagged). Update all places that reference tab.uid to use the canonicalId so
legacy payloads with id or other shapes are handled robustly.
🧹 Nitpick comments (1)
src/components/Editor/EditorReducer.js (1)

25-26: Normalize/clamp incoming insertion indexes.

index/newIndex are not validated as non-negative integers. Negative values use JS splice-from-end semantics, which can reorder tabs unexpectedly.

Proposed hardening
-            if (index !== undefined && index < state.tabs.length) {
-                tabs.splice(index, 0, normalizedTab);
+            if (Number.isInteger(index)) {
+                const safeIndex = Math.max(0, Math.min(index, state.tabs.length));
+                tabs.splice(safeIndex, 0, normalizedTab);
             } else {
                 tabs.push(normalizedTab);
             }
@@
-            const { tabId, newIndex } = action.payload;
+            const { tabId, newIndex } = action.payload;
+            if (!Number.isInteger(newIndex) || newIndex < 0 || newIndex > prevTabs.length) {
+                console.warn(`Invalid move index: ${newIndex}`);
+                return state;
+            }

Also applies to: 84-95

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

In `@src/components/Editor/EditorReducer.js` around lines 25 - 26, Normalize and
clamp incoming insertion indexes: ensure any incoming index (the variable named
index where tabs.splice(index, 0, tab) is used and the newIndex used in the
84-95 block) is converted to a non-negative integer and clamped to the bounds
[0, tabs.length] before calling splice; e.g., coerce to a number, floor or round
to an integer and then set index = Math.max(0, Math.min(tabs.length, index))
(apply same logic to newIndex) so negative or out-of-range values cannot trigger
JS splice-from-end behavior or reorder tabs unexpectedly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/Editor/EditorReducer.js`:
- Around line 1-5: The reducer's RESET_STATE branch currently returns the shared
initialState object (initialState.uid), so resets reuse the same uid; change
RESET_STATE to return a fresh state object with a new uid instead of returning
initialState directly — e.g., generate a new uid via crypto.randomUUID() and
return an object with tabs: [], activeTab: null, parentTabGroupId: null (or call
a new createInitialState() helper) so that uid (used as tabGroupId in Tabs.jsx)
is unique on each reset; update the RESET_STATE handling in the reducer (and any
related return paths around the current lines 103-105) to use this new object.
- Line 2: The module-level use of crypto.randomUUID() for uid (symbol: uid in
EditorReducer.js) can break in older Node/browser/SSR environments; replace it
with a cross-platform UUID generator (e.g., install and import uuid and use v4)
or add a safe fallback that checks for global crypto.randomUUID and otherwise
calls uuid.v4(), and update README with minimum runtime requirements if you
choose to keep crypto.randomUUID; ensure the replacement occurs wherever uid:
crypto.randomUUID() is set so the module no longer fails at init in unsupported
environments.

In `@src/components/FileBrowser/FileBrowserReducer.js`:
- Line 6: Initialize flattenedTree with the correct type (array) to match its
usage: change the initial state value for flattenedTree in FileBrowserReducer
(where flattenedTree is currently set to {}) to an empty array so SELECT_NODE
(which calls .map() on flattenedTree) and ADD_FILE_TREE use the same type;
verify reducers/selectors that reference flattenedTree (e.g., SELECT_NODE,
ADD_FILE_TREE, any map/filter uses) still work with an array and update any code
that assumes object semantics if present.
- Around line 46-48: The RESET_STATE reducer currently returns the module-level
initialState, which reuses initialState.uid and causes stale React keys (used in
Tree.jsx as key={parentId + "-" + node.uid}); change the RESET_STATE case to
return a fresh state object that copies initialState but generates a new uid
(e.g., return { ...initialState, uid: <call your existing uid generator or uuid
function>() }) so each reset produces a new node.uid and forces remounting.
- Around line 11-49: The reducer fileBrowserReducer lacks a default branch, so
unrecognized actions cause undefined state; update the switch in
fileBrowserReducer to include a default case that returns the current state
(i.e., return state) so unknown action types don't break state, keeping the
existing cases ("ADD_FILE_TREE", "SELECT_NODE", "RESET_STATE" which returns
initialState) intact.

---

Duplicate comments:
In `@src/components/Editor/EditorReducer.js`:
- Around line 12-15: Normalize the incoming tab identity before any
find/dedupe/insert operations: instead of assuming action.payload.tab.uid
exists, compute a single canonical id (e.g., const canonicalId = tab.uid ||
tab.id || tab._id) and use that canonicalId when searching state.tabs (the
tabInfo lookup), when comparing for dedupe/insert, and when creating new tab
entries in the reducer (the other occurrences you flagged). Update all places
that reference tab.uid to use the canonicalId so legacy payloads with id or
other shapes are handled robustly.

---

Nitpick comments:
In `@src/components/Editor/EditorReducer.js`:
- Around line 25-26: Normalize and clamp incoming insertion indexes: ensure any
incoming index (the variable named index where tabs.splice(index, 0, tab) is
used and the newIndex used in the 84-95 block) is converted to a non-negative
integer and clamped to the bounds [0, tabs.length] before calling splice; e.g.,
coerce to a number, floor or round to an integer and then set index =
Math.max(0, Math.min(tabs.length, index)) (apply same logic to newIndex) so
negative or out-of-range values cannot trigger JS splice-from-end behavior or
reorder tabs unexpectedly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 395d9fd0-23d8-4dc9-b1ad-5452e4392a29

📥 Commits

Reviewing files that changed from the base of the PR and between e45511a and e6b6860.

📒 Files selected for processing (5)
  • src/components/Editor/EditorReducer.js
  • src/components/Editor/Tabs/Tabs.jsx
  • src/components/FileBrowser/FileBrowserReducer.js
  • src/components/FileBrowser/Tree/Tree.jsx
  • src/components/FileBrowser/TreeNode/TreeNode.jsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/FileBrowser/Tree/Tree.jsx
  • src/components/Editor/Tabs/Tabs.jsx
  • src/components/FileBrowser/TreeNode/TreeNode.jsx

Comment thread src/components/Editor/EditorReducer.js
Comment thread src/components/Editor/EditorReducer.js
Comment thread src/components/FileBrowser/FileBrowserReducer.js Outdated
Comment thread src/components/FileBrowser/FileBrowserReducer.js
Comment on lines +46 to +48
case "RESET_STATE": {
return initialState;
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Mar 24, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

RESET_STATE returns stale uid, breaking React key uniqueness.

initialState.uid is computed once at module load. Returning the same object on reset means the uid never changes. Per Tree.jsx (line 33), this uid is used in React keys (key={parentId + "-" + node.uid}). After a reset, component instances won't remount properly because keys remain identical.

Generate a fresh uid on each reset:

🐛 Proposed fix
         case "RESET_STATE": {
-            return initialState;
+            return {
+                ...initialState,
+                uid: crypto.randomUUID(),
+            };
         }
📝 Committable suggestion

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

Suggested change
case "RESET_STATE": {
return initialState;
}
case "RESET_STATE": {
return {
...initialState,
uid: crypto.randomUUID(),
};
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/FileBrowser/FileBrowserReducer.js` around lines 46 - 48, The
RESET_STATE reducer currently returns the module-level initialState, which
reuses initialState.uid and causes stale React keys (used in Tree.jsx as
key={parentId + "-" + node.uid}); change the RESET_STATE case to return a fresh
state object that copies initialState but generates a new uid (e.g., return {
...initialState, uid: <call your existing uid generator or uuid function>() })
so each reset produces a new node.uid and forces remounting.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I added this because when the components are unmounted and remounted in strict mode (not a true unmount), the state persists in the reducer and I ran into quite a few corner cases. However, I don't see an issue with it retaining the same UID, the components will have unmounted, so the keys won't be reused. That being said, if there is a reason I am not aware of, I'd be interested in learning why I should do it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@vishalpalaniappan, that's a fair and well-reasoned explanation. You're right — in a true unmount/remount cycle, React creates fresh component instances regardless of key values, so the uid reuse doesn't cause any issue. And in the Strict Mode double-invoke scenario you're specifically targeting, RESET_STATE clears flattenedTree/collapsedTree to empty, meaning there are no stale node keys to conflict with during re-population.

My original concern was overstated — the scenario I had in mind (same uid causing stale key collisions) doesn't materialize in practice given how RESET_STATE is actually used here. The current implementation is fine. Thanks for clarifying the intent!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/FileBrowser/FileBrowserReducer.js`:
- Around line 50-52: The reducer FileBrowserReducer's switch default currently
only contains a break which causes the reducer to return undefined for unknown
actions; update the default case in the reducer function (FileBrowserReducer) to
return the current state (i.e., return state) instead of breaking so that
unknown action types preserve existing state and satisfy useReducer
requirements.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c7fb2216-d114-4a17-9a0f-377f57bc54ff

📥 Commits

Reviewing files that changed from the base of the PR and between 3adcfa9 and 6ed2f54.

📒 Files selected for processing (1)
  • src/components/FileBrowser/FileBrowserReducer.js

Comment thread src/components/FileBrowser/FileBrowserReducer.js
@vishalpalaniappan
Copy link
Copy Markdown
Owner Author

@coderabbitai PR title suggestions?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 24, 2026

Tip

For best results, initiate chat on the files or code changes.

@vishalpalaniappan Here are some PR title suggestions based on the changes:

  1. refactor(FileBrowser): migrate to modular architecture with context, reducer, and imperative ref API
    — Highlights the three core structural changes.

  2. feat(FileBrowser): add forwardRef-based imperative API with Tree/TreeNode subcomponents
    — Focuses on the new public API surface and new sub-components.

  3. refactor: migrate FileBrowser to reducer-driven modular components with parent/child API
    — Close to the original title but more precise about the reducer pattern.

  4. refactor(FileBrowser): modularize with context/reducer pattern and remove Viewer component
    — Also calls out the removal of Viewer, which is a significant change.

  5. refactor: migrate FileBrowser to modular component pattern and align Editor API
    — Broader, covering both FileBrowser and Editor changes in one title.

My personal recommendation would be #1 or #3, as they clearly communicate the architectural intent (modular, reducer-driven, imperative API) without being overly broad.

@vishalpalaniappan vishalpalaniappan merged commit d70cfb2 into main Mar 24, 2026
1 check passed
@vishalpalaniappan vishalpalaniappan deleted the modular-filebrowser branch March 24, 2026 20:43
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