Skills fable-dev
install
source · Clone the upstream repo
git clone https://github.com/openclaw/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/anoldgh/fable-dev" ~/.claude/skills/openclaw-skills-fable-dev && rm -rf "$T"
OpenClaw · Install into ~/.openclaw/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.openclaw/skills && cp -r "$T/skills/anoldgh/fable-dev" ~/.openclaw/skills/openclaw-skills-fable-dev && rm -rf "$T"
manifest:
skills/anoldgh/fable-dev/SKILL.mdsource content
Fable Development Guide
Fable is a creative-writing desktop app: Electron + React 19 + Vite + TanStack Query + Plate.js frontend, Node.js + SQLite + custom indexed filesystem backend, Google ADK for AI features.
Critical Invariant
Every file can have children, not just folders.
FileNode.children exists on ANY node regardless of category. A text document can contain child documents (chapters containing scenes). Never assume only folders have children.
parentId and siblingIndex are computed by buildFileTree() at runtime — never persist them.
Where Does My Code Go?
digraph code_placement { rankdir=TB; node [shape=diamond]; "Used by both\nfrontend & backend?" -> "src/shared/" [label="yes"]; "Used by both\nfrontend & backend?" -> "Runs in\nNode.js only?" [label="no"]; "Runs in\nNode.js only?" -> "IPC handler?" [label="yes"]; "Runs in\nNode.js only?" -> "React component\nor hook?" [label="no"]; "IPC handler?" -> "src/backend/handlers/" [label="yes"]; "IPC handler?" -> "src/backend/services/" [label="no"]; "React component\nor hook?" -> "src/frontend/components/" [label="component"]; "React component\nor hook?" -> "src/frontend/hooks/" [label="hook"]; "React component\nor hook?" -> "src/frontend/services/" [label="service/singleton"]; node [shape=box]; "src/shared/" [style=filled, fillcolor="#e8f5e9"]; "src/backend/handlers/" [style=filled, fillcolor="#e3f2fd"]; "src/backend/services/" [style=filled, fillcolor="#e3f2fd"]; "src/frontend/components/" [style=filled, fillcolor="#fff3e0"]; "src/frontend/hooks/" [style=filled, fillcolor="#fff3e0"]; "src/frontend/services/" [style=filled, fillcolor="#fff3e0"]; }
Shared code constraint:
src/shared/ must NOT import browser APIs, Node.js APIs, or React.
How Do I Access / Mutate Data?
digraph data_access { rankdir=TB; node [shape=diamond]; "Inside React\ncomponent?" -> "Use query hooks" [label="yes"]; "Inside React\ncomponent?" -> "Use queryService\nsingleton" [label="no\n(command, service)"]; "Need to mutate\nfile tree?" -> "queryService.mutations.*\n(optimistic updates)" [label="always"]; "Need to read\nfile content?" -> "readFileContent()\nhelper (cache-first)" [label="preferred"]; node [shape=box]; "Use query hooks" [style=filled, fillcolor="#e8f5e9"]; "Use queryService\nsingleton" [style=filled, fillcolor="#e8f5e9"]; "queryService.mutations.*\n(optimistic updates)" [style=filled, fillcolor="#fff3e0"]; "readFileContent()\nhelper (cache-first)" [style=filled, fillcolor="#fff3e0"]; }
Never call
directly for tree mutations — always go through window.api.*
queryService.mutations.* which handles optimistic updates and rollback.
Key Directories
| Directory | What to find |
|---|---|
| All shared types (FileNode, FileMetadata, ChatSession, Agent, etc.) |
| Code shared by both processes (graph, agentFlow, toolDescriptors, search) |
| IPC handlers: file, project, agent, agentFlow |
| IndexedFsService, UserDataService, ReferenceIndexService, agent/ |
| AI system: AdkRunner, ToolFactory, providers, ChatDatabaseService |
| All React components (chat/, graph/, fileViewer/, collectionViewer/) |
| VS Code-style commands, keybindings, context keys |
| TanStack Query: queryService, mutations, treeTransformations |
| Custom hooks: useFileTree, useChat, useAgentFlowRun, etc. |
| Plate.js editor plugins and kits |
| i18n translations (8 namespaces) |
| Playwright E2E tests |
| IPC bridge — definitions |
| TypeScript declarations for |
Common Task Recipes
Read
references/recipes.md for detailed step-by-step procedures including:
- Adding a new IPC channel (5-step checklist)
- Adding a new command (5-step checklist)
- Adding a new file subType (8-step checklist)
- Adding a new agent tool (6-step checklist)
- Adding a new sidebar panel or editor handler
- Adding i18n strings
Architecture at a Glance
Read
references/architecture.md for the full breakdown. Key mental model:
Frontend (React) --window.api.*--> Preload --IPC--> Backend handlers --> Services --> Disk <--transport push events-- <--IPC-- ProjectManager <-- IndexedFsService events
- State: TanStack Query is single source of truth.
everywhere.staleTime: Infinity - File tree: Event-driven push from backend, never polled.
sets up listener.useFileTree - Mutations:
factory: optimistic update -> IPC call -> rollback on error.createTreeMutation - Commands: VS Code-style
+CommandService
+ContextService
.KeybindingService - Editor routing:
dispatches byContentViewer
first, thensubType
.category - AI: Google ADK with multi-provider LLM registry. See
.references/agent-system.md
i18n Checklist
- 8 namespaces:
,common
,fileExplorer
,chat
,project
,editor
,agentBuilder
,graphagentDefinition - In components:
const { t } = useTranslation("namespace") - Outside React:
import i18n from "@/src/i18n/i18n"; i18n.t("ns:key") - Always add keys to BOTH
andenzh-CN
Build, Lint & Test
npm run dev:electron # Dev mode (Vite + Electron) npm run build # Production build npm run check # Lint + format + TypeScript check npm run test:e2e # Playwright E2E tests (headless) npm run test:e2e:headed # E2E with visible browser
- Path alias:
maps to repo root@/* - Formatting: Prettier (double quotes, 4-space indent, semicolons)
- Always run E2E tests you wrote before committing
Pitfalls
- Any node can have children — not just folders
- parentId/siblingIndex are derived — never persist
- Tree mutations must be pure —
returns new arrays, never mutatetreeTransformations.ts - Always add i18n to BOTH locales —
andenzh-CN - Toolbar renders via portal —
in TopBar, not inside editor DOM#file-toolbar-portal - Input elements suppress keybindings —
checksuseKeybindings
tagevent.target - subType controls routing — checked before category in ContentViewer
- Binary files use base64 over IPC — text uses UTF-8
- STRUCTURAL_FILE_CHANGES control broadcasts — content "update" does NOT re-push tree
- Add Window.api types — update
when adding IPC channelssrc/global.d.ts - Shared code is process-agnostic —
cannot import browser or Node APIssrc/shared/ - Tool descriptors are shared —
serves both UI commands and LLM agentstoolDescriptors.ts - Every user-facing data element needs a tooltip
References
| Reference | Use when |
|---|---|
| Understanding layers, IPC channels, backend services, frontend providers |
| Working with commands, keybindings, context keys, when clauses |
| On-disk file layout, metadata schema, storage guidelines |
| AI features: providers, streaming, tools, planning mode, chat persistence |
| Step-by-step procedures for common development tasks |