RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Diff/spacedriveapp-spacedrive-packages-interface-claude ↔ spacedriveapp-spacedrive-core-agents

Comparison

A · CLAUDE.md · spacedriveapp/spacedriveB · AGENTS.md · spacedriveapp/spacedrive
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections060100%
Commands0170%
Section tags25418%

What each file covers

Sections

0 shared · 60 only in A · 10 only in B
  • − Spacedrive Interface Development Rules
  • − Core Principles
  • − Package Architecture
  • − What Lives Where
  • − Current Structure
  • − Architecture Layers
  • − Code Style Rules
  • − React 19 Standards
  • − Critical: You Might Not Need an Effect
  • − Examples
  • − Function components only:
  • − Color System Rules
  • − CRITICAL: Always Use Semantic Tailwind Classes
  • − Color Categories
  • − Opacity Modifiers
  • − Component Rules
  • − Primitive vs Feature Components
  • − Component Structure
  • − Naming Conventions
  • − Styling Rules
  • − CRITICAL: Never Use Style Tags
  • − Tailwind Class Order
  • − Rounding (V2 Style)
  • − Animation
  • − Data Fetching Rules
  • − Use Type-Safe Hooks
  • − Never Fetch Manually
  • − Performance Rules
  • − Virtual Scrolling
  • − Code Splitting
  • − Memoization
  • − Component Composition Rules
  • − Dropdown Example (Current Implementation)
  • − Type Safety Rules
  • − Use Generated Types
  • − Query Type Safety
  • − File Organization Rules
  • − Component Co-location
  • − Exports
  • − macOS-Specific Rules
  • − Native Traffic Lights
  • − Window Styling
  • − Blur Effects
  • − Current Architectural Decisions
  • − 1. Expanding Dropdowns (Not Overlays)
  • − 2. Library Switcher Logic
  • − 3. Color System
  • − 4. Rounded Style (V2)
  • − Development Workflow
  • − Before Writing Code
  • − When Adding Features
  • − When Styling
  • − Common Patterns
  • − Shell Entry Point Pattern
  • − TopBar Portal Pattern
  • − Library Switcher Pattern
  • − Sidebar Item Pattern
  • − Dropdown Pattern
  • − Context Menu Pattern
  • − Type-Safe Query Pattern
  • + AGENTS.md - Spacedrive Core v2
  • + Build/Test Commands
  • + Code Style
  • + Daemon Architecture
  • + **The `Wire` Trait**
  • + **Registration Macros**
  • + **Registry System**
  • + Logging Standards
  • + Documentation
  • + Debug Instructions

Commands

0 shared · 1 only in A · 7 only in B
  • − cargo run --bin generate_typescript_types
  • + cargo build
  • + cargo test
  • + cargo test <test_name>
  • + cargo test library_test
  • + cargo clippy
  • + cargo fmt
  • + cargo run --bin sd-cli -- <command>

Section tags

2 shared · 5 only in A · 4 only in B
  • − architecture
  • − types
  • − ui
  • − performance
  • − do-not
  • + build
  • + test
  • + lint-format
  • + docs
  •   code-style
  •   agent-behaviour

Line diff

+74 added−1049 removed33 unchanged3.0% identical
spacedriveapp/spacedrive · packages/interface/CLAUDE.md
@@ −1 @@
1# Spacedrive Interface Development Rules
2 
3**Status:** Living Document - Update as architectural decisions are made
4**Purpose:** Ensure consistent, clean, and maintainable code across the interface package
5**Audience:** AI assistants and developers working on @sd/interface
6 
7---
 
 
 
 
 
8 
9## Core Principles
10 
111. **Platform Agnostic** - This package works on Tauri, Web, and React Native
122. **Clean Separation** - UI components here, state in @sd/ts-client, primitives in @sd/ui
133. **Type Safety First** - Use auto-generated types, no `any`, strict TypeScript
144. **Performance Matters** - Virtual scrolling, code splitting, memoization when needed
155. **Accessible** - Radix primitives, proper ARIA labels, keyboard navigation
166. **Consistent Styling** - Semantic color system, no arbitrary values
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17 
18---
19 
20## Package Architecture
21 
22### What Lives Where
23 
24**@sd/interface** (this package):
25- Route components and layouts
26- Feature components (Explorer, Settings, etc.)
27- React Query hook wrappers
28- UI composition and interactivity
29- NO state management (use @sd/ts-client)
30- NO primitive components (use @sd/ui)
31- NO platform APIs (use platform prop)
32 
33**@sd/ts-client**:
34- Client implementation
35- Transport layer
36- Auto-generated types from Rust
37- State stores (if needed)
38 
39**@sd/ui**:
40- Primitive components (Button, Input, DropdownMenu, etc.)
41- Reusable, unstyled or minimally styled
42- No business logic
43- No API calls
44 
45### Current Structure
46 
47```
48packages/interface/
49├── src/
50│ ├── Shell.tsx # App entry point (providers, daemon check)
51│ ├── ShellLayout.tsx # Layout shell (sidebar, inspector, TopBar)
52│ ├── router.tsx # Route configuration
53│ ├── DemoWindow.tsx # Demo/testing window
54│ ├── FloatingControls.tsx # Floating controls UI
55│ ├── components/
56│ │ ├── DndProvider.tsx # Drag-and-drop coordinator
57│ │ ├── Explorer/
58│ │ │ ├── ExplorerView.tsx # File browser view
59│ │ │ ├── context.tsx # Explorer state/context
60│ │ │ └── ...
61│ │ ├── QuickPreview/
62│ │ │ ├── Controller.tsx # Preview navigation
63│ │ │ ├── Syncer.tsx # Selection sync
64│ │ │ └── ...
65│ │ └── ...
66│ ├── TopBar/ # TopBar portal system
67│ │ ├── TopBar.tsx
68│ │ ├── Context.tsx
69│ │ └── Portal.tsx
70│ ├── routes/ # Route components
71│ │ └── overview/
72│ ├── hooks/ # React hooks
73│ ├── context.tsx # Client context and hooks
74│ ├── styles.css # Global CSS variables
75│ └── index.tsx # Public exports
76```
77 
78### Architecture Layers
79 
80The interface is organized into clear separation of concerns:
81 
82**Shell Layer** (`Shell.tsx`):
83- Root entry point
84- Provider setup (Spacedrive, Server, TabManager, Platform)
85- Daemon connection management (Tauri-specific)
86 
87**Layout Layer** (`ShellLayout.tsx`):
88- Chrome/frame (sidebar, inspector, TopBar containers)
89- Provider setup (TopBar, Selection, Explorer)
90- Tab bar positioning
91- QuickPreview coordination
92 
93**View Layer** (routes like `ExplorerView.tsx`):
94- Actual content rendering
95- TopBar button registration (via portal)
96- Feature-specific logic
97 
98**Coordination Layer**:
99- `DndProvider.tsx` - Global drag-and-drop
100- `QuickPreview/Controller.tsx` - Preview navigation
101- `QuickPreview/Syncer.tsx` - Selection-to-preview sync
102 
103**Visual Hierarchy:**
104```
105Shell (providers) → DndProvider → Router
106 ↓
107 ShellLayout (chrome)
108 ↓
109 <Outlet> (routes)
110 ↓
111 Overview | ExplorerView | Settings | etc.
112```
113 
114---
115 
116## Code Style Rules
117 
118### React 19 Standards
119 
120### Critical: You Might Not Need an Effect
121 
122**Effects are an escape hatch** - only use them to sync with external systems (network, DOM, browser APIs).
123 
124**DON'T use Effects for:**
125- Transforming data for rendering (calculate during render instead)
126- Handling user events (use event handlers)
127- Updating state based on props (calculate during render or use `key`)
128- Chains of state updates (do in event handler)
129- Initializing app (use module-level code)
130- Notifying parent of changes (pass callback, call in event handler)
131 
132**DO use Effects for:**
133- Subscribing to external systems (WebSocket, browser events)
134- Syncing with non-React widgets
135- Network requests with proper cleanup
136 
137### Examples
138 
139**Wrong - Don't use Effect to transform data:**
140```tsx
141function TodoList({ todos, filter }) {
142 const [visibleTodos, setVisibleTodos] = useState([]);
143 useEffect(() => {
144 setVisibleTodos(getFilteredTodos(todos, filter));
145 }, [todos, filter]);
146 // Extra render pass!
147}
148```
149 
150**Correct - Calculate during render:**
151```tsx
152function TodoList({ todos, filter }) {
153 const visibleTodos = getFilteredTodos(todos, filter);
154 // Or use useMemo if expensive:
155 const visibleTodos = useMemo(
156 () => getFilteredTodos(todos, filter),
157 [todos, filter]
158 );
159}
160```
161 
162**Wrong - Don't use Effect for user events:**
163```tsx
164function ProductPage({ product, addToCart }) {
165 useEffect(() => {
166 if (product.isInCart) {
167 showNotification('Added to cart!');
168 }
169 }, [product]);
170}
171```
172 
173**Correct - Use event handler:**
174```tsx
175function ProductPage({ product, addToCart }) {
176 function buyProduct() {
177 addToCart(product);
178 showNotification('Added to cart!');
179 }
180}
181```
182 
183**Wrong - Don't use Effect to update parent:**
184```tsx
185function Toggle({ onChange }) {
186 const [isOn, setIsOn] = useState(false);
187 useEffect(() => {
188 onChange(isOn); // Too late! Extra render.
189 }, [isOn, onChange]);
190}
191```
192 
193**Correct - Call in event handler:**
194```tsx
195function Toggle({ onChange }) {
196 const [isOn, setIsOn] = useState(false);
197 function updateToggle(nextIsOn) {
198 setIsOn(nextIsOn);
199 onChange(nextIsOn); // Same render pass!
200 }
201}
202```
203 
204**Wrong - Don't chain Effects:**
205```tsx
206useEffect(() => {
207 if (card.gold) setGoldCardCount(c => c + 1);
208}, [card]);
209 
210useEffect(() => {
211 if (goldCardCount > 3) setRound(r => r + 1);
212}, [goldCardCount]);
213// Multiple render passes!
214```
215 
216**Correct - Calculate in event handler:**
217```tsx
218function handlePlaceCard(nextCard) {
219 setCard(nextCard);
220 if (nextCard.gold) {
221 if (goldCardCount < 3) {
222 setGoldCardCount(goldCardCount + 1);
223 } else {
224 setGoldCardCount(0);
225 setRound(round + 1);
226 }
227 }
228 // Single render pass!
229}
230```
231 
232### Function components only:
233```tsx
234// Correct
235function Component({ name }: { name: string }) {
236 return <div>{name}</div>;
237}
238 
239// Wrong
240const Component: React.FC<{ name: string }> = ({ name }) => {
241 return <div>{name}</div>;
242};
243```
244 
245**Hooks must follow rules:**
246```tsx
247// Correct - proper cleanup
248useEffect(() => {
249 const subscription = subscribe();
250 return () => subscription.unsubscribe();
251}, [dependency]);
252 
253// Wrong - missing cleanup
254useEffect(() => {
255 subscribe();
256}, []);
257```
258 
259**Use TypeScript strictly:**
260```tsx
261// Correct - explicit types
262interface ButtonProps {
263 label: string;
264 onClick: () => void;
265}
266 
267function Button({ label, onClick }: ButtonProps) { }
 
 
 
 
 
 
268 
269// Wrong - implicit any
270function Button(props) { }
271```
272 
273---
 
 
 
