-
Notifications
You must be signed in to change notification settings - Fork 415
Refactor main area #1548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor main area #1548
Conversation
|
@coderabbitai ignore |
|
Note Reviews pausedUse the following commands to manage reviews:
📝 WalkthroughWalkthroughIntroduces a new tabbed main content system with per-type tab items and contents (sessions, events, calendars, folders, humans), adds a new sidebar with timeline and profile sections, removes legacy main-area and left-sidebar modules, and updates routing/layout to use the new components and minor devtools positioning. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant LeftSidebar as LeftSidebar
participant Tabs as useTabs Store
participant Main as MainContent
participant Item as TabItem (per-type)
participant Content as TabContent (per-type)
User->>LeftSidebar: Click session in Timeline
LeftSidebar->>Tabs: openCurrent({ type:"sessions", id })
Tabs-->>Main: currentTab updated
Main->>Item: Render tab header for type
Main->>Content: Render content for type
rect rgba(200,230,255,0.3)
note right of Content: Calendar example
User->>Content: Click Prev/Next/Today
Content->>Tabs: openCurrent({ type:"calendars", month })
Tabs-->>Main: currentTab updated
Main->>Content: Re-render calendar month
end
sequenceDiagram
autonumber
actor User
participant Profile as ProfileSection
participant Tabs as useTabs Store
User->>Profile: Expand menu
User->>Profile: Click "Folders"
Profile->>Tabs: openNew({ type:"folders", id: null })
User->>Profile: Click "Calendar"
Profile->>Tabs: openNew({ type:"calendars", month: current })
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (14)
apps/desktop2/src/components/main/body/calendars.tsx(1 hunks)apps/desktop2/src/components/main/body/events.tsx(1 hunks)apps/desktop2/src/components/main/body/folders.tsx(1 hunks)apps/desktop2/src/components/main/body/humans.tsx(1 hunks)apps/desktop2/src/components/main/body/index.tsx(1 hunks)apps/desktop2/src/components/main/body/sessions.tsx(1 hunks)apps/desktop2/src/components/main/body/shared.tsx(1 hunks)apps/desktop2/src/components/main/left-sidebar.tsx(0 hunks)apps/desktop2/src/components/main/main-area.tsx(0 hunks)apps/desktop2/src/components/main/sidebar/index.tsx(1 hunks)apps/desktop2/src/components/main/sidebar/profile.tsx(1 hunks)apps/desktop2/src/components/main/sidebar/timeline.tsx(1 hunks)apps/desktop2/src/routes/__root.tsx(1 hunks)apps/desktop2/src/routes/app/main/_layout.index.tsx(2 hunks)
💤 Files with no reviewable changes (2)
- apps/desktop2/src/components/main/main-area.tsx
- apps/desktop2/src/components/main/left-sidebar.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}
⚙️ CodeRabbit configuration file
**/*.{js,ts,tsx,rs}: 1. Do not add any error handling. Keep the existing one.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".
Files:
apps/desktop2/src/components/main/body/events.tsxapps/desktop2/src/components/main/body/humans.tsxapps/desktop2/src/components/main/body/shared.tsxapps/desktop2/src/components/main/body/index.tsxapps/desktop2/src/components/main/sidebar/index.tsxapps/desktop2/src/components/main/body/calendars.tsxapps/desktop2/src/routes/app/main/_layout.index.tsxapps/desktop2/src/components/main/sidebar/profile.tsxapps/desktop2/src/components/main/sidebar/timeline.tsxapps/desktop2/src/components/main/body/sessions.tsxapps/desktop2/src/routes/__root.tsxapps/desktop2/src/components/main/body/folders.tsx
🧬 Code graph analysis (10)
apps/desktop2/src/components/main/body/events.tsx (2)
apps/desktop2/src/components/main/body/shared.tsx (2)
TabItem(5-9)TabItemBase(11-52)apps/desktop2/src/store/zustand/tabs.ts (1)
rowIdfromTab(117-135)
apps/desktop2/src/components/main/body/humans.tsx (1)
apps/desktop2/src/components/main/body/shared.tsx (2)
TabItem(5-9)TabItemBase(11-52)
apps/desktop2/src/components/main/body/index.tsx (7)
apps/desktop2/src/store/zustand/tabs.ts (2)
useTabs(22-82)uniqueIdfromTab(137-152)apps/desktop2/src/components/main/body/shared.tsx (1)
TabItem(5-9)apps/desktop2/src/components/main/body/sessions.tsx (2)
TabItemNote(11-23)TabContentNote(25-75)apps/desktop2/src/components/main/body/events.tsx (2)
TabItemEvent(7-19)TabContentEvent(21-26)apps/desktop2/src/components/main/body/calendars.tsx (2)
TabItemCalendar(10-20)TabContentCalendar(22-60)apps/desktop2/src/components/main/body/folders.tsx (2)
TabItemFolder(8-18)TabContentFolder(50-62)apps/desktop2/src/components/main/body/humans.tsx (2)
TabItemHuman(7-23)TabContentHuman(25-31)
apps/desktop2/src/components/main/sidebar/index.tsx (4)
packages/utils/src/contexts/left-sidebar.tsx (1)
useLeftSidebar(42-48)apps/desktop2/src/components/main/sidebar/timeline.tsx (1)
TimelineView(8-23)apps/desktop2/src/components/main/sidebar/profile.tsx (1)
ProfileSection(8-97)apps/desktop2/src/components/main/left-sidebar.tsx (1)
LeftSidebar(13-36)
apps/desktop2/src/components/main/body/calendars.tsx (3)
apps/desktop2/src/components/main/body/shared.tsx (2)
TabItem(5-9)TabItemBase(11-52)apps/desktop2/src/store/zustand/tabs.ts (1)
useTabs(22-82)packages/ui/src/components/block/calendar-structure.tsx (1)
CalendarStructure(14-71)
apps/desktop2/src/routes/app/main/_layout.index.tsx (1)
apps/desktop2/src/components/main/body/index.tsx (1)
MainContent(12-25)
apps/desktop2/src/components/main/sidebar/profile.tsx (1)
apps/desktop2/src/store/zustand/tabs.ts (1)
useTabs(22-82)
apps/desktop2/src/components/main/sidebar/timeline.tsx (2)
apps/desktop2/src/store/zustand/tabs.ts (1)
useTabs(22-82)apps/desktop2/src/components/interactive-button.tsx (1)
InteractiveButton(13-55)
apps/desktop2/src/components/main/body/sessions.tsx (3)
apps/desktop2/src/components/main/body/shared.tsx (2)
TabItem(5-9)TabItemBase(11-52)apps/desktop2/src/store/zustand/tabs.ts (2)
rowIdfromTab(117-135)useTabs(22-82)packages/ui/src/components/block/title-input.tsx (1)
TitleInput(12-59)
apps/desktop2/src/components/main/body/folders.tsx (2)
apps/desktop2/src/components/main/body/shared.tsx (2)
TabItem(5-9)TabItemBase(11-52)apps/desktop2/src/store/zustand/tabs.ts (1)
useTabs(22-82)
🪛 Biome (2.1.2)
apps/desktop2/src/components/main/body/calendars.tsx
[error] 27-27: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
apps/desktop2/src/components/main/body/folders.tsx
[error] 37-37: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: ci (macos, macos-14)
- GitHub Check: ci (windows, windows-latest)
| export function TabContentCalendar({ tab }: { tab: Tab }) { | ||
| if (tab.type !== "calendars") { | ||
| return null; | ||
| } | ||
|
|
||
| const { openCurrent } = useTabs(); | ||
| const monthStart = startOfMonth(tab.month); | ||
| const monthEnd = endOfMonth(tab.month); | ||
| const days = eachDayOfInterval({ start: monthStart, end: monthEnd }).map((day) => format(day, "yyyy-MM-dd")); | ||
| const startDayOfWeek = getDay(monthStart); | ||
| const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; | ||
|
|
||
| const handlePreviousMonth = () => { | ||
| openCurrent({ ...tab, month: addMonths(tab.month, -1) }); | ||
| }; | ||
|
|
||
| const handleNextMonth = () => { | ||
| openCurrent({ ...tab, month: addMonths(tab.month, 1) }); | ||
| }; | ||
|
|
||
| const handleToday = () => { | ||
| openCurrent({ ...tab, month: new Date() }); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move useTabs before the type guard to satisfy the Hooks rule.
useTabs() currently sits after an early return, so when tab.type isn’t "calendars" the hook is skipped. If React later renders the same component with a calendar tab, the hook order changes and state can corrupt (the lint warning you’ve probably seen). Pull the hook call above the guard.
-export function TabContentCalendar({ tab }: { tab: Tab }) {
- if (tab.type !== "calendars") {
- return null;
- }
-
- const { openCurrent } = useTabs();
+export function TabContentCalendar({ tab }: { tab: Tab }) {
+ const { openCurrent } = useTabs();
+
+ if (tab.type !== "calendars") {
+ return null;
+ }📝 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.
| export function TabContentCalendar({ tab }: { tab: Tab }) { | |
| if (tab.type !== "calendars") { | |
| return null; | |
| } | |
| const { openCurrent } = useTabs(); | |
| const monthStart = startOfMonth(tab.month); | |
| const monthEnd = endOfMonth(tab.month); | |
| const days = eachDayOfInterval({ start: monthStart, end: monthEnd }).map((day) => format(day, "yyyy-MM-dd")); | |
| const startDayOfWeek = getDay(monthStart); | |
| const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; | |
| const handlePreviousMonth = () => { | |
| openCurrent({ ...tab, month: addMonths(tab.month, -1) }); | |
| }; | |
| const handleNextMonth = () => { | |
| openCurrent({ ...tab, month: addMonths(tab.month, 1) }); | |
| }; | |
| const handleToday = () => { | |
| openCurrent({ ...tab, month: new Date() }); | |
| }; | |
| export function TabContentCalendar({ tab }: { tab: Tab }) { | |
| const { openCurrent } = useTabs(); | |
| if (tab.type !== "calendars") { | |
| return null; | |
| } | |
| const monthStart = startOfMonth(tab.month); | |
| const monthEnd = endOfMonth(tab.month); | |
| const days = eachDayOfInterval({ start: monthStart, end: monthEnd }).map((day) => | |
| format(day, "yyyy-MM-dd") | |
| ); | |
| const startDayOfWeek = getDay(monthStart); | |
| const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; | |
| const handlePreviousMonth = () => { | |
| openCurrent({ ...tab, month: addMonths(tab.month, -1) }); | |
| }; | |
| const handleNextMonth = () => { | |
| openCurrent({ ...tab, month: addMonths(tab.month, 1) }); | |
| }; | |
| const handleToday = () => { | |
| openCurrent({ ...tab, month: new Date() }); | |
| }; | |
| // ...rest of component | |
| } |
🧰 Tools
🪛 Biome (2.1.2)
[error] 27-27: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
🤖 Prompt for AI Agents
In apps/desktop2/src/components/main/body/calendars.tsx around lines 22 to 44,
the call to useTabs() is after an early return guard for tab.type which violates
the Rules of Hooks; move the const { openCurrent } = useTabs(); call above the
if (tab.type !== "calendars") return null; so the hook is always invoked in the
same order, then keep the type guard and the rest of the month calculations and
handlers unchanged.
| if (tab.type !== "folders" || tab.id === null) { | ||
| return null; | ||
| } | ||
|
|
||
| const folderName = persisted.UI.useCell("folders", tab.id, "name", persisted.STORE_ID); | ||
|
|
||
| return ( | ||
| <TabItemBase | ||
| icon={<FolderIcon className="w-4 h-4" />} | ||
| title={folderName ?? ""} | ||
| active={tab.active} | ||
| handleClose={() => handleClose(tab)} | ||
| handleSelect={() => handleSelect(tab)} | ||
| /> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unconditional hook needed in TabItemFolderSpecific
persisted.UI.useCell is a hook, yet it only runs when the guard passes. That breaks the hooks contract (and Biome already errors), so lint will fail and future renders risk hook order mismatches. Please restructure so the hook lives in a component that’s always executed—e.g. delegate to an inner component that receives the narrowed folderId and calls the hook there.
-const TabItemFolderSpecific: TabItem = ({ tab, handleClose, handleSelect }) => {
- if (tab.type !== "folders" || tab.id === null) {
- return null;
- }
-
- const folderName = persisted.UI.useCell("folders", tab.id, "name", persisted.STORE_ID);
-
- return (
- <TabItemBase
- icon={<FolderIcon className="w-4 h-4" />}
- title={folderName ?? ""}
- active={tab.active}
- handleClose={() => handleClose(tab)}
- handleSelect={() => handleSelect(tab)}
- />
- );
-};
+const TabItemFolderSpecific: TabItem = ({ tab, handleClose, handleSelect }) => {
+ if (tab.type !== "folders" || tab.id === null) {
+ return null;
+ }
+
+ return (
+ <TabItemFolderSpecificContent
+ tab={tab}
+ folderId={tab.id}
+ handleClose={handleClose}
+ handleSelect={handleSelect}
+ />
+ );
+};
+
+function TabItemFolderSpecificContent(
+ { tab, folderId, handleClose, handleSelect }: {
+ tab: Tab;
+ folderId: string;
+ handleClose: (tab: Tab) => void;
+ handleSelect: (tab: Tab) => void;
+ },
+) {
+ const folderName = persisted.UI.useCell("folders", folderId, "name", persisted.STORE_ID);
+
+ return (
+ <TabItemBase
+ icon={<FolderIcon className="w-4 h-4" />}
+ title={folderName ?? ""}
+ active={tab.active}
+ handleClose={() => handleClose(tab)}
+ handleSelect={() => handleSelect(tab)}
+ />
+ );
+}📝 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.
| if (tab.type !== "folders" || tab.id === null) { | |
| return null; | |
| } | |
| const folderName = persisted.UI.useCell("folders", tab.id, "name", persisted.STORE_ID); | |
| return ( | |
| <TabItemBase | |
| icon={<FolderIcon className="w-4 h-4" />} | |
| title={folderName ?? ""} | |
| active={tab.active} | |
| handleClose={() => handleClose(tab)} | |
| handleSelect={() => handleSelect(tab)} | |
| /> | |
| ); | |
| const TabItemFolderSpecific: TabItem = ({ tab, handleClose, handleSelect }) => { | |
| if (tab.type !== "folders" || tab.id === null) { | |
| return null; | |
| } | |
| return ( | |
| <TabItemFolderSpecificContent | |
| tab={tab} | |
| folderId={tab.id} | |
| handleClose={handleClose} | |
| handleSelect={handleSelect} | |
| /> | |
| ); | |
| }; | |
| function TabItemFolderSpecificContent( | |
| { tab, folderId, handleClose, handleSelect }: { | |
| tab: Tab; | |
| folderId: string; | |
| handleClose: (tab: Tab) => void; | |
| handleSelect: (tab: Tab) => void; | |
| }, | |
| ) { | |
| const folderName = persisted.UI.useCell("folders", folderId, "name", persisted.STORE_ID); | |
| return ( | |
| <TabItemBase | |
| icon={<FolderIcon className="w-4 h-4" />} | |
| title={folderName ?? ""} | |
| active={tab.active} | |
| handleClose={() => handleClose(tab)} | |
| handleSelect={() => handleSelect(tab)} | |
| /> | |
| ); | |
| } |
🧰 Tools
🪛 Biome (2.1.2)
[error] 37-37: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
| const sessionRow = persisted.UI.useRow("sessions", sessionId, persisted.STORE_ID); | ||
|
|
||
| const handleEditTitle = persisted.UI.useSetRowCallback( | ||
| "sessions", | ||
| sessionId, | ||
| (input: string, _store) => ({ ...sessionRow, title: input }), | ||
| [sessionRow], | ||
| persisted.STORE_ID, | ||
| ); | ||
|
|
||
| const handleEditRawMd = persisted.UI.useSetRowCallback( | ||
| "sessions", | ||
| sessionId, | ||
| (input: string, _store) => ({ ...sessionRow, raw_md: input }), | ||
| [sessionRow], | ||
| persisted.STORE_ID, | ||
| ); | ||
|
|
||
| return ( | ||
| <div className="flex flex-col gap-2 px-2 pt-2"> | ||
| <TabContentNoteHeader sessionRow={sessionRow} /> | ||
|
|
||
| <TitleInput | ||
| editable={true} | ||
| value={sessionRow.title ?? ""} | ||
| onChange={(e) => handleEditTitle(e.target.value)} | ||
| /> | ||
| <TabHeader | ||
| isEnhancing={false} | ||
| onVisibilityChange={() => {}} | ||
| currentTab="raw" | ||
| onTabChange={() => {}} | ||
| isCurrentlyRecording={false} | ||
| shouldShowTab={true} | ||
| shouldShowEnhancedTab={false} | ||
| /> | ||
| <NoteEditor | ||
| initialContent={sessionRow.raw_md ?? ""} | ||
| handleChange={(e) => handleEditRawMd(e)} | ||
| mentionConfig={{ | ||
| trigger: "@", | ||
| handleSearch: async () => { | ||
| return []; | ||
| }, | ||
| }} | ||
| /> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| function TabContentNoteHeader({ sessionRow }: { sessionRow: ReturnType<typeof persisted.UI.useRow<"sessions">> }) { | ||
| return ( | ||
| <div className="flex items-center justify-between"> | ||
| <div className="flex items-center gap-2"> | ||
| {sessionRow.folder_id && ( | ||
| <TabContentNoteHeaderFolderChain | ||
| title={sessionRow.title ?? ""} | ||
| folderId={sessionRow.folder_id} | ||
| /> | ||
| )} | ||
| </div> | ||
|
|
||
| {sessionRow.event_id && <TabContentNoteHeaderEvent eventId={sessionRow.event_id} />} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| function TabContentNoteHeaderFolderChain({ title, folderId }: { title: string; folderId: string }) { | ||
| const folderIds = persisted.UI.useLinkedRowIds( | ||
| "folderToParentFolder", | ||
| folderId, | ||
| persisted.STORE_ID, | ||
| ); | ||
|
|
||
| if (!folderIds || folderIds.length === 0) { | ||
| return null; | ||
| } | ||
|
|
||
| const folderChain = [...folderIds].reverse(); | ||
|
|
||
| return ( | ||
| <div className="flex items-center gap-1 text-sm text-muted-foreground"> | ||
| {folderChain.map((id, index) => ( | ||
| <div key={id} className="flex items-center gap-1"> | ||
| {index > 0 && <span>/</span>} | ||
| <TabContentNoteHeaderFolder folderId={id} /> | ||
| </div> | ||
| ))} | ||
| <div className="flex items-center gap-2"> | ||
| <span>/</span> | ||
| <span className="truncate max-w-[80px]">{title}</span> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| function TabContentNoteHeaderFolder({ folderId }: { folderId: string }) { | ||
| const folderName = persisted.UI.useCell("folders", folderId, "name", persisted.STORE_ID); | ||
| const { openNew } = useTabs(); | ||
| const handleClick = useCallback(() => { | ||
| openNew({ type: "folders", id: folderId, active: true }); | ||
| }, [openNew, folderId]); | ||
|
|
||
| return ( | ||
| <button | ||
| className="text-gray-500 hover:text-gray-700" | ||
| onClick={handleClick} | ||
| > | ||
| {folderName} | ||
| </button> | ||
| ); | ||
| } | ||
|
|
||
| function TabContentNoteHeaderEvent({ eventId }: { eventId: string }) { | ||
| const eventRow = persisted.UI.useRow("events", eventId, persisted.STORE_ID); | ||
| return <div>{eventRow.title}</div>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against missing session/event rows to avoid crashes.
persisted.UI.useRow returns undefined when the backing row is missing or not yet hydrated (e.g., note deleted, data still loading). We immediately read from sessionRow and eventRow, so those cases will throw Cannot read properties of undefined and break the main area. Bail out early when either row is absent before touching their fields.
const sessionRow = persisted.UI.useRow("sessions", sessionId, persisted.STORE_ID);
+ if (!sessionRow) {
+ return null;
+ }
@@
function TabContentNoteHeaderEvent({ eventId }: { eventId: string }) {
const eventRow = persisted.UI.useRow("events", eventId, persisted.STORE_ID);
- return <div>{eventRow.title}</div>;
+ if (!eventRow) {
+ return null;
+ }
+ return <div>{eventRow.title}</div>;
}🤖 Prompt for AI Agents
In apps/desktop2/src/components/main/body/sessions.tsx around lines 27-142, add
guards to handle missing rows: check if sessionRow is undefined at the top of
the main component and return a safe fallback (null or a loading/empty state)
before reading sessionRow.title, raw_md, folder_id, or event_id; similarly, in
TabContentNoteHeaderEvent ensure eventRow is checked for undefined before
accessing eventRow.title and return null if absent; also ensure
TabContentNoteHeaderFolderChain handles a missing/empty folderIds (already
partly handled) but bail out early if folderIds is falsy; these changes will
prevent attempts to read properties on undefined and avoid crashes.
✅ Actions performedReviews paused. |
No description provided.