Claude-skill-registry electron-fsd
This skill should be used when developing Electron applications with Feature-Sliced Design (FSD) architecture and React 19. Triggers on requests to create components, features, entities, widgets, or pages following FSD layer structure. Also applies when setting up Electron main/preload/renderer process code organization.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/electron-fsd" ~/.claude/skills/majiayu000-claude-skill-registry-electron-fsd && rm -rf "$T"
manifest:
skills/data/electron-fsd/SKILL.mdsource content
Electron FSD Development (React 19)
Overview
This skill provides guidance for developing Electron applications using Feature-Sliced Design (FSD) architecture with React 19. It covers the three-process model (Main, Preload, Renderer) and FSD layer organization for the Renderer process.
Architecture Patterns
Electron Process Model
┌─────────────────────────────────────────────────────────────┐ │ Main Process (Node.js) │ │ ├─ app/main.ts - App lifecycle, BrowserWindow │ │ ├─ app/ipc-handler.ts - IPC routing to services │ │ ├─ services/ - Business logic (PdfMergeService) │ │ └─ workers/ - CPU-intensive tasks │ ├─────────────────────────────────────────────────────────────┤ │ Preload (Isolated) │ │ └─ index.ts - ContextBridge (window.api) │ ├─────────────────────────────────────────────────────────────┤ │ Renderer (React + FSD) │ │ ├─ shared/ - Common utilities, UI, types │ │ ├─ entities/ - READ (data display) │ │ ├─ features/ - CUD (user actions) │ │ ├─ widgets/ - Feature composition │ │ └─ pages/ - Route entry points │ └─────────────────────────────────────────────────────────────┘
FSD Layer Responsibilities
| Layer | Responsibility | IPC Direction | Example |
|---|---|---|---|
| Data display only | READ | , |
| User actions | CUD | , |
| Feature composition | - | |
| Cross-cutting concerns | - | , types, UI components |
Component Creation Workflow
Creating an Entity (READ-only)
To create a new entity for displaying data:
- Create entity folder:
src/renderer/entities/{entity-name}/ - Structure:
entities/pdf-document/ ├─ ui/ │ └─ document-card.tsx # Display component └─ model/ └─ use-pdf-metadata.ts # Data fetching hook (READ)
- Entity components must:
- Only display data (no mutations)
- Use hooks for data fetching
- Accept data via props or context
Creating a Feature (CUD operations)
To create a new feature for user actions:
- Create feature folder:
src/renderer/features/{feature-name}/ - Structure:
features/pdf-merge/ ├─ ui/ │ ├─ merge-toolbar.tsx # Action UI │ └─ merge-file-grid.tsx # Interactive grid with DnD └─ model/ └─ use-merge-command.ts # IPC command hook
- Feature components must:
- Handle user interactions
- Call IPC commands for mutations
- Compose entities for display
Creating a Widget (Composition)
To compose features and entities into workspace:
- Create widget folder:
src/renderer/widgets/{widget-name}/ - Structure:
widgets/merge-workspace/ └─ merge-workspace.tsx
- Widget implementation:
// merge-workspace.tsx export function MergeWorkspace() { const { files, addFiles, removeFile } = useMergeContext(); return ( <> <MergeToolbar onAddFiles={addFiles} /> <MergeFileGrid files={files} onRemove={removeFile} /> </> ); }
File Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Folders & files | kebab-case | |
| React components | PascalCase | |
| Hooks | camelCase with prefix | |
| Types/interfaces | PascalCase | |
| Services | PascalCase + Service | |
| Workers | kebab-case + worker | |
React 19 Component Patterns
No forwardRef (React 19)
// ❌ React 18 (forwardRef) const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => ( <input ref={ref} {...props} /> )); // ✅ React 19 (ref as regular prop) interface InputProps { ref?: React.Ref<HTMLInputElement>; placeholder?: string; } function Input({ ref, ...props }: InputProps) { return <input ref={ref} {...props} />; }
Context as Provider (React 19)
// ❌ React 18 <ThemeContext.Provider value={theme}> {children} </ThemeContext.Provider> // ✅ React 19 <ThemeContext value={theme}> {children} </ThemeContext>
use() Hook for Promise/Context
// ✅ React 19: use() for Promise (requires Suspense) import { use, Suspense } from 'react'; function FileList({ filesPromise }: { filesPromise: Promise<PdfFile[]> }) { const files = use(filesPromise); return files.map(f => <FileCard key={f.id} file={f} />); } // Usage with Suspense <Suspense fallback={<Loading />}> <FileList filesPromise={fetchFiles()} /> </Suspense> // ✅ React 19: use() for Context (conditional allowed) function ConditionalTheme({ show }: { show: boolean }) { if (show) { const theme = use(ThemeContext); return <div className={theme}>Themed</div>; } return null; }
useActionState for Forms
// ✅ React 19: Form action with state import { useActionState } from 'react'; function MergeForm() { const [state, submitAction, isPending] = useActionState( async (prev, formData) => { const files = formData.getAll('files'); const result = await window.api.merge(files); return result; }, null ); return ( <form action={submitAction}> <input type="file" name="files" multiple disabled={isPending} /> <button disabled={isPending}> {isPending ? 'Merging...' : 'Merge PDFs'} </button> </form> ); }
useOptimistic for Optimistic UI
// ✅ React 19: Optimistic updates import { useOptimistic } from 'react'; function FileList({ files }: { files: PdfFile[] }) { const [optimisticFiles, addOptimistic] = useOptimistic( files, (state, newFile: PdfFile) => [...state, { ...newFile, pending: true }] ); async function handleAdd(file: File) { const tempFile = { id: 'temp', name: file.name, pending: true }; addOptimistic(tempFile); await window.api.addFile(file); } return ( <div> {optimisticFiles.map(f => ( <FileCard key={f.id} file={f} opacity={f.pending ? 0.5 : 1} /> ))} </div> ); }
ref Callback Cleanup
// ✅ React 19: ref cleanup function function AutoFocusInput() { return ( <input ref={(el) => { if (el) el.focus(); return () => { // cleanup when element removed console.log('Input unmounted'); }; }} /> ); }
Type Definition Rules
No Inline Object Types
// ❌ Avoid function process(data: { id: string; name: string }) {} // ✅ Preferred interface ProcessData { id: string; name: string } function process(data: ProcessData) {}
Constants with ValueOf Pattern
// Define constants export const MERGE_STATUS = { IDLE: 'idle', PENDING: 'pending', COMPLETE: 'complete', } as const; // Create union type type ValueOf<T> = T[keyof T]; type MergeStatus = ValueOf<typeof MERGE_STATUS>; // Usage const status: MergeStatus = MERGE_STATUS.PENDING;
Resources
references/
- Detailed FSD layer documentationfsd-layers.md
- Electron-specific patternselectron-patterns.md
To delete unused example files in
scripts/ and assets/ directories as this skill focuses on architectural guidance.