274 
275## Color System Rules
276 
277### CRITICAL: Always Use Semantic Tailwind Classes
 
278 
279Never use `var()` syntax directly. Always use Tailwind's semantic color classes.
280 
281**WRONG:**
282```tsx
283className="bg-[var(--color-sidebar)]"
284className="text-[var(--color-sidebar-ink)]"
285className="border-[var(--color-accent)]"
286```
287 
288**CORRECT:**
289```tsx
290className="bg-sidebar"
291className="text-sidebar-ink"
292className="border-accent"
293```
294 
295**IMPORTANT:** CSS variables must be defined as comma-separated HSL values (not wrapped in `hsl()`):
296```css
297/* CORRECT - bare values for Tailwind */
298--color-sidebar: 235, 15%, 7%;
299 
300/* WRONG - wrapped in hsl() */
301--color-sidebar: hsl(235, 15%, 7%);
302```
303 
304This is because Tailwind uses `hsla(var(--color-sidebar), <alpha-value>)` which becomes `hsla(235, 15%, 7%, 0.5)` for opacity support.
305 
306### Color Categories
307 
308**Accent:** `accent`, `accent-faint`, `accent-deep`
309- Use for: Primary actions, selections, focus states
310 
311**Text (Ink):** `ink`, `ink-dull`, `ink-faint`
312- Use for: Text hierarchy (primary, secondary, tertiary)
313 
314**Sidebar:** `sidebar`, `sidebar-box`, `sidebar-line`, `sidebar-ink`, `sidebar-selected`, etc.
315- Use for: Sidebar-specific elements
316 
317**App:** `app`, `app-box`, `app-line`, `app-hover`, `app-selected`, etc.
318- Use for: Main content area elements
319 
320**Menu:** `menu`, `menu-line`, `menu-hover`, `menu-ink`, etc.
321- Use for: Dropdowns, context menus
322 
323### Opacity Modifiers
324 
325```tsx
326// Use Tailwind opacity
327className="bg-accent/10"
328className="bg-sidebar/65"
329 
330// Don't use manual alpha
331className="bg-[var(--color-accent)]/10"
332```
333 
334---
335 
336## Component Rules
337 
338### Primitive vs Feature Components
339 
340**Primitives** (@sd/ui):
341- Generic, reusable
342- Minimal styling (or unstyled)
343- No business logic
344- Example: `DropdownMenu`, `Button`, `Input`
345 
346**Feature Components** (@sd/interface):
347- Specific to Spacedrive features
348- Uses primitives
349- Can have business logic
350- Example: `Explorer`, `Sidebar`, `LibrariesDropdown`
351 
352### Component Structure
353 
354```tsx
355// Correct structure
356import { Primitive } from '@sd/ui';
357import { useSomeQuery } from '../context';
358 
359interface ComponentProps {
360 // Props interface
361}
362 
363function Component({ prop }: ComponentProps) {
364 // Hooks first
365 const data = useSomeQuery();
366 
367 // Logic
368 const derived = useMemo(() => transform(data), [data]);
369 
370 // Render
371 return (
372 <Primitive className="semantic-colors">
373 {/* Content */}
374 </Primitive>
375 );
376}
377 
378export { Component };
379```
380 
381### Naming Conventions
382 
383- **Files:** `PascalCase.tsx` for components, `camelCase.ts` for utilities
384- **Components:** `PascalCase` functions
385- **Hooks:** `useCamelCase` pattern
386- **Constants:** `SCREAMING_SNAKE_CASE`
387- **CSS classes:** Semantic names only (`bg-sidebar`, not `bg-gray-900`)
388 
389---
390 
391## Styling Rules
392 
393### CRITICAL: Never Use Style Tags
394 
395**NEVER** use `<style>`, `<style jsx>`, or any inline style tags. Always use Tailwind utility classes.
396 
397**WRONG:**
398```tsx
399<style jsx>{`
400 .slider::-webkit-slider-thumb {
401 background: var(--color-accent);
402 }
403`}</style>
404```
405 
406**CORRECT:**
407```tsx
408className="[&::-webkit-slider-thumb]:bg-accent [&::-webkit-slider-thumb]:rounded-full"
409```
410 
411Use Tailwind's arbitrary variant syntax for pseudo-elements and other edge cases.
412 
413### Tailwind Class Order
414 
415Follow this order for readability:
4161. Layout (`flex`, `grid`, `w-full`, `h-screen`)
4172. Spacing (`p-4`, `m-2`, `gap-2`)
4183. Typography (`text-sm`, `font-medium`)
4194. Colors (`bg-sidebar`, `text-ink`)
4205. Borders (`border`, `border-sidebar-line`, `rounded-lg`)
4216. Effects (`shadow-sm`, `backdrop-blur`)
4227. States (`hover:bg-app-hover`, `focus:ring-accent`)
4238. Transitions (`transition-colors`)
424 
425### Rounding (V2 Style)
426 
427V2 is more rounded than V1. Use:
428- `rounded-lg` for most containers (8px)
429- `rounded-md` for smaller elements (6px)
430- `rounded-full` for pills/badges
431- `rounded-[10px]` for window frame
432 
433### Animation
434 
435Use framer-motion for complex animations:
436```tsx
437import { motion, AnimatePresence } from 'framer-motion';
438 
439<AnimatePresence>
440 {isOpen && (
441 <motion.div
442 initial={{ height: 0, opacity: 0 }}
443 animate={{ height: 'auto', opacity: 1 }}
444 exit={{ height: 0, opacity: 0 }}
445 transition={{ duration: 0.15, ease: [0.25, 1, 0.5, 1] }}
446 >
447 {content}
448 </motion.div>
449 )}
450</AnimatePresence>
451```
452 
453---
454 
455## Data Fetching Rules
456 
457### Use Type-Safe Hooks
458 
459**Core queries** (no library required):
460```tsx
461import { useCoreQuery } from '../context';
462 
463const { data: libraries } = useCoreQuery({
464 type: 'libraries.list',
465 input: { include_stats: false },
466});
467```
468 
469**Library queries** (requires library context):
470```tsx
471import { useLibraryQuery } from '../context';
472 
473const { data: files } = useLibraryQuery({
474 type: 'files.directory_listing',
475 input: { path: '/' },
476});
477```
478 
479**Mutations:**
480```tsx
481import { useCoreMutation, useLibraryMutation } from '../context';
482 
483const createLib = useCoreMutation('libraries.create');
484const applyTags = useLibraryMutation('tags.apply');
485const copyFiles = useLibraryMutation('files.copy');
486const deleteFiles = useLibraryMutation('files.delete');
487 
488// Use mutations, not client.execute()
489createLib.mutate({ name: 'New Library', path: null });
490await copyFiles.mutateAsync({
491 sources: { paths: [path1, path2] },
492 destination: destPath,
493 overwrite: false,
494 verify_checksum: false,
495 preserve_timestamps: true,
496 move_files: false,
497 copy_method: "Auto"
498});
499```
500 
501### Never Fetch Manually
502 
503**Wrong:**
504```tsx
505const [data, setData] = useState();
506useEffect(() => {
507 fetchData().then(setData);
508}, []);
509```
510 
511**Correct:**
512```tsx
513const { data } = useCoreQuery({ type: 'operation', input: {} });
514```
515 
516---
517 
518## Performance Rules
519 
520### Virtual Scrolling
521 
522Use for lists > 100 items:
523```tsx
524import { useVirtualizer } from '@tanstack/react-virtual';
525 
526const virtualizer = useVirtualizer({
527 count: items.length,
528 getScrollElement: () => parentRef.current,
529 estimateSize: () => 50,
530});
531```
532 
533### Code Splitting
534 
535Lazy load routes:
536```tsx
537const SettingsPage = lazy(() => import('./Settings'));
538 
539<Suspense fallback={<Spinner />}>
540 <SettingsPage />
541</Suspense>
542```
543 
544### Memoization
545 
546Only when actually needed:
547```tsx
548// Expensive computation
549const sorted = useMemo(
550 () => items.sort(expensiveCompare),
551 [items]
552);
553 
554// Premature optimization
555const greeting = useMemo(() => `Hello ${name}`, [name]);
556```
557 
558---
559 
560## Component Composition Rules
561 
562### Dropdown Example (Current Implementation)
563 
564The `DropdownMenu` primitive provides minimal base functionality. Explorer customizes it:
565 
566```tsx
567// Primitive (in @sd/ui/DropdownMenu.tsx)
568export const DropdownMenu = {
569 Root: ({ trigger, children, className }) => (
570 // Minimal expanding container with motion
571 ),
572 Item: ({ children, onClick, className }) => (
573 // Basic button with flex layout
574 ),
575 Separator: ({ className }) => (
576 // Simple divider
577 ),
578};
579 
580// Usage (in Explorer.tsx)
581<DropdownMenu.Root
582 trigger={
583 <button className="w-full bg-sidebar-box border-sidebar-line rounded-lg">
584 {currentLibrary?.name}
585 </button>
586 }
587 className="bg-sidebar-box border-sidebar-line rounded-lg"
588>
589 <DropdownMenu.Item
590 className="px-2 py-1 rounded-md hover:bg-sidebar-selected"
591 onClick={() => switchLibrary(lib.id)}
592 >
593 {lib.name}
594 </DropdownMenu.Item>
595</DropdownMenu.Root>
596```
597 
598**Key principles:**
5991. Primitive has minimal/no styling
6002. All visual styling applied via className prop
6013. Business logic (filtering, selecting) in parent component
6024. Semantic color classes only
603 
604---
605 
606## Type Safety Rules
607 
608### Use Generated Types
609 
610All types are auto-generated from Rust:
611```tsx
612import type { LibraryInfo, CoreQuery, LibraryAction } from '@sd/ts-client';
613```
614 
615**Never:**
616- Define manual type interfaces that duplicate Rust types
617- Use `any` (use `unknown` with type guards if needed)
618- Ignore TypeScript errors
619 
620### Query Type Safety
621 
622The hooks automatically infer types:
623```tsx
624// TypeScript knows data is LibraryInfo[]
625const { data } = useCoreQuery({
626 type: 'libraries.list',
627 input: { include_stats: false },
628});
629 
630// data is automatically typed based on the operation!
631```
632 
633---
634 
635## File Organization Rules
636 
637### Component Co-location
638 
639```
640Explorer/
641├── index.tsx # Main component
642├── Sidebar.tsx # Sub-component
643├── TopBar.tsx # Sub-component
644└── hooks/
645 └── useExplorer.ts # Feature-specific hooks
646```
647 
648### Exports
649 
650Only export what's needed:
651```tsx
652// index.tsx
653export { Shell } from './Shell';
654export { DemoWindow } from './DemoWindow';
655// Don't export everything
656```
657 
658---
659 
660## macOS-Specific Rules
661 
662### Native Traffic Lights
663 
664The window uses **native** macOS traffic lights positioned by Swift code:
665- Traffic lights are real, functional native controls
666- Content must have `pt-[52px]` to avoid overlap
667- No fake CSS traffic lights
668- Transparent titlebar + invisible toolbar trick (see sd-desktop-macos crate)
669 
670### Window Styling
671 
672```tsx
673// Correct - accounts for native traffic lights
674<nav className="pt-[52px] ...">
675 {/* Content starts below traffic lights */}
676</nav>
677 
678// Correct - window frame with rounded corners
679<div className="rounded-[10px] border-transparent frame">
680 {/* App content */}
681</div>
682```
683 
684### Blur Effects
685 
686Use backdrop blur for macOS native feel:
687```tsx
688className="backdrop-blur-lg bg-sidebar/65"
689```
690 
691---
692 
693## Current Architectural Decisions
694 
695### 1. Expanding Dropdowns (Not Overlays)
696 
697Decision: Dropdowns should expand inline and push content down, not overlay it.
698 
699Implementation:
700- Use `framer-motion` for smooth height animation
701- No Radix Portal (renders inline in DOM)
702- Pushes surrounding content naturally
703 
704### 2. Library Switcher Logic
705 
706Decision: Show/hide current library based on count.
707 
708Rules:
709- **1 library:** Hide current from dropdown (no point showing it)
710- **2+ libraries:** Show all including current (with highlight)
711- Always show "New Library" and "Library Settings"
712 
713### 3. Color System
714 
715Decision: Use Tailwind semantic classes, never `var()` directly.
716 
717```tsx
718// Correct
719className="bg-sidebar-box text-sidebar-ink border-sidebar-line"
720 
721// Wrong
722className="bg-[var(--color-sidebar-box)]"
723```
724 
725### 4. Rounded Style (V2)
726 
727Decision: V2 is more rounded than V1.
728 
729- Containers: `rounded-lg` (8px)
730- Small elements: `rounded-md` (6px)
731- Window: `rounded-[10px]`
732- Pills/badges: `rounded-full`
733 
734---
735 
736## Development Workflow
737 
738### Before Writing Code
739 
7401. Check if primitive exists in @sd/ui
7412. Check if types are auto-generated (they probably are)
7423. Plan component composition (primitive + styling)
7434. Use semantic color classes
744 
745### When Adding Features
746 
7471. Create minimal primitive in @sd/ui if needed
7482. Use primitive in @sd/interface with styling
7493. Use type-safe queries/mutations
7504. Add to this document if architectural decision made
751 
752### When Styling
753 
7541. Use semantic colors (`bg-sidebar`, not `bg-gray-900`)
7552. Follow V2 rounded style
7563. Use opacity modifiers (`bg-accent/10`)
7574. Maintain color context (sidebar colors in sidebar, app colors in main area)
758 
759---
760 
761## Common Patterns
762 
763### Shell Entry Point Pattern
764 
765The app entry point follows a clean provider hierarchy:
766 
767```tsx
768// Shell.tsx
769export function Shell({ client }: { client: SpacedriveClient }) {
770 const platform = usePlatform();
771 
772 return (
773 <SpacedriveProvider client={client}>
774 <ServerProvider>
775 <TabManagerProvider routes={explorerRoutes}>
776 <TabKeyboardHandler />
777 <DndProvider>
778 <RouterProvider router={router} />
779 </DndProvider>
780 </TabManagerProvider>
781 </ServerProvider>
782 </SpacedriveProvider>
783 );
784}
785 
786// ShellLayout renders inside router, provides layout chrome
787// Routes (ExplorerView, Overview, etc.) render inside <Outlet />
788```
789 
790### TopBar Portal Pattern
791 
792Views register their TopBar buttons via portal:
793 
794```tsx
795// ExplorerView.tsx
796import { TopBarPortal } from '../../TopBar';
797 
798function ExplorerView() {
799 return (
800 <>
801 <TopBarPortal
802 left={<BackButton />}
803 center={<PathBar />}
804 right={<ViewControls />}
805 />
806 <div>{/* View content */}</div>
807 </>
808 );
809}
810```
811 
812### Library Switcher Pattern
813 
814```tsx
815const client = useSpacedriveClient();
816const { data: libraries } = useLibraries();
817const [currentLibraryId, setCurrentLibraryId] = useState<string | null>(null);
818 
819// Auto-select first library
820useEffect(() => {
821 if (libraries && libraries.length > 0 && !currentLibraryId) {
822 client.setCurrentLibrary(libraries[0].id);
823 setCurrentLibraryId(libraries[0].id);
824 }
825}, [libraries, currentLibraryId, client]);
826 
827// Switch library
828const handleSwitch = (id: string) => {
829 client.setCurrentLibrary(id);
830 setCurrentLibraryId(id);
831};
832```
833 
834### Sidebar Item Pattern
835 
836```tsx
837function SidebarItem({ icon: Icon, label, active }: Props) {
838 return (
839 <button
840 className={clsx(
841 "flex items-center gap-2 px-2 py-1 rounded-md text-sm font-medium",
842 active
843 ? "bg-sidebar-selected text-sidebar-ink"
844 : "text-sidebar-inkDull hover:text-sidebar-ink"
845 )}
846 >
847 <Icon className="size-4" weight={active ? "fill" : "bold"} />
848 <span className="truncate">{label}</span>
849 </button>
850 );
851}
852```
853 
854### Dropdown Pattern
855 
856```tsx
857<DropdownMenu.Root
858 trigger={<button className="...">Trigger</button>}
859 className="bg-sidebar-box border-sidebar-line rounded-lg"
860>
861 <DropdownMenu.Item
862 className="px-2 py-1 hover:bg-sidebar-selected"
863 onClick={() => action()}
864 >
865 Item content
866 </DropdownMenu.Item>
867 <DropdownMenu.Separator className="border-sidebar-line" />
868</DropdownMenu.Root>
869```
870 
871### Context Menu Pattern
872 
873Use `useContextMenu` hook for platform-agnostic context menus:
874 
875```tsx
876import { useContextMenu } from '../hooks/useContextMenu';
877import { Copy, Trash } from '@phosphor-icons/react';
878 
879const { selectedFiles } = useExplorer();
880const copyFiles = useLibraryMutation('files.copy');
881const deleteFiles = useLibraryMutation('files.delete');
882 
883const contextMenu = useContextMenu({
884 items: [
885 {
886 icon: Copy,
887 label: selectedFiles.length > 1 ? `Copy ${selectedFiles.length} items` : "Copy",
888 onClick: async () => {
889 await copyFiles.mutateAsync({
890 sources: { paths: selectedFiles.map(f => f.sd_path) },
891 destination: currentPath,
892 overwrite: false,
893 verify_checksum: false,
894 preserve_timestamps: true,
895 move_files: false,
896 copy_method: "Auto"
897 });
898 },
899 keybind: "⌘C",
900 condition: () => selectedFiles.length > 0, // Only show if files selected
901 },
902 { type: "separator" },
903 {
904 icon: Trash,
905 label: "Delete",
906 onClick: async () => {
907 await deleteFiles.mutateAsync({
908 targets: { paths: selectedFiles.map(f => f.sd_path) },
909 permanent: false,
910 recursive: true
911 });
912 },
913 keybind: "⌘⌫",
914 variant: "danger"
915 }
916 ]
917});
918 
919return <div onContextMenu={contextMenu.show}>Content</div>;
920```
921 
922**Key features:**
923- Platform-agnostic (native on Tauri, Radix on web)
924- Conditional items via `condition` callback
925- Smart labels that update based on state
926- Supports icons, keybinds, variants, submenus, separators
927- Use `useLibraryMutation` for actions, not `client.execute()`
928 
929---
930 
931## Type-Safe Query Pattern
932 
933### Query Keys
934 
935Use descriptive, hierarchical keys:
936```tsx
937// Good
938queryKey: ['libraries', 'list']
939queryKey: ['files', 'directory', libraryId, path]
940 
941// Bad
942queryKey: ['getLibraries']
943queryKey: ['data']
944```
945 
946### Using Queries
947 
948```tsx
949const { data, isLoading, error } = useCoreQuery({
950 type: 'libraries.list',
951 input: { include_stats: true },
952});
953 
954// data is automatically typed as LibraryInfo[]!
955```
956 
957---
958 
959## Testing Requirements
960 
961### Critical Paths Must Be Tested
962 
963- Explorer file operations
964- Library switching
965- Settings mutations
966- Search functionality
967 
968### Test Pattern
969 
970```tsx
971import { render, screen } from '@testing-library/react';
972import { Shell } from './Shell';
973 
974test('switches libraries', async () => {
975 const user = userEvent.setup();
976 render(<Shell client={mockClient} />);
977 
978 await user.click(screen.getByText('Switch Library'));
979 // ...
980});
981```
982 
983---
984 
985## Migration from V1
986 
987When porting V1 components:
988 
9891. **Update colors:** `bg-gray-900` → `bg-app`, `text-gray-400` → `text-ink-dull`
9902. **Use primitives:** Extract reusable parts to @sd/ui
9913. **Remove state:** Move to @sd/ts-client if global, use local state if component-specific
9924. **Update queries:** Use new type-safe hooks
9935. **Add rounding:** V1 used `rounded-md`, V2 uses `rounded-lg`
994 
995---
996 
997## Checklist Before PR
998 
999- [ ] All colors use semantic classes (no `var()` directly)
1000- [ ] Component uses primitives from @sd/ui where applicable
1001- [ ] Type-safe queries/mutations (no manual fetch)
1002- [ ] Follows V2 rounded style
1003- [ ] No `any` types
1004- [ ] Proper cleanup in useEffect
1005- [ ] Accessible (keyboard nav, ARIA labels)
1006- [ ] Tested critical paths
1007 
1008---
1009 
1010## Quick Reference
1011 
1012### Import Order
1013 
1014```tsx
1015// 1. External libraries
1016import { useState } from 'react';
1017import { motion } from 'framer-motion';
1018 
1019// 2. @sd packages
1020import { Button, DropdownMenu } from '@sd/ui';
1021import { useCoreQuery } from '@sd/ts-client';
1022 
1023// 3. Local imports
1024import { useLibraries } from './hooks/useLibraries';
1025import clsx from 'clsx';
1026```
1027 
1028### Common Mistakes
1029 
1030`<style>` or `<style jsx>` tags → Use Tailwind arbitrary variants
1031`className="bg-[var(--color-sidebar)]"` → `className="bg-sidebar"`
1032`bg-gray-900` → `bg-app`
1033`rounded-md` everywhere → `rounded-lg` for V2
1034Manual fetch → Use type-safe hooks
1035State in component → Use @sd/ts-client or local state
1036 
1037---
1038 
1039## Questions to Ask
1040 
1041Before writing code:
1042 
10431. **Is this a primitive?** → Should it be in @sd/ui?
10442. **Is this state global?** → Should it be in @sd/ts-client?
10453. **Are the types auto-generated?** → Don't duplicate them!
10464. **Can I use a semantic color?** → Yes, always!
10475. **Is this accessible?** → Keyboard nav? ARIA labels?
1048 
1049---
1050 
1051## Resources
1052 
1053- **Type Generation:** `cargo run --bin generate_typescript_types`
1054- **Color System:** `/docs/react/ui/colors.mdx`
1055- **Workbench Docs:** `/workbench/interface/`
1056- **V1 Reference:** `/Users/jamespine/Projects/spacedrive_v1`
1057 
1058---
1059 
1060## Status: Current Implementation
1061 
1062**Complete:**
1063- Type-safe client with auto-generated types
1064- Native macOS traffic lights
1065- V1 color system as CSS variables
1066- Expanding dropdown (DropdownMenu primitive)
1067- Explorer with sidebar and library switcher
1068- TanStack Query integration
1069- Clean architecture refactor (Shell → ShellLayout → Views)
1070- Extracted DndProvider for drag-and-drop coordination
1071- QuickPreview components (Controller + Syncer)
1072- TopBar portal system for view-specific controls
1073 
1074**In Progress:**
1075- Port remaining V1 components
1076- Build complete Explorer (file grid/list views)
1077- Settings pages
1078- Multi-window system
1079 
1080---
1081 
1082**Remember:** This is a living document. Update it when architectural decisions are made. This is our rulebook for building a world-class file manager interface!
spacedriveapp/spacedrive · core/AGENTS.md
@@ +1 @@
1# AGENTS.md - Spacedrive Core v2
2 
3## Build/Test Commands
 
 
4 
5- `cargo build` - Build the project
6- `cargo test` - Run all tests
7- `cargo test <test_name>` - Run specific test (e.g., `cargo test library_test`)
8- `cargo clippy` - Lint code
9- `cargo fmt` - Format code
10- `cargo run --bin sd-cli -- <command>` - Run CLI (note: binary is `sd-cli`, not `spacedrive`)
11 
12## Code Style
13 
14- **Imports**: Group std, external crates, then local modules with blank lines between
15- **Formatting**: Use `cargo fmt` - tabs for indentation, snake_case for variables/functions. DO NOT use emojis at all.
16- **Types**: Explicit types preferred, use `Result<T, E>` for error handling with `thiserror`
17- **Naming**: snake_case for functions/variables, PascalCase for types, SCREAMING_SNAKE_CASE for constants
18- **Error Handling**: Use `Result` types, `thiserror` for custom errors, `anyhow` for application errors
19- **Async**: Use `async/await`, prefer `tokio` primitives, avoid blocking operations
20- **Resumable Jobs**: For long-running jobs that need to be resumable, store the job's state within the job's struct itself. Use `#[serde(skip)]` for fields that should not be persisted. For example, in a file copy job, the list of already copied files can be stored to allow the job to resume from where it left off.
21- **Documentation**: Use `//!` for module docs, `///` for public items, include examples
22- **Architecture**: Follow a Command Query Responsibility Segregation (CQRS) and Domain-Driven Design (DDD) pattern.
23 - **Domain**: Core data structures and business logic are located in `src/domain/`. These are the "nouns" of your system.
24 - **Operations**: State-changing commands (actions) and data-retrieving queries are located in `src/ops/`. These are the "verbs" of your system.
25 - **Actions**: Operations that modify the state of the application. They should be self-contained and transactional.
26 - **Queries**: Operations that retrieve data without modifying state. They should be efficient and optimized for reading.
27- **Feature Modules**: Each new feature should be implemented in its own module within the `src/ops/` directory. For example, a new "share" feature would live in `src/ops/files/share`. Each feature module should contain the following files where applicable:
28 - `action.rs`: The main logic for the state-changing operation.
29 - `input.rs`: Data structures for the action's input.
30 - `output.rs`: Data structures for the action's output.
31 - `job.rs`: If the action is long-running, the job implementation.
32- **Database**: Use SeaORM entities, async queries, proper error propagation
33- **Comments**: Minimal inline comments, focus on why not what, no TODO comments in production code
34 
35## Daemon Architecture
36 
37Spacedrive uses a **daemon-client architecture** where a single daemon process manages the core functionality and multiple client applications (CLI, GraphQL server, desktop app) connect to it via Unix domain sockets.
38 
39> **For detailed daemon architecture documentation, see [/docs/core/daemon.md](/docs/core/daemon.md)**
40 
41### **The `Wire` Trait**
 
 
 
 
 
 
 
