wode-emittery-pattern
Use when implementing event-driven communication between React components using Emittery, including event types, sidecar components, or subscription hooks
Works with
Agent Skills format with YAML frontmatter. Claude Code reads it as-is.
---
name: "wode-emittery-pattern"
description: "Use when implementing event-driven communication between React components using Emittery, including event types, sidecar components, or subscription hooks"
license: "MIT"
---
# Wode Emittery Event-Driven Pattern
You are strictly required to follow this event-driven architecture when implementing inter-component communication or side-effect handling. This pattern decouples state management (Zustand) from side-effects (event handlers).
## 1. Core Rules (MUST FOLLOW)
1. **Event Types:** MUST use `as const` objects named `XxxEventType`. NEVER use TypeScript enums.
2. **Event Naming:** MUST use `Module:Action` format (e.g., `FileSystem:Open`, `User:Login`).
3. **Typed Emitter:** MUST define `type XxxEmitter = Emittery<XxxEventData>` for full type safety.
4. **Sidecar Pattern:** Side-effects (toast, confirm dialogs, network requests) MUST be handled in Sidecar components, NEVER inside Store actions.
5. **Store Decoupling:** Store actions MUST only emit events. They MUST NOT care who handles them.
## 2. Event Type Definition
```typescript
// MUST use as const, NEVER enum
export const FileSystemEventType = {
Open: 'FileSystem:Open',
Goto: 'FileSystem:Goto',
Refresh: 'FileSystem:Refresh',
Download: 'FileSystem:Download',
Delete: 'FileSystem:Delete',
} as const;
// Event data types
type FileSystemEventData = {
[FileSystemEventType.Open]: { fs: IFileSystem; file: IFileStat };
[FileSystemEventType.Goto]: { path: string };
[FileSystemEventType.Refresh]: {};
[FileSystemEventType.Download]: { fs: IFileSystem; files: IFileStat[] };
[FileSystemEventType.Delete]: { fs: IFileSystem; files: IFileStat[] | string[] };
};
// Emitter type
export type FileSystemEmitter = Emittery<FileSystemEventData>;
```
## 3. Emitter Factory & Store Integration
```typescript
import Emittery from 'emittery';
export function createEmitter<EventData = Record<string, any>>(name: string) {
return new Emittery<EventData>({ debug: { name } });
}
// In Store — actions MUST only emit events
const events: FileSystemEmitter = createEmitter('FileSystemEmitter');
actions: {
open: async (item) => {
// Emit event, let Sidecar handle the rest
return events.emit(FileSystemEventType.Open, { fs: getState().fs, file: item });
},
refresh: () => events.emit(FileSystemEventType.Refresh, {}),
}
```
## 4. useEmitteryListen Hook
```typescript
export function useEmitteryListen<E>(
e: Emittery<E>,
handle: { [K in keyof E]?: (data: E[K]) => void },
) {
const ref = useRef<any>(handle);
ref.current = handle;
useEffect(() => {
const unsub = Object.keys(ref.current).map((event) => {
return e.on(event as any, (data) => ref.current[event]?.(data));
});
return () => unsub.forEach((fn) => fn());
}, [e, ...Object.keys(handle).sort()]);
}
```
## 5. Sidecar Component
```tsx
export const FileManagerSidecar = () => {
const { events, actions } = useFileManagerStore(
useShallow((s) => ({ events: s.events, actions: s.actions }))
);
useEmitteryListen(events, {
[FileSystemEventType.Refresh]: async () => {
await load();
},
[FileSystemEventType.Delete]: async ({ fs, files }) => {
const ok = await showConfirm({ title: '删除确认' });
if (!ok) return;
for (const file of files) await fs.rm(file.path);
actions.refresh();
},
});
return null; // Renders no UI
};
// Mount inside Provider
<FileManagerProvider>
<FileManagerSidecar /> {/* Event handler */}
{children}
</FileManagerProvider>
```
## 6. Advanced Patterns
For complete Sidecar implementation examples (upload, rename, move, mkdir, error handling) and best practices, read: [references/events-pattern.md](references/events-pattern.md)More Frontend Frameworks skills
frontend-design
anthropics/skills
Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.
design-taste-frontend
leonxlnx/taste-skill
Anti-slop frontend skill for landing pages, portfolios, and redesigns. The agent reads the brief, infers the right design direction, and ships interfaces that do not look templated. Real design systems when applicable, audit-first on redesigns, strict pre-flight check.
hyperframes-creative
heygen-com/hyperframes
Non-animation creative direction for HyperFrames videos. Use for design spec (frame.md / design.md) handling, palettes, typography, narration, beat planning, audio-reactive visuals, composition patterns, and brand / style decisions. For atomic motion patterns and scene blueprints, use hyperframes-animation.

