From 629d4c0c61874242f5374efcb20eee4f25427106 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Mon, 11 May 2026 22:06:06 -0400 Subject: [PATCH] docs: salvage agent and motion workflow skills --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- AGENTS.md | 4 +- README.md | 6 +- README.zh-CN.md | 2 +- agent.yaml | 4 + docs/zh-CN/AGENTS.md | 4 +- docs/zh-CN/README.md | 6 +- manifests/install-modules.json | 4 + package.json | 4 + skills/agent-architecture-audit/SKILL.md | 256 ++++++++++ skills/agentic-os/SKILL.md | 387 +++++++++++++++ skills/error-handling/SKILL.md | 376 +++++++++++++++ skills/motion-ui/SKILL.md | 575 +++++++++++++++++++++++ 14 files changed, 1619 insertions(+), 13 deletions(-) create mode 100644 skills/agent-architecture-audit/SKILL.md create mode 100644 skills/agentic-os/SKILL.md create mode 100644 skills/error-handling/SKILL.md create mode 100644 skills/motion-ui/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 612c8627..191b1342 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -11,7 +11,7 @@ { "name": "ecc", "source": "./", - "description": "The most comprehensive Claude Code plugin — 56 agents, 213 skills, 72 legacy command shims, selective install profiles, and production-ready hooks for TDD, security scanning, code review, and continuous learning", + "description": "The most comprehensive Claude Code plugin — 56 agents, 217 skills, 72 legacy command shims, selective install profiles, and production-ready hooks for TDD, security scanning, code review, and continuous learning", "version": "2.0.0-rc.1", "author": { "name": "Affaan Mustafa", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 7969aca0..a39d5c24 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "ecc", "version": "2.0.0-rc.1", - "description": "Battle-tested Claude Code plugin for engineering teams — 56 agents, 213 skills, 72 legacy command shims, production-ready hooks, and selective install workflows evolved through continuous real-world use", + "description": "Battle-tested Claude Code plugin for engineering teams — 56 agents, 217 skills, 72 legacy command shims, production-ready hooks, and selective install workflows evolved through continuous real-world use", "author": { "name": "Affaan Mustafa", "url": "https://x.com/affaanmustafa" diff --git a/AGENTS.md b/AGENTS.md index 196c3f90..738700a0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Everything Claude Code (ECC) — Agent Instructions -This is a **production-ready AI coding plugin** providing 56 specialized agents, 213 skills, 72 commands, and automated hook workflows for software development. +This is a **production-ready AI coding plugin** providing 56 specialized agents, 217 skills, 72 commands, and automated hook workflows for software development. **Version:** 2.0.0-rc.1 @@ -148,7 +148,7 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat ``` agents/ — 56 specialized subagents -skills/ — 213 workflow skills and domain knowledge +skills/ — 217 workflow skills and domain knowledge commands/ — 72 slash commands hooks/ — Trigger-based automations rules/ — Always-follow guidelines (common + per-language) diff --git a/README.md b/README.md index cf6bc714..baa6efad 100644 --- a/README.md +++ b/README.md @@ -358,7 +358,7 @@ If you stacked methods, clean up in this order: /plugin list ecc@ecc ``` -**That's it!** You now have access to 56 agents, 213 skills, and 72 legacy command shims. +**That's it!** You now have access to 56 agents, 217 skills, and 72 legacy command shims. ### Dashboard GUI @@ -1362,7 +1362,7 @@ The configuration is automatically detected from `.opencode/opencode.json`. |---------|-------------|----------|--------| | Agents | PASS: 56 agents | PASS: 12 agents | **Claude Code leads** | | Commands | PASS: 72 commands | PASS: 35 commands | **Claude Code leads** | -| Skills | PASS: 213 skills | PASS: 37 skills | **Claude Code leads** | +| Skills | PASS: 217 skills | PASS: 37 skills | **Claude Code leads** | | Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** | | Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** | | MCP Servers | PASS: 14 servers | PASS: Full | **Full parity** | @@ -1467,7 +1467,7 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e |---------|------------|------------|-----------|----------| | **Agents** | 56 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 | | **Commands** | 72 | Shared | Instruction-based | 35 | -| **Skills** | 213 | Shared | 10 (native format) | 37 | +| **Skills** | 217 | Shared | 10 (native format) | 37 | | **Hook Events** | 8 types | 15 types | None yet | 11 types | | **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks | | **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions | diff --git a/README.zh-CN.md b/README.zh-CN.md index 0504057a..32846330 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -160,7 +160,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/" /plugin list ecc@ecc ``` -**完成!** 你现在可以使用 56 个代理、213 个技能和 72 个命令。 +**完成!** 你现在可以使用 56 个代理、217 个技能和 72 个命令。 ### multi-* 命令需要额外配置 diff --git a/agent.yaml b/agent.yaml index a21c3391..80fa07d6 100644 --- a/agent.yaml +++ b/agent.yaml @@ -9,10 +9,12 @@ model: fallback: - claude-sonnet-4-6 skills: + - agent-architecture-audit - agent-eval - agent-harness-construction - agent-payment-x402 - agentic-engineering + - agentic-os - ai-first-engineering - ai-regression-testing - android-clean-architecture @@ -61,6 +63,7 @@ skills: - e2e-testing - energy-procurement - enterprise-agent-ops + - error-handling - eval-harness - exa-search - fal-ai-media @@ -96,6 +99,7 @@ skills: - logistics-exception-management - market-research - mcp-server-patterns + - motion-ui - nanoclaw-repl - nextjs-turbopack - nutrient-document-processing diff --git a/docs/zh-CN/AGENTS.md b/docs/zh-CN/AGENTS.md index 9f3f4895..415086ee 100644 --- a/docs/zh-CN/AGENTS.md +++ b/docs/zh-CN/AGENTS.md @@ -1,6 +1,6 @@ # Everything Claude Code (ECC) — 智能体指令 -这是一个**生产就绪的 AI 编码插件**,提供 56 个专业代理、213 项技能、72 条命令以及自动化钩子工作流,用于软件开发。 +这是一个**生产就绪的 AI 编码插件**,提供 56 个专业代理、217 项技能、72 条命令以及自动化钩子工作流,用于软件开发。 **版本:** 2.0.0-rc.1 @@ -147,7 +147,7 @@ ``` agents/ — 56 个专业子代理 -skills/ — 213 个工作流技能和领域知识 +skills/ — 217 个工作流技能和领域知识 commands/ — 72 个斜杠命令 hooks/ — 基于触发的自动化 rules/ — 始终遵循的指导方针(通用 + 每种语言) diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md index 61cb39e1..e5d77e33 100644 --- a/docs/zh-CN/README.md +++ b/docs/zh-CN/README.md @@ -224,7 +224,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/" /plugin list ecc@ecc ``` -**搞定!** 你现在可以使用 56 个智能体、213 项技能和 72 个命令了。 +**搞定!** 你现在可以使用 56 个智能体、217 项技能和 72 个命令了。 *** @@ -1134,7 +1134,7 @@ opencode |---------|-------------|----------|--------| | 智能体 | PASS: 56 个 | PASS: 12 个 | **Claude Code 领先** | | 命令 | PASS: 72 个 | PASS: 35 个 | **Claude Code 领先** | -| 技能 | PASS: 213 项 | PASS: 37 项 | **Claude Code 领先** | +| 技能 | PASS: 217 项 | PASS: 37 项 | **Claude Code 领先** | | 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** | | 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** | | MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** | @@ -1242,7 +1242,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以 |---------|------------|------------|-----------|----------| | **智能体** | 56 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 | | **命令** | 72 | 共享 | 基于指令 | 35 | -| **技能** | 213 | 共享 | 10 (原生格式) | 37 | +| **技能** | 217 | 共享 | 10 (原生格式) | 37 | | **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 | | **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 | | **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 | diff --git a/manifests/install-modules.json b/manifests/install-modules.json index 627a27ef..c6c3477c 100644 --- a/manifests/install-modules.json +++ b/manifests/install-modules.json @@ -139,6 +139,7 @@ "skills/fastapi-patterns", "skills/frontend-patterns", "skills/frontend-slides", + "skills/motion-ui", "skills/golang-patterns", "skills/golang-testing", "skills/java-coding-standards", @@ -229,6 +230,7 @@ "skills/continuous-learning-v2", "skills/council", "skills/e2e-testing", + "skills/error-handling", "skills/eval-harness", "skills/hookify-rules", "skills/iterative-retrieval", @@ -513,8 +515,10 @@ "kind": "skills", "description": "Agentic engineering, autonomous loops, agent harness construction, and LLM pipeline optimization skills.", "paths": [ + "skills/agent-architecture-audit", "skills/agent-harness-construction", "skills/agentic-engineering", + "skills/agentic-os", "skills/ai-first-engineering", "skills/autonomous-loops", "skills/blueprint", diff --git a/package.json b/package.json index 7f4aff76..a56b27be 100644 --- a/package.json +++ b/package.json @@ -90,10 +90,12 @@ "scripts/status.js", "scripts/work-items.js", "scripts/uninstall.js", + "skills/agent-architecture-audit/", "skills/agent-harness-construction/", "skills/agent-introspection-debugging/", "skills/agent-sort/", "skills/agentic-engineering/", + "skills/agentic-os/", "skills/ai-first-engineering/", "skills/ai-regression-testing/", "skills/android-clean-architecture/", @@ -147,6 +149,7 @@ "skills/email-ops/", "skills/energy-procurement/", "skills/enterprise-agent-ops/", + "skills/error-handling/", "skills/eval-harness/", "skills/evm-token-decimals/", "skills/exa-search/", @@ -193,6 +196,7 @@ "skills/mcp-server-patterns/", "skills/messages-ops/", "skills/mle-workflow/", + "skills/motion-ui/", "skills/mysql-patterns/", "skills/nanoclaw-repl/", "skills/nestjs-patterns/", diff --git a/skills/agent-architecture-audit/SKILL.md b/skills/agent-architecture-audit/SKILL.md new file mode 100644 index 00000000..75d33e9b --- /dev/null +++ b/skills/agent-architecture-audit/SKILL.md @@ -0,0 +1,256 @@ +--- +name: agent-architecture-audit +description: Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. +origin: oh-my-agent-check +tools: Read, Write, Edit, Bash, Grep, Glob +--- + +# Agent Architecture Audit + +A diagnostic workflow for agent systems that hide failures behind wrapper layers, stale memory, retry loops, or transport/rendering mutations. + +## When to Activate + +**MANDATORY for:** +- Releasing any agent or LLM-powered application to production +- Shipping features with tool calling, memory, or multi-step workflows +- Agent behavior degrades after adding wrapper layers +- User reports "the agent is getting worse" or "tools are flaky" +- Same model works in playground but breaks inside your wrapper +- Debugging agent behavior for more than 15 minutes without finding root cause + +**Especially critical when:** +- You've added new prompt layers, tool definitions, or memory systems +- Different agents in your system behave inconsistently +- The model was fine yesterday but is hallucinating today +- You suspect hidden repair/retry loops silently mutating responses + +**Do not use for:** +- General code debugging — use `agent-introspection-debugging` +- Code review — use language-specific reviewer agents +- Security scanning — use `security-review` or `security-review/scan` +- Agent performance benchmarking — use `agent-eval` +- Writing new features — use the appropriate workflow skill + +## The 12-Layer Stack + +Every agent system has these layers. Any of them can corrupt the answer: + +| # | Layer | What Goes Wrong | +|---|-------|----------------| +| 1 | System prompt | Conflicting instructions, instruction bloat | +| 2 | Session history | Stale context injection from previous turns | +| 3 | Long-term memory | Pollution across sessions, old topics in new conversations | +| 4 | Distillation | Compressed artifacts re-entering as pseudo-facts | +| 5 | Active recall | Redundant re-summary layers wasting context | +| 6 | Tool selection | Wrong tool routing, model skips required tools | +| 7 | Tool execution | Hallucinated execution — claims to call but doesn't | +| 8 | Tool interpretation | Misread or ignored tool output | +| 9 | Answer shaping | Format corruption in final response | +| 10 | Platform rendering | Transport-layer mutation (UI, API, CLI mutates valid answers) | +| 11 | Hidden repair loops | Silent fallback/retry agents running second LLM pass | +| 12 | Persistence | Expired state or cached artifacts reused as live evidence | + +## Common Failure Patterns + +### 1. Wrapper Regression + +The base model produces correct answers, but the wrapper layers make it worse. + +**Symptoms:** +- Model works fine in playground or direct API call, breaks in your agent +- Added a new prompt layer, existing behavior degraded +- Agent sounds confident but is confidently wrong +- "It was working before the last update" + +### 2. Memory Contamination + +Old topics leak into new conversations through history, memory retrieval, or distillation. + +**Symptoms:** +- Agent brings up unrelated past topics +- User corrections don't stick (old memory overwrites new) +- Same-session artifacts re-enter as pseudo-facts +- Memory grows without bound, degrading response quality over time + +### 3. Tool Discipline Failure + +Tools are declared in the prompt but not enforced in code. The model skips them or hallucinates execution. + +**Symptoms:** +- "Must use tool X" in prompt, but model answers without calling it +- Tool results look correct but were never actually executed +- Different tools fight over the same responsibility +- Model uses tool when it shouldn't, or skips it when it must + +### 4. Rendering/Transport Corruption + +The agent's internal answer is correct, but the platform layer mutates it during delivery. + +**Symptoms:** +- Logs show correct answer, user sees broken output +- Markdown rendering, JSON parsing, or streaming fragments corrupt valid responses +- Hidden fallback agent quietly replaces the answer before delivery +- Output differs between terminal and UI + +### 5. Hidden Agent Layers + +Silent repair, retry, summarization, or recall agents run without explicit contracts. + +**Symptoms:** +- Output changes between internal generation and user delivery +- "Auto-fix" loops run a second LLM pass the user doesn't know about +- Multiple agents modify the same output without coordination +- Answers get "smoothed" or "corrected" by invisible layers + +## Audit Workflow + +### Phase 1: Scope + +Define what you're auditing: + +- **Target system** — what agent application? +- **Entrypoints** — how do users interact with it? +- **Model stack** — which LLM(s) and providers? +- **Symptoms** — what does the user report? +- **Time window** — when did it start? +- **Layers to audit** — which of the 12 layers apply? + +### Phase 2: Evidence Collection + +Gather evidence from the codebase: + +- **Source code** — agent loop, tool router, memory admission, prompt assembly +- **Logs** — historical session traces, tool call records +- **Config** — prompt templates, tool schemas, provider settings +- **Memory files** — SOPs, knowledge bases, session archives + +Use `rg` to search for anti-patterns: + +```bash +# Tool requirements expressed only in prompt text (not code) +rg "must.*tool|必须.*工具|required.*call" --type md + +# Tool execution without validation +rg "tool_call|toolCall|tool_use" --type py --type ts + +# Hidden LLM calls outside main agent loop +rg "completion|chat\.create|messages\.create|llm\.invoke" + +# Memory admission without user-correction priority +rg "memory.*admit|long.*term.*update|persist.*memory" --type py --type ts + +# Fallback loops that run additional LLM calls +rg "fallback|retry.*llm|repair.*prompt|re-?prompt" --type py --type ts + +# Silent output mutation +rg "mutate|rewrite.*response|transform.*output|shap" --type py --type ts +``` + +### Phase 3: Failure Mapping + +For each finding, document: + +- **Symptom** — what the user sees +- **Mechanism** — how the wrapper causes it +- **Source layer** — which of the 12 layers +- **Root cause** — the deepest cause +- **Evidence** — file:line or log:row reference +- **Confidence** — 0.0 to 1.0 + +### Phase 4: Fix Strategy + +Default fix order (code-first, not prompt-first): + +1. **Code-gate tool requirements** — enforce in code, not just prompt text +2. **Remove or narrow hidden repair agents** — make fallback explicit with contracts +3. **Reduce context duplication** — same info through prompt + history + memory + distillation +4. **Tighten memory admission** — user corrections > agent assertions +5. **Tighten distillation triggers** — don't compress what shouldn't be compressed +6. **Reduce rendering mutation** — pass-through, don't transform +7. **Convert to typed JSON envelopes** — structured internal flow, not freeform prose + +## Severity Model + +| Level | Meaning | Action | +|-------|---------|--------| +| `critical` | Agent can confidently produce wrong operational behavior | Fix before next release | +| `high` | Agent frequently degrades correctness or stability | Fix this sprint | +| `medium` | Correctness usually survives but output is fragile or wasteful | Plan for next cycle | +| `low` | Mostly cosmetic or maintainability issues | Backlog | + +## Output Format + +Present findings to the user in this order: + +1. **Severity-ranked findings** (most critical first) +2. **Architecture diagnosis** (which layer corrupted what, and why) +3. **Ordered fix plan** (code-first, not prompt-first) + +Do not lead with compliments or summaries. If the system is broken, say so directly. + +## Quick Diagnostic Questions + +When auditing an agent system, answer these: + +| # | Question | If Yes → | +|---|----------|----------| +| 1 | Can the model skip a required tool and still answer? | Tool not code-gated | +| 2 | Does old conversation content appear in new turns? | Memory contamination | +| 3 | Is the same info in system prompt AND memory AND history? | Context duplication | +| 4 | Does the platform run a second LLM pass before delivery? | Hidden repair loop | +| 5 | Does the output differ between internal generation and user delivery? | Rendering corruption | +| 6 | Are "must use tool X" rules only in prompt text? | Tool discipline failure | +| 7 | Can the agent's own monologue become persistent memory? | Memory poisoning | + +## Anti-Patterns to Avoid + +- Avoid blaming the model before falsifying wrapper-layer regressions. +- Avoid blaming memory without showing the contamination path. +- Do not let a clean current state erase a dirty historical incident. +- Do not treat markdown prose as a trustworthy internal protocol. +- Do not accept "must use tool" in prompt text when code never enforces it. +- Keep findings direct, evidence-backed, and severity-ranked. + +## Report Schema + +Audits should produce structured reports following this shape: + +```json +{ + "schema_version": "ecc.agent-architecture-audit.report.v1", + "executive_verdict": { + "overall_health": "high_risk", + "primary_failure_mode": "string", + "most_urgent_fix": "string" + }, + "scope": { + "target_name": "string", + "model_stack": ["string"], + "layers_to_audit": ["string"] + }, + "findings": [ + { + "severity": "critical|high|medium|low", + "title": "string", + "mechanism": "string", + "source_layer": "string", + "root_cause": "string", + "evidence_refs": ["file:line"], + "confidence": 0.0, + "recommended_fix": "string" + } + ], + "ordered_fix_plan": [ + { "order": 1, "goal": "string", "why_now": "string", "expected_effect": "string" } + ] +} +``` + +## Related Skills + +- `agent-introspection-debugging` — Debug agent runtime failures (loops, timeouts, state errors) +- `agent-eval` — Benchmark agent performance head-to-head +- `security-review` — Security audit for code and configuration +- `autonomous-agent-harness` — Set up autonomous agent operations +- `agent-harness-construction` — Build agent harnesses from scratch diff --git a/skills/agentic-os/SKILL.md b/skills/agentic-os/SKILL.md new file mode 100644 index 00000000..d8a870de --- /dev/null +++ b/skills/agentic-os/SKILL.md @@ -0,0 +1,387 @@ +--- +name: agentic-os +description: Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. +origin: ECC +--- + +# Agentic OS + +Treat Claude Code as a persistent runtime / operating system rather than a chat session. This skill codifies the architecture used by production agentic setups: a kernel config that routes tasks to specialist agents, persistent file-based memory, scheduled automation, and a JSON/markdown data layer. + +## When to Activate + +- Building a multi-agent workflow inside Claude Code +- Setting up persistent Claude Code automation that survives session restarts +- Creating a "personal OS" or "agentic OS" for recurring tasks +- User says "agentic OS", "personal OS", "multi-agent", "agent coordinator", "persistent agent" +- Structuring long-running projects where context must survive across sessions + +## Architecture Overview + +The Agentic OS has four layers. Each layer is a directory in your project root. + +``` +project-root/ +├── CLAUDE.md # Kernel: identity, routing rules, agent registry +├── agents/ # Specialist agent definitions (markdown prompts) +├── .claude/commands/ # Slash commands: user-facing CLI +├── scripts/ # Daemon scripts: scheduled or event-driven tasks +└── data/ # State: JSON/markdown filesystem, no external DB +``` + +### Layer Responsibilities + +| Layer | Purpose | Persistence | +|---|---|---| +| Kernel (`CLAUDE.md`) | Identity, routing, model policies, agent registry | Git-tracked | +| Agents (`agents/`) | Specialist identities with scoped tools and memory | Git-tracked | +| Commands (`.claude/commands/`) | User-facing slash commands (`/daily-sync`, `/outreach`) | Git-tracked | +| Scripts (`scripts/`) | Python/JS daemons triggered by cron or webhooks | Git-tracked | +| State (`data/`) | Append-only logs, project state, decision records | Git-ignored or tracked | + +## The Kernel + +`CLAUDE.md` is the kernel. It acts as the COO / orchestrator. Claude reads it at session start and uses it to route work. + +### Kernel Structure + +```markdown +# CLAUDE.md - Agentic OS Kernel + +## Identity +You are the COO of [project-name]. You route tasks to specialist agents. +You never write code directly. You delegate to the right agent and synthesize results. + +## Agent Registry + +| Agent | Role | Trigger | +|---|---|---| +| @dev | Code, architecture, debugging | User says "build", "fix", "refactor" | +| @writer | Documentation, content, emails | User says "write", "draft", "blog" | +| @researcher | Research, analysis, fact-checking | User says "research", "analyze", "compare" | +| @ops | DevOps, deployment, infrastructure | User says "deploy", "CI", "server" | + +## Routing Rules +1. Parse the user request for intent keywords +2. Match to the Agent Registry trigger column +3. Load the corresponding agent file from `agents/.md` +4. Hand off execution with full context +5. Synthesize and present the result back to the user + +## Model Policies +- Default model: use the repository or harness default. +- @dev tasks: prefer a higher-reasoning model for complex architecture. +- @researcher tasks: use the configured research-capable model and approved search tools. +- Cost ceiling: warn before exceeding the project's configured spend threshold. +``` + +### Key Principle + +The kernel should be **small and declarative**. Routing logic lives in plain markdown tables, not code. This makes the system inspectable and editable without debugging. + +## Specialist Agents + +Each agent is a standalone markdown file in `agents/`. Claude loads the relevant agent file when routing a task. + +### Agent Definition Format + +```markdown +# @dev - Software Engineer + +## Identity +You are a senior software engineer. You write clean, tested, production-grade code. +You prefer simple solutions. You ask clarifying questions when requirements are ambiguous. + +## Memory Scope +- Read `data/projects/.md` for context +- Read `data/decisions/` for architectural decisions +- Append execution logs to `data/logs/-@dev.md` + +## Tool Access +- Full filesystem access within project root +- Git operations (status, diff, commit, branch) +- Test runner access +- MCP servers as configured in `.claude/mcp.json` + +## Constraints +- Always write tests for new features +- Never commit directly to `main`; use feature branches +- Prefer editing existing files over creating new ones +- Keep functions under 50 lines when possible +``` + +### Multi-Agent Collaboration Pattern + +When a task spans multiple agents, the kernel runs them sequentially or in parallel: + +``` +User: "Build a landing page and write the launch blog post" + +Kernel routing: +1. @dev - "Build a landing page with [requirements]" +2. @writer - "Write a launch blog post for [product] using the landing page copy" +3. Kernel synthesizes both outputs into a unified response +``` + +For parallel execution, use Claude Code's background task capability or shell scripts that invoke Claude Code with specific agent contexts. + +## Commands and Daily Workflows + +Slash commands are markdown files in `.claude/commands/`. They define reusable workflows. + +### Command Structure + +```markdown +# /daily-sync + +Run the morning briefing: + +1. Read `data/logs/last-sync.md` for context +2. Check project status: `git status`, pending PRs, CI health +3. Review `data/inbox/` for new tasks or decisions needed +4. Generate a summary of blockers, priorities, and next actions +5. Append the briefing to `data/logs/daily/.md` +``` + +### Standard Command Set + +| Command | Purpose | +|---|---| +| `/daily-sync` | Morning briefing: status, blockers, priorities | +| `/outreach` | Run outreach workflow (email, LinkedIn, etc.) | +| `/research ` | Deep research with citation tracking | +| `/apply-jobs` | Tailor resume + cover letter for a target role | +| `/analytics` | Pull metrics from Stripe, GitHub, or custom sources | +| `/interview-prep` | Generate flashcards or mock interview questions | +| `/decision ` | Log a decision with pros/cons and chosen path | + +### Activating Commands + +Place command files in `.claude/commands/.md`. Claude Code auto-discovers them. Users invoke them with `/`. + +## Persistent Memory + +Memory is file-based. No vector DB, no Redis, no PostgreSQL. JSON and markdown files in `data/` are the database. + +### Memory Directory Structure + +``` +data/ +├── daily-logs/ # Append-only daily activity logs +├── projects/ # Per-project context files +├── decisions/ # Architectural and business decisions (ADR format) +├── inbox/ # New tasks or ideas awaiting triage +├── contacts/ # People, companies, relationship notes +└── templates/ # Reusable prompts and formats +``` + +### Daily Log Format + +```markdown +# 2026-04-22 - Daily Log + +## Sessions +- 09:00 - Session 1: Refactored auth module (@dev) +- 11:30 - Session 2: Drafted investor update (@writer) + +## Decisions +- Switched from JWT to session cookies (see `data/decisions/2026-04-22-auth.md`) + +## Blockers +- Waiting on API key from vendor (follow up 2026-04-24) + +## Next Actions +- [ ] Merge auth refactor PR +- [ ] Send investor update for review +``` + +### Auto-Reflection Pattern + +At the end of each session, the kernel appends a reflection: + +```markdown +## Reflection - Session 3 +- What worked: Parallel agent execution saved 20 minutes +- What didn't: @researcher hit a paywalled source, need better source ranking +- What to change: Add `source-tier` field to research notes (A/B/C credibility) +``` + +This creates a feedback loop that improves the system over time without code changes. + +## Scheduled Automation + +Agentic OS tasks run on a schedule using external cron, not Claude Code's built-in cron (which dies when the session ends). + +### macOS: LaunchAgent + +```xml + + + + + + Label + com.agentic.daily-sync + ProgramArguments + + /claude + --cwd + /path/to/project + --command + /daily-sync + + StartCalendarInterval + + Hour + 8 + Minute + 0 + + StandardOutPath + /tmp/agentic-daily-sync.log + + +``` + +### Linux: systemd Timer + +```ini +# ~/.config/systemd/user/agentic-daily-sync.service +[Unit] +Description=Agentic OS Daily Sync + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/claude --cwd /path/to/project --command /daily-sync +``` + +```ini +# ~/.config/systemd/user/agentic-daily-sync.timer +[Unit] +Description=Run daily sync every morning + +[Timer] +OnCalendar=*-*-* 8:00:00 +Persistent=true + +[Install] +WantedBy=timers.target +``` + +### Cross-Platform: pm2 + +```bash +# ecosystem.config.js +module.exports = { + apps: [{ + name: 'agentic-daily-sync', + script: 'claude', + args: '--cwd /path/to/project --command /daily-sync', + cron_restart: '0 8 * * *', + autorestart: false + }] +}; +``` + +## Data Layer + +The data layer is your filesystem. Use JSON for structured data and markdown for narrative content. + +### JSON for Structured State + +```json +// data/projects/website-v2.json +{ + "name": "Website v2", + "status": "in-progress", + "milestone": "beta-launch", + "agents_involved": ["@dev", "@writer"], + "files": { + "spec": "docs/website-v2-spec.md", + "design": "designs/website-v2.fig" + }, + "metrics": { + "commits": 47, + "last_session": "2026-04-22T11:30:00Z" + } +} +``` + +### Markdown for Narrative + +Use markdown for anything a human reads: decisions, logs, research notes, contact records. + +### Schema Evolution + +Never rename existing fields. Add new fields and mark old ones deprecated: + +```json +{ + "name": "Website v2", + "status": "in-progress", + "milestone": "beta-launch", + "_deprecated_priority": "high", + "priority_v2": { "level": "high", "rationale": "Blocks investor demo" } +} +``` + +This keeps historical data readable without migration scripts. + +## Anti-Patterns + +### Monolithic Single Agent + +```markdown +# BAD - One agent does everything +You are a full-stack developer, writer, researcher, and DevOps engineer. +``` + +Split into specialist agents. The kernel handles routing. + +### Stateless Sessions + +```markdown +# BAD - No memory between sessions +Starting fresh every time Claude Code opens. +``` + +Always read `data/` at session start and write back at session end. + +### Hardcoded Credentials + +```markdown +# BAD - API keys in agent files or CLAUDE.md +Your OpenAI API key is sk-xxxxxxxx +``` + +Use environment variables or a `.env` file loaded by scripts. Agents reference `process.env.API_KEY`. + +### External Database for Simple State + +```markdown +# BAD - PostgreSQL for a solo user's agentic OS +``` + +Use JSON/markdown files until you have multiple concurrent users or GBs of data. + +### Over-Engineered Routing + +```markdown +# BAD - Routing logic in code instead of markdown tables +if (intent.includes('deploy')) { agent = opsAgent; } +``` + +Keep routing declarative in `CLAUDE.md` markdown tables. It is inspectable, editable, and debuggable. + +## Best Practices + +- [ ] `CLAUDE.md` is under 200 lines and fits in context window +- [ ] Each agent file is under 100 lines and focused on one domain +- [ ] `data/` is git-ignored for sensitive logs, git-tracked for decisions and specs +- [ ] Commands use imperative names: `/daily-sync`, not `/run-daily-sync` +- [ ] Logs are append-only; never edit past daily logs +- [ ] Every agent has a `Memory Scope` section defining what files it reads +- [ ] Reflections are written at the end of every session +- [ ] Scheduled tasks use external cron (LaunchAgent, systemd, pm2), not Claude Code's session cron +- [ ] Cost tracking: log API spend per session in `data/logs/-costs.json` +- [ ] One project = one Agentic OS. Do not share a single `CLAUDE.md` across unrelated projects. diff --git a/skills/error-handling/SKILL.md b/skills/error-handling/SKILL.md new file mode 100644 index 00000000..111f18cf --- /dev/null +++ b/skills/error-handling/SKILL.md @@ -0,0 +1,376 @@ +--- +name: error-handling +description: Patterns for robust error handling across TypeScript, Python, and Go. Covers typed errors, error boundaries, retries, circuit breakers, and user-facing error messages. +origin: ECC +--- + +# Error Handling Patterns + +Consistent, robust error handling patterns for production applications. + +## When to Activate + +- Designing error types or exception hierarchies for a new module or service +- Adding retry logic or circuit breakers for unreliable external dependencies +- Reviewing API endpoints for missing error handling +- Implementing user-facing error messages and feedback +- Debugging cascading failures or silent error swallowing + +## Core Principles + +1. **Fail fast and loudly** — surface errors at the boundary where they occur; don't bury them +2. **Typed errors over string messages** — errors are first-class values with structure +3. **User messages ≠ developer messages** — show friendly text to users, log full context server-side +4. **Never swallow errors silently** — every `catch` block must either handle, re-throw, or log +5. **Errors are part of your API contract** — document every error code a client may receive + +## TypeScript / JavaScript + +### Typed Error Classes + +```typescript +// Define an error hierarchy for your domain +export class AppError extends Error { + constructor( + message: string, + public readonly code: string, + public readonly statusCode: number = 500, + public readonly details?: unknown, + ) { + super(message) + this.name = this.constructor.name + // Maintain correct prototype chain in transpiled ES5 JavaScript. + // Required for `instanceof` checks (e.g., `error instanceof NotFoundError`) + // to work correctly when extending the built-in Error class. + Object.setPrototypeOf(this, new.target.prototype) + } +} + +export class NotFoundError extends AppError { + constructor(resource: string, id: string) { + super(`${resource} not found: ${id}`, 'NOT_FOUND', 404) + } +} + +export class ValidationError extends AppError { + constructor(message: string, details: { field: string; message: string }[]) { + super(message, 'VALIDATION_ERROR', 422, details) + } +} + +export class UnauthorizedError extends AppError { + constructor(reason = 'Authentication required') { + super(reason, 'UNAUTHORIZED', 401) + } +} + +export class RateLimitError extends AppError { + constructor(public readonly retryAfterMs: number) { + super('Rate limit exceeded', 'RATE_LIMITED', 429) + } +} +``` + +### Result Pattern (no-throw style) + +For operations where failure is expected and common (parsing, external calls): + +```typescript +type Result = + | { ok: true; value: T } + | { ok: false; error: E } + +function ok(value: T): Result { + return { ok: true, value } +} + +function err(error: E): Result { + return { ok: false, error } +} + +// Usage +async function fetchUser(id: string): Promise> { + try { + const user = await db.users.findUnique({ where: { id } }) + if (!user) return err(new NotFoundError('User', id)) + return ok(user) + } catch (e) { + return err(new AppError('Database error', 'DB_ERROR')) + } +} + +const result = await fetchUser('abc-123') +if (!result.ok) { + // TypeScript knows result.error here + logger.error('Failed to fetch user', { error: result.error }) + return +} +// TypeScript knows result.value here +console.log(result.value.email) +``` + +### API Error Handler (Next.js / Express) + +```typescript +import { NextRequest, NextResponse } from 'next/server' + +function handleApiError(error: unknown): NextResponse { + // Known application error + if (error instanceof AppError) { + return NextResponse.json( + { + error: { + code: error.code, + message: error.message, + ...(error.details ? { details: error.details } : {}), + }, + }, + { status: error.statusCode }, + ) + } + + // Zod validation error + if (error instanceof z.ZodError) { + return NextResponse.json( + { + error: { + code: 'VALIDATION_ERROR', + message: 'Request validation failed', + details: error.issues.map(i => ({ + field: i.path.join('.'), + message: i.message, + })), + }, + }, + { status: 422 }, + ) + } + + // Unexpected error — log details, return generic message + console.error('Unexpected error:', error) + return NextResponse.json( + { error: { code: 'INTERNAL_ERROR', message: 'An unexpected error occurred' } }, + { status: 500 }, + ) +} + +export async function POST(req: NextRequest) { + try { + // ... handler logic + } catch (error) { + return handleApiError(error) + } +} +``` + +### React Error Boundary + +```typescript +import { Component, ErrorInfo, ReactNode } from 'react' + +interface Props { + fallback: ReactNode + onError?: (error: Error, info: ErrorInfo) => void + children: ReactNode +} + +interface State { + hasError: boolean + error: Error | null +} + +export class ErrorBoundary extends Component { + state: State = { hasError: false, error: null } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error } + } + + componentDidCatch(error: Error, info: ErrorInfo) { + this.props.onError?.(error, info) + console.error('Unhandled React error:', error, info) + } + + render() { + if (this.state.hasError) return this.props.fallback + return this.props.children + } +} + +// Usage +Something went wrong. Please refresh.

}> + +
+``` + +## Python + +### Custom Exception Hierarchy + +```python +class AppError(Exception): + """Base application error.""" + def __init__(self, message: str, code: str, status_code: int = 500): + super().__init__(message) + self.code = code + self.status_code = status_code + +class NotFoundError(AppError): + def __init__(self, resource: str, id: str): + super().__init__(f"{resource} not found: {id}", "NOT_FOUND", 404) + +class ValidationError(AppError): + def __init__(self, message: str, details: list[dict] | None = None): + super().__init__(message, "VALIDATION_ERROR", 422) + self.details = details or [] +``` + +### FastAPI Global Exception Handler + +```python +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse + +app = FastAPI() + +@app.exception_handler(AppError) +async def app_error_handler(request: Request, exc: AppError) -> JSONResponse: + return JSONResponse( + status_code=exc.status_code, + content={"error": {"code": exc.code, "message": str(exc)}}, + ) + +@app.exception_handler(Exception) +async def generic_error_handler(request: Request, exc: Exception) -> JSONResponse: + # Log full details, return generic message + logger.exception("Unexpected error", exc_info=exc) + return JSONResponse( + status_code=500, + content={"error": {"code": "INTERNAL_ERROR", "message": "An unexpected error occurred"}}, + ) +``` + +## Go + +### Sentinel Errors and Error Wrapping + +```go +package domain + +import "errors" + +// Sentinel errors for type-checking +var ( + ErrNotFound = errors.New("not found") + ErrUnauthorized = errors.New("unauthorized") + ErrConflict = errors.New("conflict") +) + +// Wrap errors with context — never lose the original +func (r *UserRepository) FindByID(ctx context.Context, id string) (*User, error) { + user, err := r.db.QueryRow(ctx, "SELECT * FROM users WHERE id = $1", id) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("user %s: %w", id, ErrNotFound) + } + if err != nil { + return nil, fmt.Errorf("querying user %s: %w", id, err) + } + return user, nil +} + +// At the handler level, unwrap to determine response +func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) { + user, err := h.service.GetUser(r.Context(), chi.URLParam(r, "id")) + if err != nil { + switch { + case errors.Is(err, domain.ErrNotFound): + writeError(w, http.StatusNotFound, "not_found", err.Error()) + case errors.Is(err, domain.ErrUnauthorized): + writeError(w, http.StatusForbidden, "forbidden", "Access denied") + default: + slog.Error("unexpected error", "err", err) + writeError(w, http.StatusInternalServerError, "internal_error", "An unexpected error occurred") + } + return + } + writeJSON(w, http.StatusOK, user) +} +``` + +## Retry with Exponential Backoff + +```typescript +interface RetryOptions { + maxAttempts?: number + baseDelayMs?: number + maxDelayMs?: number + retryIf?: (error: unknown) => boolean +} + +async function withRetry( + fn: () => Promise, + options: RetryOptions = {}, +): Promise { + const { + maxAttempts = 3, + baseDelayMs = 500, + maxDelayMs = 10_000, + retryIf = () => true, + } = options + + let lastError: unknown + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await fn() + } catch (error) { + lastError = error + if (attempt === maxAttempts || !retryIf(error)) throw error + + const jitter = Math.random() * baseDelayMs + const delay = Math.min(baseDelayMs * 2 ** (attempt - 1) + jitter, maxDelayMs) + await new Promise(resolve => setTimeout(resolve, delay)) + } + } + + throw lastError +} + +// Usage: retry transient network errors, not 4xx +const data = await withRetry(() => fetch('/api/data').then(r => r.json()), { + maxAttempts: 3, + retryIf: (error) => !(error instanceof AppError && error.statusCode < 500), +}) +``` + +## User-Facing Error Messages + +Map error codes to human-readable messages. Keep technical details out of user-visible text. + +```typescript +const USER_ERROR_MESSAGES: Record = { + NOT_FOUND: 'The requested item could not be found.', + UNAUTHORIZED: 'Please sign in to continue.', + FORBIDDEN: "You don't have permission to do that.", + VALIDATION_ERROR: 'Please check your input and try again.', + RATE_LIMITED: 'Too many requests. Please wait a moment and try again.', + INTERNAL_ERROR: 'Something went wrong on our end. Please try again later.', +} + +export function getUserMessage(code: string): string { + return USER_ERROR_MESSAGES[code] ?? USER_ERROR_MESSAGES.INTERNAL_ERROR +} +``` + +## Error Handling Checklist + +Before merging any code that touches error handling: + +- [ ] Every `catch` block handles, re-throws, or logs — no silent swallowing +- [ ] API errors follow the standard envelope `{ error: { code, message } }` +- [ ] User-facing messages contain no stack traces or internal details +- [ ] Full error context is logged server-side +- [ ] Custom error classes extend a base `AppError` with a `code` field +- [ ] Async functions surface errors to callers — no fire-and-forget without fallback +- [ ] Retry logic only retries retriable errors (not 4xx client errors) +- [ ] React components are wrapped in `ErrorBoundary` for rendering errors diff --git a/skills/motion-ui/SKILL.md b/skills/motion-ui/SKILL.md new file mode 100644 index 00000000..7b9bf0c5 --- /dev/null +++ b/skills/motion-ui/SKILL.md @@ -0,0 +1,575 @@ +--- +name: motion-ui +description: "Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns." +origin: ECC +--- + +# Motion System v4.2 + +Production-ready UI motion system for React / Next.js. + +Focused on **performance, accessibility, and usability** — not decoration. + +## When to Use + +Use this motion system when motion: + +* Guides attention (e.g., onboarding, key actions) +* Communicates state (loading, success, error, transitions) +* Preserves spatial continuity (layout changes, navigation) + +### Appropriate Scenarios + +* Interactive components (buttons, modals, menus) +* State transitions (loading → loaded, open → closed) +* Navigation and layout continuity (shared elements, crossfade) + +### Considerations + +* **Accessibility**: Always support reduced motion +* **Device adaptation**: Adjust for low-end devices +* **Performance trade-offs**: Prefer responsiveness over visual smoothness + +### Avoid Using Motion When + +* It is purely decorative +* It reduces usability or clarity +* It impacts performance negatively + +--- + +## How It Works + +### Core Principle + +Motion must: + +* Guide attention +* Communicate state +* Preserve spatial continuity + +If it does none → remove it. + +--- + +### Installation + +```bash +npm install motion +``` + +--- + +### Version + +* `motion/react` - default for current Motion for React projects (package: `motion`) +* `framer-motion` - legacy import path for projects that still depend on Framer Motion + +**Do not mix.** Mixing causes conflicting internal schedulers and broken `AnimatePresence` contexts — components from one package will not coordinate exit animations with components from the other. + +To check which version your project uses: + +```bash +cat package.json | grep -E '"motion"|"framer-motion"' +``` + +Always import from one source consistently: + +```ts +// Correct (modern) +import { motion, AnimatePresence } from "motion/react" + +// Correct (legacy) +import { motion, AnimatePresence } from "framer-motion" + +// Never mix both in the same project +``` + +--- + +### Motion Tokens + +```ts +// motionTokens.ts +export const motionTokens = { + duration: { + fast: 0.18, + normal: 0.35, + slow: 0.6 + }, + // Use these as the `ease` value inside a `transition` object: + // transition={{ duration: motionTokens.duration.normal, ease: motionTokens.easing.smooth }} + easing: { + smooth: [0.22, 1, 0.36, 1] as [number, number, number, number], + sharp: [0.4, 0, 0.2, 1] as [number, number, number, number] + }, + distance: { + sm: 8, + md: 16, + lg: 24 + } +} +``` + +Usage example: + +```tsx +import { motionTokens } from "@/lib/motionTokens" + + +``` + +--- + +### Performance Rules + +**Safe** + +* transform +* opacity + +**Avoid** + +* width / height +* top / left + +Rule: responsiveness > smoothness + +--- + +### Device Adaptation + +The heuristic combines CPU core count **and** available memory for a more reliable signal. `deviceMemory` is available on Chrome/Android; the fallback covers Safari and Firefox. + +```ts +const isLowEnd = + typeof navigator !== "undefined" && ( + // Low memory (Chrome/Android only; undefined elsewhere → treat as capable) + (navigator.deviceMemory !== undefined && navigator.deviceMemory <= 2) || + // Few cores AND no memory API (covers Safari/Firefox on weak hardware) + (navigator.deviceMemory === undefined && navigator.hardwareConcurrency <= 4) + ) + +const duration = isLowEnd ? 0.2 : 0.4 +``` + +--- + +### Accessibility + +#### JS (useReducedMotion) + +```tsx +import { motion, useReducedMotion } from "motion/react" + +export function FadeIn() { + const reduce = useReducedMotion() + + return ( + + ) +} +``` + +#### CSS + +```css +@media (prefers-reduced-motion: reduce) { + .motion-safe-transition { + transition: opacity 0.2s; + } + + .motion-reduce-transform { + transform: none !important; + } +} +``` + +#### Tailwind + +```html +
+``` + +--- + +### Architecture & Patterns + +#### Core Patterns + +| Scenario | Pattern | +|---|---| +| Hover feedback | `whileHover` | +| Tap / press feedback | `whileTap` | +| Reveal on scroll | `whileInView` | +| Scroll-linked value | `useScroll` + `useTransform` | +| Conditional mount/unmount | `AnimatePresence` | +| Small layout shifts (single element, < ~300px change) | `layout` prop | +| Large layout shifts or full-page reflows | Avoid `layout`; use CSS transitions or page-level routing instead | +| Complex, imperative sequences | `useAnimate` | + +> **Why avoid `layout` on large containers?** Framer's layout animation uses `transform` to reconcile positions, but on elements that span the full viewport or trigger deep reflow, the measurement cost causes visible jank and CLS. Prefer CSS Grid/Flexbox transitions or coordinate with `layoutId` on specific child elements only. + +#### Layout & Transitions + +* Shared element transitions → `layoutId` (must be unique per mounted instance) +* Enter / exit transitions → `AnimatePresence` (see `mode` guidance below) + +#### AnimatePresence `mode` + +Always specify `mode` explicitly — the default (`"sync"`) runs enter and exit simultaneously, which causes visual overlap in most UI patterns. + +| `mode` | When to use | +|---|---| +| `"wait"` | Exit completes before enter starts. Use for **modals, toasts, page transitions**. | +| `"sync"` (default) | Enter and exit overlap. Use only when overlap is intentional (e.g., crossfade carousels). | +| `"popLayout"` | Exiting element is popped out of flow immediately; remaining items animate to fill. Use for **lists, tabs, dismissible cards**. | + +```tsx +// Modal — always use "wait" + + {open && } + + +// Dismissible list item — use "popLayout" + + {items.map(item => )} + +``` + +--- + +### Advanced Patterns (Concepts) + +* Parallax (scroll-linked transforms) +* Scroll storytelling (sticky sections) +* 3D tilt (pointer-based transforms) +* Crossfade (shared `layoutId`) +* Progressive reveal (clip-path) +* Skeleton loading (looped opacity) +* Micro-interactions (hover/tap feedback) +* Spring system (physics-based motion) + +--- + +### Modal Essentials + +* Focus trap +* Escape close +* Scroll lock +* ARIA roles +* Use `AnimatePresence mode="wait"` so exit animation completes before the next modal enters + +#### Full Example + +```tsx +import React, { useEffect, useRef, useState } from "react" +import { motion, AnimatePresence } from "motion/react" + +function useFocusTrap(ref: React.RefObject, active: boolean) { + useEffect(() => { + if (!active || !ref.current) return + const el = ref.current + const focusable = el.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ) + const first = focusable[0] + const last = focusable[focusable.length - 1] + + function handleKey(e: KeyboardEvent) { + if (e.key !== "Tab") return + if (e.shiftKey && document.activeElement === first) { + e.preventDefault() + last?.focus() + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault() + first?.focus() + } + } + + el.addEventListener("keydown", handleKey) + first?.focus() + return () => el.removeEventListener("keydown", handleKey) + }, [active, ref]) +} + +function useScrollLock(active: boolean) { + useEffect(() => { + if (!active) return + const prev = document.body.style.overflow + document.body.style.overflow = "hidden" + return () => { document.body.style.overflow = prev } + }, [active]) +} + +function Modal({ open, closeModal }: { open: boolean; closeModal: () => void }) { + const ref = useRef(null) + + useFocusTrap(ref, open) + useScrollLock(open) + + useEffect(() => { + function onKey(e: KeyboardEvent) { + if (e.key === "Escape") closeModal() + } + if (open) window.addEventListener("keydown", onKey) + return () => window.removeEventListener("keydown", onKey) + }, [open, closeModal]) + + return ( + // mode="wait" ensures exit animation finishes before any new modal enters + + {open && ( + + + + + + + )} + + ) +} + +export function Example() { + const [open, setOpen] = useState(false) + + return ( + <> + + setOpen(false)} /> + + ) +} +``` + +--- + +### SSR Safety + +* Match initial states between server and client renders +* Avoid implicit animation origins (always set `initial` explicitly) +* Wrap motion components in `"use client"` in Next.js App Router + +--- + +### Debugging + +Check: + +* Wrong import (mixing `motion/react` and `framer-motion`) +* Missing `"use client"` directive in Next.js App Router +* Missing `key` prop on `AnimatePresence` children +* Hydration mismatch (initial state differs between SSR and client) +* `layout` prop misuse on large containers causing reflow jank +* State-driven animation not triggering (check dependency arrays) + +--- + +### QA + +* No CLS +* Keyboard works +* Focus trapped in modals +* ARIA roles correct (`role="dialog"`, `aria-modal="true"`) +* Reduced motion respected (`useReducedMotion` + CSS media query) +* No hydration warnings in Next.js +* Animations stop cleanly on unmount (no memory leaks) +* `AnimatePresence mode` set explicitly on all usage sites + +--- + +### Anti-Patterns + +* Animating layout properties (`width`, `height`, `top`, `left`) +* Infinite animations without purpose (always ask: what state does this communicate?) +* Over-staggering lists (keep `staggerChildren` ≤ 0.1s; beyond that it feels slow) +* Ignoring reduced motion preferences +* Using `layout` on large or full-viewport containers +* Omitting `mode` on `AnimatePresence` (default `"sync"` causes visual overlap) +* Using motion purely for decoration + +--- + +### Philosophy + +Motion is interaction design. + +--- + +### Final Rule + +> If motion does not improve UX → remove it. + +--- + +## Examples + +### Button Interaction + +```tsx +import { motion } from "motion/react" + +export function Button() { + return ( + + Click me + + ) +} +``` + +--- + +### Reduced Motion Example + +```tsx +import { motion, useReducedMotion } from "motion/react" + +export function FadeIn() { + const reduce = useReducedMotion() + + return ( + + ) +} +``` + +--- + +### Stagger List + +```tsx +import { motion } from "motion/react" + +const container = { + hidden: {}, + visible: { + transition: { staggerChildren: 0.08 } // keep ≤ 0.1s to avoid sluggishness + } +} + +const item = { + hidden: { opacity: 0, y: 10 }, + visible: { opacity: 1, y: 0, transition: { duration: 0.3, ease: [0.22, 1, 0.36, 1] } } +} + +export function List() { + return ( + + {[1, 2, 3].map(i => ( + Item {i} + ))} + + ) +} +``` + +--- + +### Modal with AnimatePresence + +```tsx +import { motion, AnimatePresence } from "motion/react" + +export function Modal({ open }: { open: boolean }) { + return ( + + {open && ( + + )} + + ) +} +``` + +--- + +### Scroll Parallax + +```tsx +import { useScroll, useTransform, motion } from "motion/react" + +export function Parallax() { + const { scrollYProgress } = useScroll() + const y = useTransform(scrollYProgress, [0, 1], [0, -80]) + + return +} +``` + +--- + +### Skeleton Loading + +```tsx +import { motion } from "motion/react" + +export function Skeleton() { + return ( + + ) +} +``` + +--- + +### Shared Layout (Crossfade) + +```tsx +import { motion } from "motion/react" + +// layoutId must be unique per mounted instance. +// If multiple instances can exist simultaneously, append a unique id: +// layoutId={`shared-${item.id}`} +export function Shared() { + return +} +```