42 
43All actions and queries must implement the `Wire` trait to enable type-safe client-daemon communication:
 
 
 
 
44 
45```rust
46pub trait Wire {
47 const METHOD: &'static str;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48}
49```
50 
51### **Registration Macros**
 
 
 
 
 
 
 
 
 
 
52 
53Instead of manually implementing `Wire`, use these registration macros that automatically:
 
 
 
 
 
 
 
 
 
54 
551. Implement the `Wire` trait with the correct method string
562. Register the operation in the global registry using the `inventory` crate
 
 
 
 
 
 
 
57 
58**For Queries:**
 
 
 
 
 
 
 
 
59 
60```rust
61crate::register_query!(NetworkStatusQuery, "network.status");
62// Generates method: "query:network.status"
 
 
 
 
 
 
63```
64 
65**For Library Actions:**
 
 
 
 
66 
67```rust
68crate::register_library_action!(FileCopyAction, "files.copy");
69// Generates method: "action:files.copy.input"
 
70```
71 
72**For Core Actions:**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73 
74```rust
75crate::register_core_action!(LibraryCreateAction, "libraries.create");
76// Generates method: "action:libraries.create.input"
 
 
 
 
 
 
 
 
77```
78 
79### **Registry System**
 
 
 
 
 
 
80 
81- **Location**: `core/src/ops/registry.rs`
82- **Mechanism**: Uses the `inventory` crate for compile-time registration
83- **Global Maps**: `QUERIES` and `ACTIONS` hashmaps populated at startup
84- **Handler Functions**: Generic handlers that decode payloads, execute operations, and encode results
 
