Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/desktop/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# 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
Comment on lines +1 to +8
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.

```
Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.
_____________________________________
Expand Down
7 changes: 4 additions & 3 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"resources": "src/resources",
"author": {
"name": "Superset",
"email": "hi@superset.com"
"email": "hi@superset.sh"
},
"scripts": {
"start": "electron-vite preview",
Expand Down Expand Up @@ -48,20 +48,21 @@
"framer-motion": "^12.23.24",
"http-proxy": "^1.18.1",
"lowdb": "^7.0.1",
"lucide-react": "^0.553.0",
"node-pty": "1.1.0-beta30",
"react": "^19.1.1",
"react-arborist": "^3.4.3",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^19.1.1",
"react-icons": "^5.5.0",
"react-mosaic-component": "^6.1.1",
"react-resizable-panels": "^3.0.6",
"react-router-dom": "^7.8.2",
"react-syntax-highlighter": "^16.1.0",
"superjson": "^2.2.5",
"tailwind-merge": "^2.6.0",
"trpc-electron": "^0.1.2"
"trpc-electron": "^0.1.2",
"zustand": "^5.0.8"
},
"devDependencies": {
"@biomejs/biome": "^2.2.6",
Expand Down
69 changes: 69 additions & 0 deletions apps/desktop/src/renderer/components/ZustandExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useCount, useExampleStore, useText } from "renderer/stores";

/**
* Example component demonstrating Zustand usage patterns
*
* This component shows:
* 1. Using selector hooks for optimized re-renders
* 2. Accessing actions from the store
* 3. Direct store access for multiple values
*/
export function ZustandExample() {
// Optimized selectors - only re-renders when specific values change
const count = useCount();
const text = useText();

// Access actions directly from the store
const increment = useExampleStore((state) => state.increment);
const decrement = useExampleStore((state) => state.decrement);
const setText = useExampleStore((state) => state.setText);
const reset = useExampleStore((state) => state.reset);

return (
<div className="p-4 space-y-4">
<h2 className="text-xl font-bold">Zustand Example</h2>

{/* Counter Example */}
<div className="space-y-2">
<p className="font-semibold">Counter: {count}</p>
<div className="space-x-2">
<button
type="button"
onClick={increment}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Increment
</button>
<button
type="button"
onClick={decrement}
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
>
Decrement
</button>
</div>
</div>

{/* Text Input Example */}
<div className="space-y-2">
<p className="font-semibold">Text: {text}</p>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type something..."
className="px-3 py-2 border rounded w-full"
/>
</div>

{/* Reset Button */}
<button
type="button"
onClick={reset}
className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
>
Reset All
</button>
</div>
);
}
6 changes: 0 additions & 6 deletions apps/desktop/src/renderer/lib/utils.ts

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { motion } from "framer-motion";
import { useSidebarStore } from "renderer/stores";

export function Sidebar() {
const { isSidebarOpen } = useSidebarStore();

return (
<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",
}}
>
Comment on lines +8 to +21
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}
>

<motion.div
initial={false}
animate={{
opacity: isSidebarOpen ? 1 : 0,
}}
transition={{
duration: 0.15,
ease: "easeInOut",
}}
className="p-4 flex-1 overflow-y-auto"
>
<nav className="space-y-2">
{/* Add navigation items here */}
<div className="text-sm text-sidebar-foreground">
<p className="font-medium mb-2">Navigation</p>
<ul className="space-y-1">
<li className="px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer">
Dashboard
</li>
<li className="px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer">
Projects
</li>
<li className="px-3 py-2 rounded-md hover:bg-sidebar-accent cursor-pointer">
Settings
</li>
</ul>
</div>
</nav>
</motion.div>

<motion.div
initial={false}
animate={{
opacity: isSidebarOpen ? 1 : 0,
}}
transition={{
duration: 0.15,
ease: "easeInOut",
}}
className="p-4 border-t border-sidebar-border"
></motion.div>
</motion.aside>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Button } from "@superset/ui/button";
import { HiMiniBars3, HiMiniBars3BottomLeft } from "react-icons/hi2";
import { useSidebarStore } from "renderer/stores";

export function SidebarControl() {
const { isSidebarOpen, toggleSidebar } = useSidebarStore();

return (
<Button
variant="ghost"
size="icon"
onClick={toggleSidebar}
aria-label="Toggle sidebar"
className="no-drag"
>
{isSidebarOpen ? (
<HiMiniBars3BottomLeft className="size-4" />
) : (
<HiMiniBars3 className="size-4" />
)}
</Button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Button } from "@superset/ui/button";
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>
</Button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Button } from "@superset/ui/button";
import { cn } from "@superset/ui/utils";
import { useDrag, useDrop } from "react-dnd";
import { HiMiniXMark } from "react-icons/hi2";
import { useTabsStore } from "renderer/stores/tabs";

const TAB_TYPE = "TAB";

interface TabItemProps {
id: string;
title: string;
isActive: boolean;
index: number;
width: number;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
}

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

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

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
className="group relative flex items-end shrink-0 h-full"
style={{ width: `${width}px` }}
>
{/* Active tab bottom border overlay */}
{isActive && <div className="absolute bottom-0 left-0 right-0 h-px" />}

Comment on lines +56 to +58
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.

{/* Main tab button */}
<button
type="button"
ref={(node) => {
drag(drop(node));
}}
onClick={() => setActiveTab(id)}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={`
flex items-center gap-0.5 rounded-t-md transition-all w-full shrink-0 pr-6 pl-3 h-[80%]
${
isActive
? "text-foreground bg-sidebar"
: "text-muted-foreground hover:text-foreground hover:bg-muted/30"
}
${isDragging ? "opacity-30" : "opacity-100"}
`}
style={{ cursor: isDragging ? "grabbing" : "grab" }}
>
<span className="text-sm whitespace-nowrap truncate flex-1 text-left">
{title}
</span>
</button>

<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-90" : "opacity-0 group-hover:opacity-90",
)}
aria-label="Close tab"
>
<HiMiniXMark className="size-4" />
</Button>
Comment on lines +84 to +99
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.

</div>
);
}
Loading
Loading