Skip to content

use ui state#103

Merged
Kitenite merged 6 commits intomainfrom
use-ui-state
Nov 19, 2025
Merged

use ui state#103
Kitenite merged 6 commits intomainfrom
use-ui-state

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Nov 19, 2025

Summary by CodeRabbit

  • New Features

    • Animated sidebar with smooth collapse/expand and pointer-event handling.
    • Tabbed navigation with add, remove, and drag-to-reorder tabs.
    • Sidebar toggle control in the top bar.
  • UI Components

    • New visual separator component.
    • Updated window and menu icons for a refreshed look.
  • Chores

    • Added new state-management infrastructure and docs.
    • Updated package configuration and runtime dependencies.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 19, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds Zustand stores (sidebar, tabs, example), animated Framer Motion Sidebar and TopBar tab UI (drag/reorder), a new Separator component in the UI package, replaces lucide-react icons with react-icons, removes the local cn utility, updates package deps and docs, and adjusts several re-exports and IPC import ordering.

Changes

Cohort / File(s) Summary
Zustand stores & docs
apps/desktop/src/renderer/stores/sidebar-state.ts, apps/desktop/src/renderer/stores/tabs.ts, apps/desktop/src/renderer/stores/example-store.ts, apps/desktop/src/renderer/stores/index.ts, apps/desktop/src/renderer/stores/README.md
Adds Zustand stores for sidebar, tabs, and an example store with devtools integration, selectors, actions, createNewTab helper, and README with usage/best-practices.
TopBar Tabs UI
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.tsx, apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx, apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx
New Tabs container with dynamic tab width, overflow fades, separator logic and hover state; TabItem with react-dnd drag/reorder and close action; AddTabButton to create tabs.
Sidebar UI & control
apps/desktop/src/renderer/screens/main/components/Sidebar/index.tsx, apps/desktop/src/renderer/screens/main/components/TopBar/SidebarControl.tsx
New Sidebar component using Framer Motion to animate width/opacity and pointer-events; SidebarControl button toggles sidebar via store.
TopBar layout changes
apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx, apps/desktop/src/renderer/screens/main/components/TopBar/index.ts
Replaces heading with SidebarControl and embeds Tabs in top bar; removes TopBar re-export from index.
UI package: Separator & icon changes
packages/ui/src/components/separator.tsx, packages/ui/package.json, packages/ui/src/components/*.tsx
Adds Radix-based Separator component and export; adds @radix-ui/react-separator and react-icons deps; replaces lucide-react icon usage across multiple UI components with react-icons equivalents.
Utility removal & small reorders
apps/desktop/src/renderer/lib/utils.ts, apps/desktop/src/shared/ipc-channels/index.ts, apps/desktop/src/shared/ipc-channels/ui.ts, apps/desktop/CLAUDE.md
Removes exported cn utility and its dependencies, reorders some type imports, and updates CLAUDE guideline to prefer Zustand.
Example component & package updates
apps/desktop/src/renderer/components/ZustandExample.tsx, apps/desktop/package.json
Adds a demo component showing store selectors/actions; updates package.json author email, adds zustand and react-icons, removes lucide-react.
Index re-export removals
apps/desktop/src/renderer/screens/main/components/Sidebar/index.ts, apps/desktop/src/renderer/screens/main/components/TopBar/index.ts
Removes re-exports of Sidebar and TopBar from their index barrels (adjusts public export surface).

Sequence Diagram(s)

sequenceDiagram
  participant U as User
  participant UI as TopBar/Sidebar Components
  participant Store as Zustand Stores
  participant DND as react-dnd

  U->>UI: Click toggle / drag tab / click add/close
  UI->>Store: call action (toggleSidebar / addTab / removeTab / reorderTabs / setActiveTab)
  Store-->>UI: updated state (isSidebarOpen / tabs list / activeTabId)
  UI->>DND: drag start / hover events
  DND->>UI: hover -> request reorder
  UI->>Store: reorderTabs(...) 
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas to focus:
    • Tab drag-and-drop logic and index/hover mutation in TabItem.tsx.
    • Tab reordering, active-tab fallback, and createNewTab behavior in tabs.ts.
    • Sidebar animation interaction with pointer-events and store subscriptions.
    • Global icon replacement across UI components and package.json export/dependency changes.
    • Removal of cn utility and usages (ensure no broken imports).

Possibly related PRs

  • arc sidebar baby #87 — Overlaps on tab drag-and-drop surface and react-dnd usage; likely to conflict with Tabs/TabItem changes.
  • carousel #74 — Modifies Sidebar implementation; may conflict with Framer Motion-based Sidebar changes.
  • initial ws #77 — Touches IPC channel definitions and renderer UI; related to reordered ipc-channel imports and UI/sharing across renderer.

Poem

🐰 I hopped into the code today,
Zuständ springs where states now play,
Tabs that tumble, slide, and glide,
A sidebar dances at my side,
New separators mark the way — hooray! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete, consisting of only a bullet-point list without following the provided template structure, missing details on what changes do, why they were made, and testing information. Restructure the description to follow the template: add a clear Description section explaining changes, link related issues, specify Type of Change, describe testing performed, and add any additional notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'use ui state' is vague and generic, using non-descriptive terms that don't clearly convey the specific changes being made. Consider using a more specific title like 'Add Zustand-based UI state management for sidebar and tabs' to better summarize the main changes.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b88617a and 437e9ef.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • apps/desktop/package.json (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/SidebarControl.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/WindowControls.tsx (2 hunks)
  • packages/ui/package.json (2 hunks)
  • packages/ui/src/components/context-menu.tsx (4 hunks)
  • packages/ui/src/components/dialog.tsx (2 hunks)
  • packages/ui/src/components/dropdown-menu.tsx (4 hunks)
  • packages/ui/src/components/resizable.tsx (2 hunks)
  • packages/ui/src/components/select.tsx (5 hunks)

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/desktop/src/renderer/stores/README.md (1)

1-191: Good, comprehensive Zustand docs; consider typing set in slice examples

This README does a nice job documenting the intended Zustand patterns (selectors, devtools, persist, slices, and the division of concerns vs tRPC). It should be very helpful onboarding-wise.

One optional refinement: in the “Slice Pattern for Large Stores” section, the createUserSlice / createSettingsSlice helpers accept an untyped set parameter. For a “TypeScript first” codebase, you might consider giving set an explicit type (e.g., based on your app store state) so examples don’t rely on implicit any.

Otherwise this looks solid.

apps/desktop/src/renderer/stores/example-store.ts (1)

1-54: Store shape and actions look consistent; a couple of optional type/devtools refinements

The example store is well-structured:

  • ExampleState cleanly separates data (count, text) from actions.
  • initialState is reused for both initial setup and reset, keeping defaults in one place.
  • Increment/decrement use the functional set form, which is safe for concurrent updates.
  • Lightweight selectors useCount / useText are a good pattern for consumers.

Two optional improvements you could consider:

  1. Type initialState explicitly – e.g., as Pick<ExampleState, "count" | "text"> – to catch drift if more data fields are added later.
  2. Devtools in production – if you care about minimizing prod overhead, you might conditionally enable devtools only in development via your existing env flags.

Neither is blocking; the current implementation is sound.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03b0cfa and da333e9.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • apps/desktop/CLAUDE.md (1 hunks)
  • apps/desktop/package.json (1 hunks)
  • apps/desktop/src/renderer/components/ZustandExample.tsx (1 hunks)
  • apps/desktop/src/renderer/stores/README.md (1 hunks)
  • apps/desktop/src/renderer/stores/example-store.ts (1 hunks)
  • apps/desktop/src/renderer/stores/index.ts (1 hunks)
  • apps/desktop/src/shared/ipc-channels/index.ts (1 hunks)
  • apps/desktop/src/shared/ipc-channels/ui.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/desktop/src/renderer/components/ZustandExample.tsx (1)
apps/desktop/src/renderer/stores/example-store.ts (3)
  • useCount (53-53)
  • useText (54-54)
  • useExampleStore (30-50)
🪛 GitHub Actions: CI
apps/desktop/package.json

[warning] Typecheck step encountered multiple TypeScript errors across the monorepo. Original log shows numerous TS errors in old-desktop build.

🪛 LanguageTool
apps/desktop/CLAUDE.md

[grammar] ~2-~2: Ensure spelling is correct
Context: ...ation details For Electron interprocess communnication, ALWAYS use trpc as defined in `src/lib...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~3-~3: Ensure spelling is correct
Context: ...as defined in src/lib/trpc Please use alias as defined in tsconfig.json when poss...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🔇 Additional comments (5)
apps/desktop/package.json (1)

64-65: Zustand dependency addition looks good; ensure lockfile/typechecks are updated

The new zustand dependency fits the added store/component usage and JSON stays valid. Please just make sure the lockfile is updated and apps/desktop still passes the typecheck script after this addition.

apps/desktop/src/shared/ipc-channels/ui.ts (1)

5-5: Import reorder is a no-op and safe

Reordering the { MosaicNode, Tab } type import doesn’t change behavior or typings; this is safe.

apps/desktop/src/shared/ipc-channels/index.ts (1)

16-32: IPC channels composition remains consistent

Adding/reordering WorktreeChannels in the imports and IpcChannels extension list keeps the aggregate interface consistent and type-safe; no behavioral change here.

apps/desktop/src/renderer/stores/index.ts (1)

1-7: Barrel export is straightforward and matches documented import pattern

Centralizing store exports via export * from "./example-store"; aligns with the docs’ import { ... } from "renderer/stores"; examples and keeps imports tidy.

apps/desktop/src/renderer/components/ZustandExample.tsx (1)

1-68: Example component cleanly demonstrates the intended Zustand patterns

The component nicely showcases:

  • Selector hooks (useCount, useText) for state to minimize re-renders.
  • Direct store selection for actions (increment, decrement, setText, reset).
  • Simple, readable wiring between UI controls and store actions.

This is a good, self-contained demo component; no changes needed from my side.

Comment thread apps/desktop/CLAUDE.md
Comment on lines +1 to +8
# Implementation details
For Electron interprocess communnication, ALWAYS use trpc as defined in `src/lib/trpc`
Please use alias as defined in `tsconfig.json` when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.

Please follow the clean code rules:


# Code quality
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Tighten wording and fix minor spelling in this section

The new guideline about preferring Zustand and minimizing effects is clear and matches the new store patterns. A couple of small polish tweaks you might consider in the same block:

  • Line 2: communnicationcommunication
  • Line 3: aliasaliases (to match plural usage)

Content-wise this section looks good.

🧰 Tools
🪛 LanguageTool

[grammar] ~2-~2: Ensure spelling is correct
Context: ...ation details For Electron interprocess communnication, ALWAYS use trpc as defined in `src/lib...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~3-~3: Ensure spelling is correct
Context: ...as defined in src/lib/trpc Please use alias as defined in tsconfig.json when poss...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
In apps/desktop/CLAUDE.md around lines 1 to 8, tighten wording and fix typos:
change "communnication" to "communication" on line 2 and change "alias" to
plural "aliases" on line 3, and while here, slightly rephrase the third sentence
for clarity (e.g., "Prefer Zustand for state management when appropriate; avoid
useEffect unless absolutely necessary.") so the guidance reads cleanly and
consistently.

Copy link
Copy Markdown
Contributor

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

🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/TopBar/TopBar.tsx (1)

2-2: Tabs integration into TopBar looks consistent; consider future overflow handling

The new no-drag middle section with <Tabs /> is wired correctly and respects the drag/no-drag separation for the title bar. One thing to watch for later: with flex-1 overflow-hidden, a large number of tabs will simply be clipped rather than scrollable; if you expect many tabs, adding horizontal scrolling (e.g. overflow-x-auto) or a more explicit overflow strategy would improve usability.

Also applies to: 18-20

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da333e9 and 9bb0f08.

📒 Files selected for processing (6)
  • apps/desktop/package.json (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/TopBar.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/components/Tabs/Tabs.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/components/Tabs/index.ts (1 hunks)
  • apps/desktop/src/renderer/stores/index.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/renderer/stores/index.ts
  • apps/desktop/package.json
🧰 Additional context used
🧬 Code graph analysis (2)
apps/desktop/src/renderer/screens/main/components/TopBar/components/Tabs/Tabs.tsx (2)
apps/desktop/src/renderer/stores/tabs.ts (1)
  • useTabsStore (24-71)
apps/desktop/src/renderer/screens/main/components/TopBar/components/Tabs/index.ts (1)
  • Tabs (1-1)
apps/desktop/src/renderer/screens/main/components/TopBar/TopBar.tsx (1)
apps/desktop/src/renderer/screens/main/components/TopBar/components/Tabs/Tabs.tsx (1)
  • Tabs (70-93)
🔇 Additional comments (1)
apps/desktop/src/renderer/screens/main/components/TopBar/components/Tabs/index.ts (1)

1-1: Barrel export for Tabs is clean and appropriate

Simple re-export keeps the Tabs public API tidy and matches how TopBar consumes it. No issues here.

Comment on lines +1 to +93
import { X } from "lucide-react";
import { useDrag, useDrop } from "react-dnd";
import { useTabsStore } from "renderer/stores/tabs";

const TAB_TYPE = "TAB";

interface TabItemProps {
id: string;
title: string;
isActive: boolean;
index: number;
}

function TabItem({ id, title, isActive, index }: TabItemProps) {
const { setActiveTab, removeTab, reorderTabs } = useTabsStore();

const [{ isDragging }, drag] = useDrag({
type: TAB_TYPE,
item: { id, index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});

const [, drop] = useDrop({
accept: TAB_TYPE,
hover: (item: { id: string; index: number }) => {
if (item.index !== index) {
reorderTabs(item.index, index);
item.index = index;
}
},
});

return (
<button
type="button"
ref={(node) => {
drag(drop(node));
}}
onClick={() => setActiveTab(id)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
setActiveTab(id);
}
}}
tabIndex={0}
className={`
group relative flex items-center gap-2 px-4 h-full min-w-[120px] max-w-[240px] cursor-pointer
${isActive ? "bg-background" : "bg-muted/50 hover:bg-muted"}
${isDragging ? "opacity-50" : "opacity-100"}
border-r border-border
`}
>
<span className="flex-1 truncate text-sm text-foreground">{title}</span>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
removeTab(id);
}}
className="opacity-0 group-hover:opacity-100 hover:bg-muted-foreground/20 rounded p-0.5 transition-opacity"
>
<X className="w-3.5 h-3.5" />
</button>
</button>
);
}

export function Tabs() {
const { tabs, activeTabId, addTab } = useTabsStore();

return (
<div className="flex items-center h-full">
{tabs.map((tab, index) => (
<TabItem
key={tab.id}
id={tab.id}
title={tab.title}
isActive={tab.id === activeTabId}
index={index}
/>
))}
<button
type="button"
onClick={addTab}
className="px-3 h-full hover:bg-muted transition-colors text-muted-foreground hover:text-foreground"
>
<span className="text-lg">+</span>
</button>
</div>
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix nested <button> structure and add basic tab ARIA roles

TabItem currently renders a <button> that contains another <button> for the close icon. Nested buttons are invalid HTML and can cause unpredictable focus/keyboard behavior and accessibility issues.

You can keep the UX the same by making the outer element a focusable div with tab semantics, and leaving the inner close button as-is. While you’re here, adding minimal ARIA roles will improve accessibility for the tab strip.

Suggested update:

 function TabItem({ id, title, isActive, index }: TabItemProps) {
 	const { setActiveTab, removeTab, reorderTabs } = useTabsStore();
@@
-	return (
-		<button
-			type="button"
-			ref={(node) => {
-				drag(drop(node));
-			}}
-			onClick={() => setActiveTab(id)}
-			onKeyDown={(e) => {
-				if (e.key === "Enter" || e.key === " ") {
-					setActiveTab(id);
-				}
-			}}
-			tabIndex={0}
-			className={`
+	return (
+		<div
+			ref={(node) => {
+				drag(drop(node));
+			}}
+			role="tab"
+			aria-selected={isActive}
+			onClick={() => setActiveTab(id)}
+			onKeyDown={(e) => {
+				if (e.key === "Enter" || e.key === " ") {
+					e.preventDefault();
+					setActiveTab(id);
+				}
+			}}
+			tabIndex={0}
+			className={`
 				group relative flex items-center gap-2 px-4 h-full min-w-[120px] max-w-[240px] cursor-pointer
 				${isActive ? "bg-background" : "bg-muted/50 hover:bg-muted"}
 				${isDragging ? "opacity-50" : "opacity-100"}
 				border-r border-border
 			`}
 		>
 			<span className="flex-1 truncate text-sm text-foreground">{title}</span>
 			<button
 				type="button"
 				onClick={(e) => {
 					e.stopPropagation();
 					removeTab(id);
 				}}
 				className="opacity-0 group-hover:opacity-100 hover:bg-muted-foreground/20 rounded p-0.5 transition-opacity"
 			>
 				<X className="w-3.5 h-3.5" />
 			</button>
-		</button>
+		</div>
 	);
 }
@@
 export function Tabs() {
 	const { tabs, activeTabId, addTab } = useTabsStore();
 
 	return (
-		<div className="flex items-center h-full">
+		<div className="flex items-center h-full" role="tablist">
 			{tabs.map((tab, index) => (
 				<TabItem
 					key={tab.id}

This removes the invalid nesting, keeps drag-and-drop / click behavior intact, and gives assistive tech a clearer model of the tab strip. You can extend this later with aria-controls/tab panels as needed.

📝 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
import { X } from "lucide-react";
import { useDrag, useDrop } from "react-dnd";
import { useTabsStore } from "renderer/stores/tabs";
const TAB_TYPE = "TAB";
interface TabItemProps {
id: string;
title: string;
isActive: boolean;
index: number;
}
function TabItem({ id, title, isActive, index }: TabItemProps) {
const { setActiveTab, removeTab, reorderTabs } = useTabsStore();
const [{ isDragging }, drag] = useDrag({
type: TAB_TYPE,
item: { id, index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
const [, drop] = useDrop({
accept: TAB_TYPE,
hover: (item: { id: string; index: number }) => {
if (item.index !== index) {
reorderTabs(item.index, index);
item.index = index;
}
},
});
return (
<button
type="button"
ref={(node) => {
drag(drop(node));
}}
onClick={() => setActiveTab(id)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
setActiveTab(id);
}
}}
tabIndex={0}
className={`
group relative flex items-center gap-2 px-4 h-full min-w-[120px] max-w-[240px] cursor-pointer
${isActive ? "bg-background" : "bg-muted/50 hover:bg-muted"}
${isDragging ? "opacity-50" : "opacity-100"}
border-r border-border
`}
>
<span className="flex-1 truncate text-sm text-foreground">{title}</span>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
removeTab(id);
}}
className="opacity-0 group-hover:opacity-100 hover:bg-muted-foreground/20 rounded p-0.5 transition-opacity"
>
<X className="w-3.5 h-3.5" />
</button>
</button>
);
}
export function Tabs() {
const { tabs, activeTabId, addTab } = useTabsStore();
return (
<div className="flex items-center h-full">
{tabs.map((tab, index) => (
<TabItem
key={tab.id}
id={tab.id}
title={tab.title}
isActive={tab.id === activeTabId}
index={index}
/>
))}
<button
type="button"
onClick={addTab}
className="px-3 h-full hover:bg-muted transition-colors text-muted-foreground hover:text-foreground"
>
<span className="text-lg">+</span>
</button>
</div>
);
}
import { X } from "lucide-react";
import { useDrag, useDrop } from "react-dnd";
import { useTabsStore } from "renderer/stores/tabs";
const TAB_TYPE = "TAB";
interface TabItemProps {
id: string;
title: string;
isActive: boolean;
index: number;
}
function TabItem({ id, title, isActive, index }: TabItemProps) {
const { setActiveTab, removeTab, reorderTabs } = useTabsStore();
const [{ isDragging }, drag] = useDrag({
type: TAB_TYPE,
item: { id, index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
const [, drop] = useDrop({
accept: TAB_TYPE,
hover: (item: { id: string; index: number }) => {
if (item.index !== index) {
reorderTabs(item.index, index);
item.index = index;
}
},
});
return (
<div
ref={(node) => {
drag(drop(node));
}}
role="tab"
aria-selected={isActive}
onClick={() => setActiveTab(id)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setActiveTab(id);
}
}}
tabIndex={0}
className={`
group relative flex items-center gap-2 px-4 h-full min-w-[120px] max-w-[240px] cursor-pointer
${isActive ? "bg-background" : "bg-muted/50 hover:bg-muted"}
${isDragging ? "opacity-50" : "opacity-100"}
border-r border-border
`}
>
<span className="flex-1 truncate text-sm text-foreground">{title}</span>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
removeTab(id);
}}
className="opacity-0 group-hover:opacity-100 hover:bg-muted-foreground/20 rounded p-0.5 transition-opacity"
>
<X className="w-3.5 h-3.5" />
</button>
</div>
);
}
export function Tabs() {
const { tabs, activeTabId, addTab } = useTabsStore();
return (
<div className="flex items-center h-full" role="tablist">
{tabs.map((tab, index) => (
<TabItem
key={tab.id}
id={tab.id}
title={tab.title}
isActive={tab.id === activeTabId}
index={index}
/>
))}
<button
type="button"
onClick={addTab}
className="px-3 h-full hover:bg-muted transition-colors text-muted-foreground hover:text-foreground"
>
<span className="text-lg">+</span>
</button>
</div>
);
}

Comment on lines +1 to +71
import { create } from "zustand";
import { devtools } from "zustand/middleware";

export interface Tab {
id: string;
title: string;
}

interface TabsState {
tabs: Tab[];
activeTabId: string | null;

addTab: () => void;
removeTab: (id: string) => void;
setActiveTab: (id: string) => void;
reorderTabs: (startIndex: number, endIndex: number) => void;
}

const createNewTab = (): Tab => ({
id: `tab-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
title: "New Tab",
});

export const useTabsStore = create<TabsState>()(
devtools(
(set) => ({
tabs: [{ id: "tab-1", title: "Home" }],
activeTabId: "tab-1",

addTab: () => {
const newTab = createNewTab();
set((state) => ({
tabs: [...state.tabs, newTab],
activeTabId: newTab.id,
}));
},

removeTab: (id) => {
set((state) => {
const tabs = state.tabs.filter((tab) => tab.id !== id);
if (tabs.length === 0) {
const newTab = createNewTab();
return { tabs: [newTab], activeTabId: newTab.id };
}

if (id === state.activeTabId) {
const closedIndex = state.tabs.findIndex((tab) => tab.id === id);
const nextTab = tabs[closedIndex] || tabs[closedIndex - 1];
return { tabs, activeTabId: nextTab.id };
}

return { tabs };
});
},

setActiveTab: (id) => {
set({ activeTabId: id });
},

reorderTabs: (startIndex, endIndex) => {
set((state) => {
const tabs = [...state.tabs];
const [removed] = tabs.splice(startIndex, 1);
tabs.splice(endIndex, 0, removed);
return { tabs };
});
},
}),
{ name: "TabsStore" },
),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard removeTab against non-existent ids; activeTabId nullability is unused

The core store logic looks solid, but there are a couple of edge cases worth tightening:

  1. Potential runtime error in removeTab when id is not found

If removeTab is ever called with an id that isn’t present in state.tabs but happens to equal activeTabId, findIndex returns -1, and nextTab becomes undefined, leading to nextTab.id throwing.

A small guard makes this safer without changing behavior for valid ids:

-			removeTab: (id) => {
-				set((state) => {
-					const tabs = state.tabs.filter((tab) => tab.id !== id);
+			removeTab: (id) => {
+				set((state) => {
+					const tabs = state.tabs.filter((tab) => tab.id !== id);
 					if (tabs.length === 0) {
 						const newTab = createNewTab();
 						return { tabs: [newTab], activeTabId: newTab.id };
 					}
 
-					if (id === state.activeTabId) {
-						const closedIndex = state.tabs.findIndex((tab) => tab.id === id);
+					const closedIndex = state.tabs.findIndex((tab) => tab.id === id);
+
+					if (id === state.activeTabId && closedIndex !== -1) {
 						const nextTab = tabs[closedIndex] || tabs[closedIndex - 1];
 						return { tabs, activeTabId: nextTab.id };
 					}
 
 					return { tabs };
 				});
 			},
  1. activeTabId type could be simplified

Given that you always ensure at least one tab exists and always set activeTabId to a valid tab id (never null), you could simplify the state by making activeTabId just string instead of string | null, or explicitly handle the null case if you plan to support “no active tab” in the future.

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/stores/tabs.ts around lines 1-71, add a guard in
removeTab to handle IDs not present in state.tabs and avoid accessing nextTab.id
when closedIndex is -1: check findIndex result and if id not found simply return
current state (or no-op), and when computing the replacement active tab ensure
you pick a valid tab (e.g., tabs[closedIndex] || tabs[closedIndex - 1] ||
tabs[0] || tabs[tabs.length-1]) before reading .id; optionally simplify the
activeTabId type from string | null to string since the store always maintains
at least one tab and always sets a valid activeTabId.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/Tabs.tsx (1)

42-46: Space key handling is redundant for button elements.

The onKeyDown handler checks for the space key, but HTML button elements already trigger onClick when space is pressed by default. The space key check can be removed.

Apply this diff to simplify the keyboard handler:

-			onKeyDown={(e) => {
-				if (e.key === "Enter" || e.key === " ") {
-					setActiveTab(id);
-				}
-			}}
+			onKeyDown={(e) => {
+				if (e.key === "Enter") {
+					setActiveTab(id);
+				}
+			}}

Alternatively, you could remove the onKeyDown handler entirely and rely solely on onClick, as buttons handle both Enter and Space keys natively.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9bb0f08 and 526532c.

📒 Files selected for processing (3)
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/Tabs.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/TopBar.tsx (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.ts
🧰 Additional context used
🧬 Code graph analysis (2)
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/Tabs.tsx (1)
apps/desktop/src/renderer/stores/tabs.ts (1)
  • useTabsStore (24-71)
apps/desktop/src/renderer/screens/main/components/TopBar/TopBar.tsx (2)
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/Tabs.tsx (1)
  • Tabs (70-98)
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.ts (1)
  • Tabs (1-1)
🔇 Additional comments (6)
apps/desktop/src/renderer/screens/main/components/TopBar/TopBar.tsx (2)

2-2: LGTM!

The import correctly references the Tabs component from the sibling Tabs directory.


18-20: LGTM!

The middle section correctly integrates the Tabs component with appropriate styling (flex-1, overflow-hidden) and the no-drag class to allow user interaction within the draggable window area.

apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/Tabs.tsx (4)

1-5: LGTM!

Imports are correctly structured, bringing in the necessary dependencies for the drag-and-drop tab functionality and icon rendering.


56-66: LGTM!

The close button correctly uses stopPropagation to prevent triggering the parent tab's activation handler. The opacity transition on hover provides good visual feedback.


89-95: LGTM!

The add tab button correctly invokes the store's addTab action and provides appropriate hover states and visual feedback.


17-33: The original review comment is incorrect—DndProvider is already properly configured.

The DndProvider is already set up in MainScreen.tsx and wraps the entire component tree (including TopBar and Tabs). The drag-and-drop hooks will work correctly without any changes needed.

Likely an incorrect or invalid review comment.

/>
))}
</div>
<div className="pointer-events-none absolute right-0 top-0 h-full w-8 bg-linear-to-l from-background to-transparent" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix invalid Tailwind CSS class.

The class bg-linear-to-l is not a valid Tailwind CSS class. It should be bg-gradient-to-l for a left-to-right gradient background.

Apply this diff to fix the gradient class:

-				<div className="pointer-events-none absolute right-0 top-0 h-full w-8 bg-linear-to-l from-background to-transparent" />
+				<div className="pointer-events-none absolute right-0 top-0 h-full w-8 bg-gradient-to-l from-background to-transparent" />
📝 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
<div className="pointer-events-none absolute right-0 top-0 h-full w-8 bg-linear-to-l from-background to-transparent" />
<div className="pointer-events-none absolute right-0 top-0 h-full w-8 bg-gradient-to-l from-background to-transparent" />
🤖 Prompt for AI Agents
In apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/Tabs.tsx around
line 87, the Tailwind class `bg-linear-to-l` is invalid; replace it with the
correct `bg-gradient-to-l` so the element uses a leftward gradient, preserving
the other classes (`pointer-events-none absolute right-0 top-0 h-full w-8
from-background to-transparent`) unchanged.

Copy link
Copy Markdown
Contributor

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

🧹 Nitpick comments (3)
packages/ui/src/components/separator.tsx (1)

1-1: Optimize React import for type-only usage.

The pipeline correctly flags that React is only used for type annotations here. Using import type avoids unnecessary runtime imports.

Apply this diff:

-import * as React from "react";
+import type * as React from "react";
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx (2)

13-13: Consider removing layout adjustment from component.

The mt-2 class suggests the button is being positioned relative to its container. This is typically better handled by the parent layout (Tabs component) to keep positioning concerns separated.


15-15: Consider using an icon component for consistency.

Using a text "+" is less consistent with the icon usage throughout the UI (e.g., PanelLeft, PanelRight in SidebarControl). Consider importing Plus from lucide-react for visual consistency.

Apply this diff:

 import { Button } from "@superset/ui/button";
+import { Plus } from "lucide-react";
 import { useTabsStore } from "renderer/stores/tabs";

 export function AddTabButton() {
 	const { addTab } = useTabsStore();

 	return (
 		<Button
 			variant="ghost"
 			size="icon"
 			onClick={addTab}
 			aria-label="Add new tab"
 			className="mt-2"
 		>
-			<span className="text-lg">+</span>
+			<Plus className="size-4" />
 		</Button>
 	);
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 526532c and 51e2c70.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • apps/desktop/src/renderer/lib/utils.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/SidebarControl.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (1 hunks)
  • apps/desktop/src/renderer/stores/app-state.ts (1 hunks)
  • apps/desktop/src/renderer/stores/index.ts (1 hunks)
  • packages/ui/package.json (2 hunks)
  • packages/ui/src/components/separator.tsx (1 hunks)
💤 Files with no reviewable changes (2)
  • apps/desktop/src/renderer/lib/utils.ts
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/stores/index.ts
🧰 Additional context used
🧬 Code graph analysis (7)
packages/ui/src/components/separator.tsx (1)
packages/ui/src/lib/utils.ts (1)
  • cn (4-6)
apps/desktop/src/renderer/screens/main/components/TopBar/SidebarControl.tsx (1)
apps/desktop/src/renderer/stores/app-state.ts (1)
  • useAppStore (10-25)
apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (2)
apps/desktop/src/renderer/screens/main/components/TopBar/SidebarControl.tsx (1)
  • SidebarControl (5-23)
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.tsx (1)
  • Tabs (11-117)
apps/desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx (1)
apps/desktop/src/renderer/stores/app-state.ts (1)
  • useAppStore (10-25)
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx (2)
apps/desktop/src/renderer/stores/tabs.ts (1)
  • useTabsStore (24-71)
packages/ui/src/lib/utils.ts (1)
  • cn (4-6)
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx (2)
apps/desktop/src/renderer/stores/tabs.ts (1)
  • useTabsStore (24-71)
packages/ui/src/components/button.tsx (1)
  • Button (61-61)
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.tsx (4)
apps/desktop/src/renderer/stores/tabs.ts (1)
  • useTabsStore (24-71)
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx (1)
  • TabItem (19-98)
packages/ui/src/components/separator.tsx (1)
  • Separator (25-25)
apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/AddTabButton.tsx (1)
  • AddTabButton (4-18)
🪛 GitHub Actions: CI
packages/ui/src/components/separator.tsx

[warning] 1-1: lint/style/useImportType: Importing React types via normal import. Consider using 'import type' to avoid runtime impact.

🔇 Additional comments (9)
packages/ui/package.json (1)

22-22: LGTM!

The separator export and dependency addition follow the established patterns in this package.

Also applies to: 39-39

packages/ui/src/components/separator.tsx (1)

5-23: LGTM!

The Separator component is well-implemented with proper defaults, accessible attributes, and orientation-based styling.

apps/desktop/src/renderer/stores/app-state.ts (1)

10-25: LGTM!

The Zustand store is correctly implemented with devtools middleware and provides both toggle and explicit setter methods for flexibility.

apps/desktop/src/renderer/screens/main/components/Sidebar/Sidebar.tsx (1)

8-67: LGTM!

The sidebar animation implementation is well-structured with smooth transitions. The staggered timing (0.15s for opacity, 0.2s for width) creates a polished fade-out effect, and the pointerEvents toggle properly disables interaction when closed.

apps/desktop/src/renderer/screens/main/components/TopBar/SidebarControl.tsx (1)

5-23: LGTM!

The SidebarControl component is well-implemented with appropriate accessibility labels, semantic icon choices, and the no-drag class for proper Electron window behavior.

apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (1)

2-3: LGTM!

The TopBar integration cleanly incorporates the new SidebarControl and Tabs components with proper flexbox layout and spacing.

Also applies to: 10-21

apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.tsx (2)

20-59: LGTM!

The effect correctly handles scroll detection and tab width calculation with proper cleanup of event listeners.


68-102: LGTM!

The tab rendering logic correctly manages separator visibility based on active and hover states, creating smooth visual transitions.

apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx (1)

19-47: Core tab behavior and drag‑reorder integration look solid

State wiring to useTabsStore and the react-dnd pattern of updating item.index after calling reorderTabs(item.index, index) are consistent and should keep the store and UI in sync. Activation and close behavior are also clear and side‑effect free apart from the intended store updates.

Also applies to: 80-87

Comment on lines +106 to +111
{showStartFade && (
<div className="pointer-events-none absolute left-0 top-0 h-full w-8 bg-linear-to-r from-background to-transparent" />
)}
{showEndFade && (
<div className="pointer-events-none absolute right-0 top-0 h-full w-8 bg-linear-to-l from-background to-transparent" />
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix invalid Tailwind gradient classes.

The fade effects use bg-linear-to-r and bg-linear-to-l, which are not valid Tailwind classes. The correct syntax is bg-gradient-to-r and bg-gradient-to-l.

Apply this diff:

 {showStartFade && (
-	<div className="pointer-events-none absolute left-0 top-0 h-full w-8 bg-linear-to-r from-background to-transparent" />
+	<div className="pointer-events-none absolute left-0 top-0 h-full w-8 bg-gradient-to-r from-background to-transparent" />
 )}
 {showEndFade && (
-	<div className="pointer-events-none absolute right-0 top-0 h-full w-8 bg-linear-to-l from-background to-transparent" />
+	<div className="pointer-events-none absolute right-0 top-0 h-full w-8 bg-gradient-to-r from-background to-transparent" />
 )}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.tsx
around lines 106 to 111 the gradient utility classes are mistyped as
bg-linear-to-r and bg-linear-to-l; replace them with the correct Tailwind
classes bg-gradient-to-r and bg-gradient-to-l so the fade effects render
properly, keeping the rest of the element structure and class names unchanged.

Comment on lines +53 to +55
{/* Active tab bottom border overlay */}
{isActive && <div className="absolute bottom-0 left-0 right-0 h-px" />}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Active tab bottom border overlay lacks a visible style

The active indicator div has only positioning/size classes and no color/background, so it’s likely invisible and not distinguishing the active tab.

You can make it visibly match the rest of the design, e.g.:

-			{/* Active tab bottom border overlay */}
-			{isActive && <div className="absolute bottom-0 left-0 right-0 h-px" />}
+			{/* Active tab bottom border overlay */}
+			{isActive && (
+				<div className="absolute bottom-0 left-0 right-0 h-px bg-border" />
+			)}

(or use whatever bg-* / border color token matches the tab theme).

📝 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
{/* Active tab bottom border overlay */}
{isActive && <div className="absolute bottom-0 left-0 right-0 h-px" />}
{/* Active tab bottom border overlay */}
{isActive && (
<div className="absolute bottom-0 left-0 right-0 h-px bg-border" />
)}
🤖 Prompt for AI Agents
In apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx
around lines 53 to 55, the active tab bottom border overlay div currently only
has positioning and height classes so it’s invisible; update that element to
include a visible color (for example a Tailwind bg- or border- utility that
matches the design token used for active state, e.g. bg-[token] or
border-b-[token]) so the 1px indicator is visible, and ensure the chosen token
meets contrast/accessibility requirements for the theme.

Comment on lines +80 to +95
<Button
type="button"
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
removeTab(id);
}}
className={cn(
"mt-1 absolute right-1 top-1/2 -translate-y-1/2 size-5",
isActive ? "opacity-100" : "opacity-0 group-hover:opacity-100",
)}
aria-label="Close tab"
>
<X className="size-3" />
</Button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Invisible close buttons remain focusable and clickable

When a tab is inactive, the close Button is opacity-0 but still in the tab order and can receive clicks/focus, which is confusing for keyboard users and may intercept pointer events over “empty” space.

Consider also disabling pointer and keyboard interaction when the button is visually hidden, for example:

-				className={cn(
-					"mt-1 absolute right-1 top-1/2 -translate-y-1/2 size-5",
-					isActive ? "opacity-100" : "opacity-0 group-hover:opacity-100",
-				)}
+				className={cn(
+					"mt-1 absolute right-1 top-1/2 -translate-y-1/2 size-5",
+					isActive
+						? "opacity-100"
+						: "pointer-events-none opacity-0 group-hover:opacity-100 group-hover:pointer-events-auto",
+				)}
+				tabIndex={isActive ? 0 : -1}

This keeps the close button accessible when appropriate while avoiding focus on invisible controls.

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx
around lines 80–95, the close Button is only visually hidden via opacity-0 but
remains focusable and clickable; update the Button so when the tab is inactive
(isActive false) it is removed from the tab order and pointer events are
disabled: add conditional props/classes such as pointer-events-none and
aria-hidden="true" (or disabled) and set tabIndex to -1 when hidden, and restore
pointer events, tabIndex (or remove disabled/aria-hidden) when isActive is true
so the control is accessible only when visible.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/Sidebar/index.tsx (1)

52-62: Consider adding content or documenting the empty bottom section.

The bottom motion.div has animation logic but no content. If this is a placeholder for future implementation (e.g., version info, user profile), consider adding a TODO comment for clarity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51e2c70 and b88617a.

📒 Files selected for processing (7)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/index.ts (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/SidebarControl.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.tsx (1 hunks)
  • apps/desktop/src/renderer/stores/index.ts (1 hunks)
  • apps/desktop/src/renderer/stores/sidebar-state.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/desktop/src/renderer/screens/main/components/Sidebar/index.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/desktop/src/renderer/screens/main/components/TopBar/SidebarControl.tsx
  • apps/desktop/src/renderer/stores/index.ts
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/index.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/Tabs/TabItem.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
apps/desktop/src/renderer/screens/main/components/Sidebar/index.tsx (1)
apps/desktop/src/renderer/stores/sidebar-state.ts (1)
  • useSidebarStore (10-25)
🔇 Additional comments (2)
apps/desktop/src/renderer/stores/sidebar-state.ts (1)

1-25: LGTM! Clean Zustand store implementation.

The sidebar state store is well-structured and follows Zustand best practices. The use of devtools middleware, functional updates in toggleSidebar, and proper TypeScript typing all look good.

apps/desktop/src/renderer/screens/main/components/Sidebar/index.tsx (1)

22-50: LGTM! Animation and layout are well-structured.

The opacity animation on the scrollable content section works well with the sidebar collapse animation. The navigation placeholder structure is appropriate for initial implementation.

Comment on lines +8 to +21
<motion.aside
initial={false}
animate={{
width: isSidebarOpen ? 256 : 0,
}}
transition={{
duration: 0.2,
ease: "easeInOut",
}}
className="h-full border-r border-sidebar-border bg-sidebar flex flex-col overflow-hidden"
style={{
pointerEvents: isSidebarOpen ? "auto" : "none",
}}
>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add accessibility attributes for the collapsed state.

When isSidebarOpen is false, the sidebar content remains keyboard-accessible despite pointerEvents: "none". Keyboard users may tab into invisible elements, and screen readers will still announce the hidden content.

Apply this diff to improve accessibility:

 <motion.aside
 	initial={false}
 	animate={{
 		width: isSidebarOpen ? 256 : 0,
 	}}
 	transition={{
 		duration: 0.2,
 		ease: "easeInOut",
 	}}
 	className="h-full border-r border-sidebar-border bg-sidebar flex flex-col overflow-hidden"
 	style={{
 		pointerEvents: isSidebarOpen ? "auto" : "none",
 	}}
+	aria-hidden={!isSidebarOpen}
 >
📝 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
<motion.aside
initial={false}
animate={{
width: isSidebarOpen ? 256 : 0,
}}
transition={{
duration: 0.2,
ease: "easeInOut",
}}
className="h-full border-r border-sidebar-border bg-sidebar flex flex-col overflow-hidden"
style={{
pointerEvents: isSidebarOpen ? "auto" : "none",
}}
>
<motion.aside
initial={false}
animate={{
width: isSidebarOpen ? 256 : 0,
}}
transition={{
duration: 0.2,
ease: "easeInOut",
}}
className="h-full border-r border-sidebar-border bg-sidebar flex flex-col overflow-hidden"
style={{
pointerEvents: isSidebarOpen ? "auto" : "none",
}}
aria-hidden={!isSidebarOpen}
>

@coderabbitai coderabbitai Bot mentioned this pull request Dec 27, 2025
5 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Feb 8, 2026
5 tasks
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