85 
86## Logging Standards
 
 
 
 
 
 
87 
88- **Setup**: Use `tracing_subscriber::fmt()` with env filter for structured logging
89- **Macros**: Use `info!`, `warn!`, `error!`, `debug!` from `tracing` crate, not `println!`
90- **Job Context**: Use `ctx.log()` in jobs for job-specific logging with automatic job_id tagging
91- **Structured**: Include relevant context fields: `debug!(job_id = %self.id, "message")`
92- **Levels**: debug for detailed flow, info for user-relevant events, warn for recoverable issues, error for failures
93- **Format**: `tracing_subscriber::fmt().with_env_filter(env_filter).init()` in main/examples
94- **Environment**: Respect `RUST_LOG` env var, fallback to module-specific filters like `sd_core=info`
95 
96## Documentation
 
 
97 
98- **Core level docs**: Live in `/docs/core` - comprehensive architecture and implementation guides
99- **Core design docs**: Live in `/docs/core/design` - planning documents, RFCs, and design decisions
100- **Application level docs**: Live in `/docs`
101- **Code docs**: Use `///` for public APIs, `//!` for module overviews, include examples
102 
103## Debug Instructions
104 
105- You can view the logs of a job in the job_logs directory in the root of the data folder
106- When testing the CLI, after compiling you must first use the `restart` command to ensure the Spacedrive daemon is using the latest build.
107 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
@@ −1 +1 @@
1−# Spacedrive Interface Development Rules
1+# AGENTS.md - Spacedrive Core v2
22  
3−**Status:** Living Document - Update as architectural decisions are made
4−**Purpose:** Ensure consistent, clean, and maintainable code across the interface package
5−**Audience:** AI assistants and developers working on @sd/interface
3+## Build/Test Commands
64  
7−---
5+- `cargo build` - Build the project
6+- `cargo test` - Run all tests
7+- `cargo test <test_name>` - Run specific test (e.g., `cargo test library_test`)
8+- `cargo clippy` - Lint code
9+- `cargo fmt` - Format code
10+- `cargo run --bin sd-cli -- <command>` - Run CLI (note: binary is `sd-cli`, not `spacedrive`)
811  
9−## Core Principles
12+## Code Style
1013  
11−1. **Platform Agnostic** - This package works on Tauri, Web, and React Native
12−2. **Clean Separation** - UI components here, state in @sd/ts-client, primitives in @sd/ui
13−3. **Type Safety First** - Use auto-generated types, no `any`, strict TypeScript
14−4. **Performance Matters** - Virtual scrolling, code splitting, memoization when needed
15−5. **Accessible** - Radix primitives, proper ARIA labels, keyboard navigation
16−6. **Consistent Styling** - Semantic color system, no arbitrary values
14+- **Imports**: Group std, external crates, then local modules with blank lines between
15+- **Formatting**: Use `cargo fmt` - tabs for indentation, snake_case for variables/functions. DO NOT use emojis at all.
16+- **Types**: Explicit types preferred, use `Result<T, E>` for error handling with `thiserror`
17+- **Naming**: snake_case for functions/variables, PascalCase for types, SCREAMING_SNAKE_CASE for constants
18+- **Error Handling**: Use `Result` types, `thiserror` for custom errors, `anyhow` for application errors
19+- **Async**: Use `async/await`, prefer `tokio` primitives, avoid blocking operations
20+- **Resumable Jobs**: For long-running jobs that need to be resumable, store the job's state within the job's struct itself. Use `#[serde(skip)]` for fields that should not be persisted. For example, in a file copy job, the list of already copied files can be stored to allow the job to resume from where it left off.
21+- **Documentation**: Use `//!` for module docs, `///` for public items, include examples
22+- **Architecture**: Follow a Command Query Responsibility Segregation (CQRS) and Domain-Driven Design (DDD) pattern.
23+ - **Domain**: Core data structures and business logic are located in `src/domain/`. These are the "nouns" of your system.
24+ - **Operations**: State-changing commands (actions) and data-retrieving queries are located in `src/ops/`. These are the "verbs" of your system.
25+ - **Actions**: Operations that modify the state of the application. They should be self-contained and transactional.
26+ - **Queries**: Operations that retrieve data without modifying state. They should be efficient and optimized for reading.
27+- **Feature Modules**: Each new feature should be implemented in its own module within the `src/ops/` directory. For example, a new "share" feature would live in `src/ops/files/share`. Each feature module should contain the following files where applicable:
28+ - `action.rs`: The main logic for the state-changing operation.
29+ - `input.rs`: Data structures for the action's input.
30+ - `output.rs`: Data structures for the action's output.
31+ - `job.rs`: If the action is long-running, the job implementation.
32+- **Database**: Use SeaORM entities, async queries, proper error propagation
33+- **Comments**: Minimal inline comments, focus on why not what, no TODO comments in production code
1734  
18−---
35+## Daemon Architecture
1936  
20−## Package Architecture
37+Spacedrive uses a **daemon-client architecture** where a single daemon process manages the core functionality and multiple client applications (CLI, GraphQL server, desktop app) connect to it via Unix domain sockets.
2138  
22−### What Lives Where
39+> **For detailed daemon architecture documentation, see [/docs/core/daemon.md](/docs/core/daemon.md)**
2340  
24−**@sd/interface** (this package):
25−- Route components and layouts
26−- Feature components (Explorer, Settings, etc.)
27−- React Query hook wrappers
28−- UI composition and interactivity
29−- NO state management (use @sd/ts-client)
30−- NO primitive components (use @sd/ui)
31−- NO platform APIs (use platform prop)
41+### **The `Wire` Trait**
3242  
33−**@sd/ts-client**:
34−- Client implementation
35−- Transport layer
36−- Auto-generated types from Rust
37−- State stores (if needed)
43+All actions and queries must implement the `Wire` trait to enable type-safe client-daemon communication:
3844  
39−**@sd/ui**:
40−- Primitive components (Button, Input, DropdownMenu, etc.)
41−- Reusable, unstyled or minimally styled
42−- No business logic
43−- No API calls
44− 
45−### Current Structure
46− 
47−```
48−packages/interface/
49−├── src/
50−│ ├── Shell.tsx # App entry point (providers, daemon check)
51−│ ├── ShellLayout.tsx # Layout shell (sidebar, inspector, TopBar)
52−│ ├── router.tsx # Route configuration
53−│ ├── DemoWindow.tsx # Demo/testing window
54−│ ├── FloatingControls.tsx # Floating controls UI
55−│ ├── components/
56−│ │ ├── DndProvider.tsx # Drag-and-drop coordinator
57−│ │ ├── Explorer/
58−│ │ │ ├── ExplorerView.tsx # File browser view
59−│ │ │ ├── context.tsx # Explorer state/context
60−│ │ │ └── ...
61−│ │ ├── QuickPreview/
62−│ │ │ ├── Controller.tsx # Preview navigation
63−│ │ │ ├── Syncer.tsx # Selection sync
64−│ │ │ └── ...
65−│ │ └── ...
66−│ ├── TopBar/ # TopBar portal system
67−│ │ ├── TopBar.tsx
68−│ │ ├── Context.tsx
69−│ │ └── Portal.tsx
70−│ ├── routes/ # Route components
71−│ │ └── overview/
72−│ ├── hooks/ # React hooks
73−│ ├── context.tsx # Client context and hooks
74−│ ├── styles.css # Global CSS variables
75−│ └── index.tsx # Public exports
76−```
77− 
78−### Architecture Layers
79− 
80−The interface is organized into clear separation of concerns:
81− 
82−**Shell Layer** (`Shell.tsx`):
83−- Root entry point
84−- Provider setup (Spacedrive, Server, TabManager, Platform)
85−- Daemon connection management (Tauri-specific)
86− 
87−**Layout Layer** (`ShellLayout.tsx`):
88−- Chrome/frame (sidebar, inspector, TopBar containers)
89−- Provider setup (TopBar, Selection, Explorer)
90−- Tab bar positioning
91−- QuickPreview coordination
92− 
93−**View Layer** (routes like `ExplorerView.tsx`):
94−- Actual content rendering
95−- TopBar button registration (via portal)
96−- Feature-specific logic
97− 
98−**Coordination Layer**:
99−- `DndProvider.tsx` - Global drag-and-drop
100−- `QuickPreview/Controller.tsx` - Preview navigation
101−- `QuickPreview/Syncer.tsx` - Selection-to-preview sync
102− 
103−**Visual Hierarchy:**
104−```
105−Shell (providers) → DndProvider → Router
106− ↓
107− ShellLayout (chrome)
108− ↓
109− <Outlet> (routes)
110− ↓
111− Overview | ExplorerView | Settings | etc.
112−```
113− 
114−---
115− 
116−## Code Style Rules
117− 
118−### React 19 Standards
119− 
120−### Critical: You Might Not Need an Effect
121− 
122−**Effects are an escape hatch** - only use them to sync with external systems (network, DOM, browser APIs).
123− 
124−**DON'T use Effects for:**
125−- Transforming data for rendering (calculate during render instead)
126−- Handling user events (use event handlers)
127−- Updating state based on props (calculate during render or use `key`)
128−- Chains of state updates (do in event handler)
129−- Initializing app (use module-level code)
130−- Notifying parent of changes (pass callback, call in event handler)
131− 
132−**DO use Effects for:**
133−- Subscribing to external systems (WebSocket, browser events)
134−- Syncing with non-React widgets
135−- Network requests with proper cleanup
136− 
137−### Examples
138− 
139−**Wrong - Don't use Effect to transform data:**
140−```tsx
141−function TodoList({ todos, filter }) {
142− const [visibleTodos, setVisibleTodos] = useState([]);
143− useEffect(() => {
144− setVisibleTodos(getFilteredTodos(todos, filter));
145− }, [todos, filter]);
146− // Extra render pass!
45+```rust
46+pub trait Wire {
47+ const METHOD: &'static str;
14748 }
14849 ```
14950  
150−**Correct - Calculate during render:**
151−```tsx
152−function TodoList({ todos, filter }) {
153− const visibleTodos = getFilteredTodos(todos, filter);
154− // Or use useMemo if expensive:
155− const visibleTodos = useMemo(
156− () => getFilteredTodos(todos, filter),
157− [todos, filter]
158− );
159−}
160−```
51+### **Registration Macros**
16152  
162−**Wrong - Don't use Effect for user events:**
163−```tsx
164−function ProductPage({ product, addToCart }) {
165− useEffect(() => {
166− if (product.isInCart) {
167− showNotification('Added to cart!');
168− }
169− }, [product]);
170−}
171−```
53+Instead of manually implementing `Wire`, use these registration macros that automatically:
17254  
173−**Correct - Use event handler:**
174−```tsx
175−function ProductPage({ product, addToCart }) {
176− function buyProduct() {
177− addToCart(product);
178− showNotification('Added to cart!');
179− }
180−}
181−```
55+1. Implement the `Wire` trait with the correct method string
56+2. Register the operation in the global registry using the `inventory` crate
18257  
183−**Wrong - Don't use Effect to update parent:**
184−```tsx
185−function Toggle({ onChange }) {
186− const [isOn, setIsOn] = useState(false);
187− useEffect(() => {
188− onChange(isOn); // Too late! Extra render.
189− }, [isOn, onChange]);
190−}
191−```
58+**For Queries:**
19259  
193−**Correct - Call in event handler:**
194−```tsx
195−function Toggle({ onChange }) {
196− const [isOn, setIsOn] = useState(false);
197− function updateToggle(nextIsOn) {
198− setIsOn(nextIsOn);
199− onChange(nextIsOn); // Same render pass!
200− }
201−}
60+```rust
61+crate::register_query!(NetworkStatusQuery, "network.status");
62+// Generates method: "query:network.status"
20263 ```
20364  
204−**Wrong - Don't chain Effects:**
205−```tsx
206−useEffect(() => {
207− if (card.gold) setGoldCardCount(c => c + 1);
208−}, [card]);
65+**For Library Actions:**
20966  
210−useEffect(() => {
211− if (goldCardCount > 3) setRound(r => r + 1);
212−}, [goldCardCount]);
213−// Multiple render passes!
67+```rust
68+crate::register_library_action!(FileCopyAction, "files.copy");
69+// Generates method: "action:files.copy.input"
21470 ```
21571  
216−**Correct - Calculate in event handler:**
217−```tsx
218−function handlePlaceCard(nextCard) {
219− setCard(nextCard);
220− if (nextCard.gold) {
221− if (goldCardCount < 3) {
222− setGoldCardCount(goldCardCount + 1);
223− } else {
224− setGoldCardCount(0);
225− setRound(round + 1);
226− }
227− }
228− // Single render pass!
229−}
230−```
72+**For Core Actions:**
23173  
232−### Function components only:
233−```tsx
234−// Correct
235−function Component({ name }: { name: string }) {
236− return <div>{name}</div>;
237−}
238− 
239−// Wrong
240−const Component: React.FC<{ name: string }> = ({ name }) => {
241− return <div>{name}</div>;
242−};
74+```rust
75+crate::register_core_action!(LibraryCreateAction, "libraries.create");
76+// Generates method: "action:libraries.create.input"
24377 ```
24478  
245−**Hooks must follow rules:**
246−```tsx
247−// Correct - proper cleanup
248−useEffect(() => {
249− const subscription = subscribe();
250− return () => subscription.unsubscribe();
251−}, [dependency]);
79+### **Registry System**
25280  
253−// Wrong - missing cleanup
254−useEffect(() => {
255− subscribe();
256−}, []);
257−```
81+- **Location**: `core/src/ops/registry.rs`
82+- **Mechanism**: Uses the `inventory` crate for compile-time registration
83+- **Global Maps**: `QUERIES` and `ACTIONS` hashmaps populated at startup
84+- **Handler Functions**: Generic handlers that decode payloads, execute operations, and encode results
25885  
259−**Use TypeScript strictly:**
260−```tsx
261−// Correct - explicit types
262−interface ButtonProps {
263− label: string;
264− onClick: () => void;
265−}
86+## Logging Standards
26687  
267−function Button({ label, onClick }: ButtonProps) { }
88+- **Setup**: Use `tracing_subscriber::fmt()` with env filter for structured logging
89+- **Macros**: Use `info!`, `warn!`, `error!`, `debug!` from `tracing` crate, not `println!`
90+- **Job Context**: Use `ctx.log()` in jobs for job-specific logging with automatic job_id tagging
91+- **Structured**: Include relevant context fields: `debug!(job_id = %self.id, "message")`
92+- **Levels**: debug for detailed flow, info for user-relevant events, warn for recoverable issues, error for failures
93+- **Format**: `tracing_subscriber::fmt().with_env_filter(env_filter).init()` in main/examples
94+- **Environment**: Respect `RUST_LOG` env var, fallback to module-specific filters like `sd_core=info`
26895  
269−// Wrong - implicit any
270−function Button(props) { }
271−```
96+## Documentation
27297  
273−---
98+- **Core level docs**: Live in `/docs/core` - comprehensive architecture and implementation guides
99+- **Core design docs**: Live in `/docs/core/design` - planning documents, RFCs, and design decisions
100+- **Application level docs**: Live in `/docs`
101+- **Code docs**: Use `///` for public APIs, `//!` for module overviews, include examples
274102  
275−## Color System Rules
103+## Debug Instructions
276104  
277−### CRITICAL: Always Use Semantic Tailwind Classes
105+- You can view the logs of a job in the job_logs directory in the root of the data folder
106+- When testing the CLI, after compiling you must first use the `restart` command to ensure the Spacedrive daemon is using the latest build.
278107  
279−Never use `var()` syntax directly. Always use Tailwind's semantic color classes.
280− 
281−**WRONG:**
282−```tsx
283−className="bg-[var(--color-sidebar)]"
284−className="text-[var(--color-sidebar-ink)]"
285−className="border-[var(--color-accent)]"
286−```
287− 
288−**CORRECT:**
289−```tsx
290−className="bg-sidebar"
291−className="text-sidebar-ink"
292−className="border-accent"
293−```
294− 
295−**IMPORTANT:** CSS variables must be defined as comma-separated HSL values (not wrapped in `hsl()`):
296−```css
297−/* CORRECT - bare values for Tailwind */
298−--color-sidebar: 235, 15%, 7%;
299− 
300−/* WRONG - wrapped in hsl() */
301−--color-sidebar: hsl(235, 15%, 7%);
302−```
303− 
304−This is because Tailwind uses `hsla(var(--color-sidebar), <alpha-value>)` which becomes `hsla(235, 15%, 7%, 0.5)` for opacity support.
305− 
306−### Color Categories
307− 
308−**Accent:** `accent`, `accent-faint`, `accent-deep`
309−- Use for: Primary actions, selections, focus states
310− 
311−**Text (Ink):** `ink`, `ink-dull`, `ink-faint`
312−- Use for: Text hierarchy (primary, secondary, tertiary)
313− 
314−**Sidebar:** `sidebar`, `sidebar-box`, `sidebar-line`, `sidebar-ink`, `sidebar-selected`, etc.
315−- Use for: Sidebar-specific elements
316− 
317−**App:** `app`, `app-box`, `app-line`, `app-hover`, `app-selected`, etc.
318−- Use for: Main content area elements
319− 
320−**Menu:** `menu`, `menu-line`, `menu-hover`, `menu-ink`, etc.
321−- Use for: Dropdowns, context menus
322− 
323−### Opacity Modifiers
324− 
325−```tsx
326−// Use Tailwind opacity
327−className="bg-accent/10"
328−className="bg-sidebar/65"
329− 
330−// Don't use manual alpha
331−className="bg-[var(--color-accent)]/10"
332−```
333− 
334−---
335− 
336−## Component Rules
337− 
338−### Primitive vs Feature Components
339− 
340−**Primitives** (@sd/ui):
341−- Generic, reusable
342−- Minimal styling (or unstyled)
343−- No business logic
344−- Example: `DropdownMenu`, `Button`, `Input`
345− 
346−**Feature Components** (@sd/interface):
347−- Specific to Spacedrive features
348−- Uses primitives
349−- Can have business logic
350−- Example: `Explorer`, `Sidebar`, `LibrariesDropdown`
351− 
352−### Component Structure
353− 
354−```tsx
355−// Correct structure
356−import { Primitive } from '@sd/ui';
357−import { useSomeQuery } from '../context';
358− 
359−interface ComponentProps {
360− // Props interface
361−}
362− 
363−function Component({ prop }: ComponentProps) {
364− // Hooks first
365− const data = useSomeQuery();
366− 
367− // Logic
368− const derived = useMemo(() => transform(data), [data]);
369− 
370− // Render
371− return (
372− <Primitive className="semantic-colors">
373− {/* Content */}
374− </Primitive>
375− );
376−}
377− 
378−export { Component };
379−```
380− 
381−### Naming Conventions
382− 
383−- **Files:** `PascalCase.tsx` for components, `camelCase.ts` for utilities
384−- **Components:** `PascalCase` functions
385−- **Hooks:** `useCamelCase` pattern
386−- **Constants:** `SCREAMING_SNAKE_CASE`
387−- **CSS classes:** Semantic names only (`bg-sidebar`, not `bg-gray-900`)
388− 
389−---
390− 
391−## Styling Rules
392− 
393−### CRITICAL: Never Use Style Tags
394− 
395−**NEVER** use `<style>`, `<style jsx>`, or any inline style tags. Always use Tailwind utility classes.
396− 
397−**WRONG:**
398−```tsx
399−<style jsx>{`
400− .slider::-webkit-slider-thumb {
401− background: var(--color-accent);
402− }
403−`}</style>
404−```
405− 
406−**CORRECT:**
407−```tsx
408−className="[&::-webkit-slider-thumb]:bg-accent [&::-webkit-slider-thumb]:rounded-full"
409−```
410− 
411−Use Tailwind's arbitrary variant syntax for pseudo-elements and other edge cases.
412− 
413−### Tailwind Class Order
414− 
415−Follow this order for readability:
416−1. Layout (`flex`, `grid`, `w-full`, `h-screen`)
417−2. Spacing (`p-4`, `m-2`, `gap-2`)
418−3. Typography (`text-sm`, `font-medium`)
419−4. Colors (`bg-sidebar`, `text-ink`)
420−5. Borders (`border`, `border-sidebar-line`, `rounded-lg`)
421−6. Effects (`shadow-sm`, `backdrop-blur`)
422−7. States (`hover:bg-app-hover`, `focus:ring-accent`)
423−8. Transitions (`transition-colors`)
424− 
425−### Rounding (V2 Style)
426− 
427−V2 is more rounded than V1. Use:
428−- `rounded-lg` for most containers (8px)
429−- `rounded-md` for smaller elements (6px)
430−- `rounded-full` for pills/badges
431−- `rounded-[10px]` for window frame
432− 
433−### Animation
434− 
435−Use framer-motion for complex animations:
436−```tsx
437−import { motion, AnimatePresence } from 'framer-motion';
438− 
439−<AnimatePresence>
440− {isOpen && (
441− <motion.div
442− initial={{ height: 0, opacity: 0 }}
443− animate={{ height: 'auto', opacity: 1 }}
444− exit={{ height: 0, opacity: 0 }}
445− transition={{ duration: 0.15, ease: [0.25, 1, 0.5, 1] }}
446− >
447− {content}
448− </motion.div>
449− )}
450−</AnimatePresence>
451−```
452− 
453−---
454− 
455−## Data Fetching Rules
456− 
457−### Use Type-Safe Hooks
458− 
459−**Core queries** (no library required):
460−```tsx
461−import { useCoreQuery } from '../context';
462− 
463−const { data: libraries } = useCoreQuery({
464− type: 'libraries.list',
465− input: { include_stats: false },
466−});
467−```
468− 
469−**Library queries** (requires library context):
470−```tsx
471−import { useLibraryQuery } from '../context';
472− 
473−const { data: files } = useLibraryQuery({
474− type: 'files.directory_listing',
475− input: { path: '/' },
476−});
477−```
478− 
479−**Mutations:**
480−```tsx
481−import { useCoreMutation, useLibraryMutation } from '../context';
482− 
483−const createLib = useCoreMutation('libraries.create');
484−const applyTags = useLibraryMutation('tags.apply');
485−const copyFiles = useLibraryMutation('files.copy');
486−const deleteFiles = useLibraryMutation('files.delete');
487− 
488−// Use mutations, not client.execute()
489−createLib.mutate({ name: 'New Library', path: null });
490−await copyFiles.mutateAsync({
491− sources: { paths: [path1, path2] },
492− destination: destPath,
493− overwrite: false,
494− verify_checksum: false,
495− preserve_timestamps: true,
496− move_files: false,
497− copy_method: "Auto"
498−});
499−```
500− 
501−### Never Fetch Manually
502− 
503−**Wrong:**
504−```tsx
505−const [data, setData] = useState();
506−useEffect(() => {
507− fetchData().then(setData);
508−}, []);
509−```
510− 
511−**Correct:**
512−```tsx
513−const { data } = useCoreQuery({ type: 'operation', input: {} });
514−```
515− 
516−---
517− 
518−## Performance Rules
519− 
520−### Virtual Scrolling
521− 
522−Use for lists > 100 items:
523−```tsx
524−import { useVirtualizer } from '@tanstack/react-virtual';
525− 
526−const virtualizer = useVirtualizer({
527− count: items.length,
528− getScrollElement: () => parentRef.current,
529− estimateSize: () => 50,
530−});
531−```
532− 
533−### Code Splitting
534− 
535−Lazy load routes:
536−```tsx
537−const SettingsPage = lazy(() => import('./Settings'));
538− 
539−<Suspense fallback={<Spinner />}>
540− <SettingsPage />
541−</Suspense>
542−```
543− 
544−### Memoization
545− 
546−Only when actually needed:
547−```tsx
548−// Expensive computation
549−const sorted = useMemo(
550− () => items.sort(expensiveCompare),
551− [items]
552−);
553− 
554−// Premature optimization
555−const greeting = useMemo(() => `Hello ${name}`, [name]);
556−```
557− 
558−---
559− 
560−## Component Composition Rules
561− 
562−### Dropdown Example (Current Implementation)
563− 
564−The `DropdownMenu` primitive provides minimal base functionality. Explorer customizes it:
565− 
566−```tsx
567−// Primitive (in @sd/ui/DropdownMenu.tsx)
568−export const DropdownMenu = {
569− Root: ({ trigger, children, className }) => (
570− // Minimal expanding container with motion
571− ),
572− Item: ({ children, onClick, className }) => (
573− // Basic button with flex layout
574− ),
575− Separator: ({ className }) => (
576− // Simple divider
577− ),
578−};
579− 
580−// Usage (in Explorer.tsx)
581−<DropdownMenu.Root
582− trigger={
583− <button className="w-full bg-sidebar-box border-sidebar-line rounded-lg">
584− {currentLibrary?.name}
585− </button>
586− }
587− className="bg-sidebar-box border-sidebar-line rounded-lg"
588−>
589− <DropdownMenu.Item
590− className="px-2 py-1 rounded-md hover:bg-sidebar-selected"
591− onClick={() => switchLibrary(lib.id)}
592− >
593− {lib.name}
594− </DropdownMenu.Item>
595−</DropdownMenu.Root>
596−```
597− 
598−**Key principles:**
599−1. Primitive has minimal/no styling
600−2. All visual styling applied via className prop
601−3. Business logic (filtering, selecting) in parent component
602−4. Semantic color classes only
603− 
604−---
605− 
606−## Type Safety Rules
607− 
608−### Use Generated Types
609− 
610−All types are auto-generated from Rust:
611−```tsx
612−import type { LibraryInfo, CoreQuery, LibraryAction } from '@sd/ts-client';
613−```
614− 
615−**Never:**
616−- Define manual type interfaces that duplicate Rust types
617−- Use `any` (use `unknown` with type guards if needed)
618−- Ignore TypeScript errors
619− 
620−### Query Type Safety
621− 
622−The hooks automatically infer types:
623−```tsx
624−// TypeScript knows data is LibraryInfo[]
625−const { data } = useCoreQuery({
626− type: 'libraries.list',
627− input: { include_stats: false },
628−});
629− 
630−// data is automatically typed based on the operation!
631−```
632− 
633−---
634− 
635−## File Organization Rules
636− 
637−### Component Co-location
638− 
639−```
640−Explorer/
641−├── index.tsx # Main component
642−├── Sidebar.tsx # Sub-component
643−├── TopBar.tsx # Sub-component
644−└── hooks/
645− └── useExplorer.ts # Feature-specific hooks
646−```
647− 
648−### Exports
649− 
650−Only export what's needed:
651−```tsx
652−// index.tsx
653−export { Shell } from './Shell';
654−export { DemoWindow } from './DemoWindow';
655−// Don't export everything
656−```
657− 
658−---
659− 
660−## macOS-Specific Rules
661− 
662−### Native Traffic Lights
663− 
664−The window uses **native** macOS traffic lights positioned by Swift code:
665−- Traffic lights are real, functional native controls
666−- Content must have `pt-[52px]` to avoid overlap
667−- No fake CSS traffic lights
668−- Transparent titlebar + invisible toolbar trick (see sd-desktop-macos crate)
669− 
670−### Window Styling
671− 
672−```tsx
673−// Correct - accounts for native traffic lights
674−<nav className="pt-[52px] ...">
675− {/* Content starts below traffic lights */}
676−</nav>
677− 
678−// Correct - window frame with rounded corners
679−<div className="rounded-[10px] border-transparent frame">
680− {/* App content */}
681−</div>
682−```
683− 
684−### Blur Effects
685− 
686−Use backdrop blur for macOS native feel:
687−```tsx
688−className="backdrop-blur-lg bg-sidebar/65"
689−```
690− 
691−---
692− 
693−## Current Architectural Decisions
694− 
695−### 1. Expanding Dropdowns (Not Overlays)
696− 
697−Decision: Dropdowns should expand inline and push content down, not overlay it.
698− 
699−Implementation:
700−- Use `framer-motion` for smooth height animation
701−- No Radix Portal (renders inline in DOM)
702−- Pushes surrounding content naturally
703− 
704−### 2. Library Switcher Logic
705− 
706−Decision: Show/hide current library based on count.
707− 
708−Rules:
709−- **1 library:** Hide current from dropdown (no point showing it)
710−- **2+ libraries:** Show all including current (with highlight)
711−- Always show "New Library" and "Library Settings"
712− 
713−### 3. Color System
714− 
715−Decision: Use Tailwind semantic classes, never `var()` directly.
716− 
717−```tsx
718−// Correct
719−className="bg-sidebar-box text-sidebar-ink border-sidebar-line"
720− 
721−// Wrong
722−className="bg-[var(--color-sidebar-box)]"
723−```
724− 
725−### 4. Rounded Style (V2)
726− 
727−Decision: V2 is more rounded than V1.
728− 
729−- Containers: `rounded-lg` (8px)
730−- Small elements: `rounded-md` (6px)
731−- Window: `rounded-[10px]`
732−- Pills/badges: `rounded-full`
733− 
734−---
735− 
736−## Development Workflow
737− 
738−### Before Writing Code
739− 
740−1. Check if primitive exists in @sd/ui
741−2. Check if types are auto-generated (they probably are)
742−3. Plan component composition (primitive + styling)
743−4. Use semantic color classes
744− 
745−### When Adding Features
746− 
747−1. Create minimal primitive in @sd/ui if needed
748−2. Use primitive in @sd/interface with styling
749−3. Use type-safe queries/mutations
750−4. Add to this document if architectural decision made
751− 
752−### When Styling
753− 
754−1. Use semantic colors (`bg-sidebar`, not `bg-gray-900`)
755−2. Follow V2 rounded style
756−3. Use opacity modifiers (`bg-accent/10`)
757−4. Maintain color context (sidebar colors in sidebar, app colors in main area)
758− 
759−---
760− 
761−## Common Patterns
762− 
763−### Shell Entry Point Pattern
764− 
765−The app entry point follows a clean provider hierarchy:
766− 
767−```tsx
768−// Shell.tsx
769−export function Shell({ client }: { client: SpacedriveClient }) {
770− const platform = usePlatform();
771− 
772− return (
773− <SpacedriveProvider client={client}>
774− <ServerProvider>
775− <TabManagerProvider routes={explorerRoutes}>
776− <TabKeyboardHandler />
777− <DndProvider>
778− <RouterProvider router={router} />
779− </DndProvider>
780− </TabManagerProvider>
781− </ServerProvider>
782− </SpacedriveProvider>
783− );
784−}
785− 
786−// ShellLayout renders inside router, provides layout chrome
787−// Routes (ExplorerView, Overview, etc.) render inside <Outlet />
788−```
789− 
790−### TopBar Portal Pattern
791− 
792−Views register their TopBar buttons via portal:
793− 
794−```tsx
795−// ExplorerView.tsx
796−import { TopBarPortal } from '../../TopBar';
797− 
798−function ExplorerView() {
799− return (
800− <>
801− <TopBarPortal
802− left={<BackButton />}
803− center={<PathBar />}
804− right={<ViewControls />}
805− />
806− <div>{/* View content */}</div>
807− </>
808− );
809−}
810−```
811− 
812−### Library Switcher Pattern
813− 
814−```tsx
815−const client = useSpacedriveClient();
816−const { data: libraries } = useLibraries();
817−const [currentLibraryId, setCurrentLibraryId] = useState<string | null>(null);
818− 
819−// Auto-select first library
820−useEffect(() => {
821− if (libraries && libraries.length > 0 && !currentLibraryId) {
822− client.setCurrentLibrary(libraries[0].id);
823− setCurrentLibraryId(libraries[0].id);
824− }
825−}, [libraries, currentLibraryId, client]);
826− 
827−// Switch library
828−const handleSwitch = (id: string) => {
829− client.setCurrentLibrary(id);
830− setCurrentLibraryId(id);
831−};
832−```
833− 
834−### Sidebar Item Pattern
835− 
836−```tsx
837−function SidebarItem({ icon: Icon, label, active }: Props) {
838− return (
839− <button
840− className={clsx(
841− "flex items-center gap-2 px-2 py-1 rounded-md text-sm font-medium",
842− active
843− ? "bg-sidebar-selected text-sidebar-ink"
844− : "text-sidebar-inkDull hover:text-sidebar-ink"
845− )}
846− >
847− <Icon className="size-4" weight={active ? "fill" : "bold"} />
848− <span className="truncate">{label}</span>
849− </button>
850− );
851−}
852−```
853− 
854−### Dropdown Pattern
855− 
856−```tsx
857−<DropdownMenu.Root
858− trigger={<button className="...">Trigger</button>}
859− className="bg-sidebar-box border-sidebar-line rounded-lg"
860−>
861− <DropdownMenu.Item
862− className="px-2 py-1 hover:bg-sidebar-selected"
863− onClick={() => action()}
864− >
865− Item content
866− </DropdownMenu.Item>
867− <DropdownMenu.Separator className="border-sidebar-line" />
868−</DropdownMenu.Root>
869−```
870− 
871−### Context Menu Pattern
872− 
873−Use `useContextMenu` hook for platform-agnostic context menus:
874− 
875−```tsx
876−import { useContextMenu } from '../hooks/useContextMenu';
877−import { Copy, Trash } from '@phosphor-icons/react';
878− 
879−const { selectedFiles } = useExplorer();
880−const copyFiles = useLibraryMutation('files.copy');
881−const deleteFiles = useLibraryMutation('files.delete');
882− 
883−const contextMenu = useContextMenu({
884− items: [
885− {
886− icon: Copy,
887− label: selectedFiles.length > 1 ? `Copy ${selectedFiles.length} items` : "Copy",
888− onClick: async () => {
889− await copyFiles.mutateAsync({
890− sources: { paths: selectedFiles.map(f => f.sd_path) },
891− destination: currentPath,
892− overwrite: false,
893− verify_checksum: false,
894− preserve_timestamps: true,
895− move_files: false,
896− copy_method: "Auto"
897− });
898− },
899− keybind: "⌘C",
900− condition: () => selectedFiles.length > 0, // Only show if files selected
901− },
902− { type: "separator" },
903− {
904− icon: Trash,
905− label: "Delete",
906− onClick: async () => {
907− await deleteFiles.mutateAsync({
908− targets: { paths: selectedFiles.map(f => f.sd_path) },
909− permanent: false,
910− recursive: true
911− });
912− },
913− keybind: "⌘⌫",
914− variant: "danger"
915− }
916− ]
917−});
918− 
919−return <div onContextMenu={contextMenu.show}>Content</div>;
920−```
921− 
922−**Key features:**
923−- Platform-agnostic (native on Tauri, Radix on web)
924−- Conditional items via `condition` callback
925−- Smart labels that update based on state
926−- Supports icons, keybinds, variants, submenus, separators
927−- Use `useLibraryMutation` for actions, not `client.execute()`
928− 
929−---
930− 
931−## Type-Safe Query Pattern
932− 
933−### Query Keys
934− 
935−Use descriptive, hierarchical keys:
936−```tsx
937−// Good
938−queryKey: ['libraries', 'list']
939−queryKey: ['files', 'directory', libraryId, path]
940− 
941−// Bad
942−queryKey: ['getLibraries']
943−queryKey: ['data']
944−```
945− 
946−### Using Queries
947− 
948−```tsx
949−const { data, isLoading, error } = useCoreQuery({
950− type: 'libraries.list',
951− input: { include_stats: true },
952−});
953− 
954−// data is automatically typed as LibraryInfo[]!
955−```
956− 
957−---
958− 
959−## Testing Requirements
960− 
961−### Critical Paths Must Be Tested
962− 
963−- Explorer file operations
964−- Library switching
965−- Settings mutations
966−- Search functionality
967− 
968−### Test Pattern
969− 
970−```tsx
971−import { render, screen } from '@testing-library/react';
972−import { Shell } from './Shell';
973− 
974−test('switches libraries', async () => {
975− const user = userEvent.setup();
976− render(<Shell client={mockClient} />);
977− 
978− await user.click(screen.getByText('Switch Library'));
979− // ...
980−});
981−```
982− 
983−---
984− 
985−## Migration from V1
986− 
987−When porting V1 components:
988− 
989−1. **Update colors:** `bg-gray-900` → `bg-app`, `text-gray-400` → `text-ink-dull`
990−2. **Use primitives:** Extract reusable parts to @sd/ui
991−3. **Remove state:** Move to @sd/ts-client if global, use local state if component-specific
992−4. **Update queries:** Use new type-safe hooks
993−5. **Add rounding:** V1 used `rounded-md`, V2 uses `rounded-lg`
994− 
995−---
996− 
997−## Checklist Before PR
998− 
999−- [ ] All colors use semantic classes (no `var()` directly)
1000−- [ ] Component uses primitives from @sd/ui where applicable
1001−- [ ] Type-safe queries/mutations (no manual fetch)
1002−- [ ] Follows V2 rounded style
1003−- [ ] No `any` types
1004−- [ ] Proper cleanup in useEffect
1005−- [ ] Accessible (keyboard nav, ARIA labels)
1006−- [ ] Tested critical paths
1007− 
1008−---
1009− 
1010−## Quick Reference
1011− 
1012−### Import Order
1013− 
1014−```tsx
1015−// 1. External libraries
1016−import { useState } from 'react';
1017−import { motion } from 'framer-motion';
1018− 
1019−// 2. @sd packages
1020−import { Button, DropdownMenu } from '@sd/ui';
1021−import { useCoreQuery } from '@sd/ts-client';
1022− 
1023−// 3. Local imports
1024−import { useLibraries } from './hooks/useLibraries';
1025−import clsx from 'clsx';
1026−```
1027− 
1028−### Common Mistakes
1029− 
1030−`<style>` or `<style jsx>` tags → Use Tailwind arbitrary variants
1031−`className="bg-[var(--color-sidebar)]"` → `className="bg-sidebar"`
1032−`bg-gray-900` → `bg-app`
1033−`rounded-md` everywhere → `rounded-lg` for V2
1034−Manual fetch → Use type-safe hooks
1035−State in component → Use @sd/ts-client or local state
1036− 
1037−---
1038− 
1039−## Questions to Ask
1040− 
1041−Before writing code:
1042− 
1043−1. **Is this a primitive?** → Should it be in @sd/ui?
1044−2. **Is this state global?** → Should it be in @sd/ts-client?
1045−3. **Are the types auto-generated?** → Don't duplicate them!
1046−4. **Can I use a semantic color?** → Yes, always!
1047−5. **Is this accessible?** → Keyboard nav? ARIA labels?
1048− 
1049−---
1050− 
1051−## Resources
1052− 
1053−- **Type Generation:** `cargo run --bin generate_typescript_types`
1054−- **Color System:** `/docs/react/ui/colors.mdx`
1055−- **Workbench Docs:** `/workbench/interface/`
1056−- **V1 Reference:** `/Users/jamespine/Projects/spacedrive_v1`
1057− 
1058−---
1059− 
1060−## Status: Current Implementation
1061− 
1062−**Complete:**
1063−- Type-safe client with auto-generated types
1064−- Native macOS traffic lights
1065−- V1 color system as CSS variables
1066−- Expanding dropdown (DropdownMenu primitive)
1067−- Explorer with sidebar and library switcher
1068−- TanStack Query integration
1069−- Clean architecture refactor (Shell → ShellLayout → Views)
1070−- Extracted DndProvider for drag-and-drop coordination
1071−- QuickPreview components (Controller + Syncer)
1072−- TopBar portal system for view-specific controls
1073− 
1074−**In Progress:**
1075−- Port remaining V1 components
1076−- Build complete Explorer (file grid/list views)
1077−- Settings pages
1078−- Multi-window system
1079− 
1080−---
1081− 
1082−**Remember:** This is a living document. Update it when architectural decisions are made. This is our rulebook for building a world-class file manager interface!
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack