Claude Code Plugins

Community-maintained marketplace

Feedback

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 Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name electron-fsd
description 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.

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
entities Data display only READ document-card.tsx, page-thumbnail.tsx
features User actions CUD use-merge-command.ts, merge-toolbar.tsx
widgets Feature composition - merge-workspace.tsx
shared Cross-cutting concerns - ipc-client.ts, types, UI components

Component Creation Workflow

Creating an Entity (READ-only)

To create a new entity for displaying data:

  1. Create entity folder: src/renderer/entities/{entity-name}/
  2. Structure:
entities/pdf-document/
├─ ui/
│  └─ document-card.tsx     # Display component
└─ model/
   └─ use-pdf-metadata.ts   # Data fetching hook (READ)
  1. 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:

  1. Create feature folder: src/renderer/features/{feature-name}/
  2. 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
  1. 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:

  1. Create widget folder: src/renderer/widgets/{widget-name}/
  2. Structure:
widgets/merge-workspace/
└─ merge-workspace.tsx
  1. 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 pdf-merge-service.ts
React components PascalCase MergeWorkspace
Hooks camelCase with use prefix useMergeCommand
Types/interfaces PascalCase MergeRequest
Services PascalCase + Service PdfMergeService
Workers kebab-case + worker merge-worker.ts

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/

  • fsd-layers.md - Detailed FSD layer documentation
  • electron-patterns.md - Electron-specific patterns

To delete unused example files in scripts/ and assets/ directories as this skill focuses on architectural guidance.