mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-13 18:00:35 +08:00
docs: salvage agent and motion workflow skills
This commit is contained in:
parent
60782502d5
commit
629d4c0c61
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 |
|
||||
|
||||
@ -160,7 +160,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**完成!** 你现在可以使用 56 个代理、213 个技能和 72 个命令。
|
||||
**完成!** 你现在可以使用 56 个代理、217 个技能和 72 个命令。
|
||||
|
||||
### multi-* 命令需要额外配置
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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/ — 始终遵循的指导方针(通用 + 每种语言)
|
||||
|
||||
@ -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 条指令 |
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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/",
|
||||
|
||||
256
skills/agent-architecture-audit/SKILL.md
Normal file
256
skills/agent-architecture-audit/SKILL.md
Normal file
@ -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
|
||||
387
skills/agentic-os/SKILL.md
Normal file
387
skills/agentic-os/SKILL.md
Normal file
@ -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/<name>.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/<current-project>.md` for context
|
||||
- Read `data/decisions/` for architectural decisions
|
||||
- Append execution logs to `data/logs/<date>-@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/<date>.md`
|
||||
```
|
||||
|
||||
### Standard Command Set
|
||||
|
||||
| Command | Purpose |
|
||||
|---|---|
|
||||
| `/daily-sync` | Morning briefing: status, blockers, priorities |
|
||||
| `/outreach` | Run outreach workflow (email, LinkedIn, etc.) |
|
||||
| `/research <topic>` | 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 <topic>` | Log a decision with pros/cons and chosen path |
|
||||
|
||||
### Activating Commands
|
||||
|
||||
Place command files in `.claude/commands/<command-name>.md`. Claude Code auto-discovers them. Users invoke them with `/<command-name>`.
|
||||
|
||||
## 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
|
||||
<!-- ~/Library/LaunchAgents/com.agentic.daily-sync.plist -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ...>
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.agentic.daily-sync</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/claude</string>
|
||||
<string>--cwd</string>
|
||||
<string>/path/to/project</string>
|
||||
<string>--command</string>
|
||||
<string>/daily-sync</string>
|
||||
</array>
|
||||
<key>StartCalendarInterval</key>
|
||||
<dict>
|
||||
<key>Hour</key>
|
||||
<integer>8</integer>
|
||||
<key>Minute</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/tmp/agentic-daily-sync.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
### 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/<date>-costs.json`
|
||||
- [ ] One project = one Agentic OS. Do not share a single `CLAUDE.md` across unrelated projects.
|
||||
376
skills/error-handling/SKILL.md
Normal file
376
skills/error-handling/SKILL.md
Normal file
@ -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<T, E = AppError> =
|
||||
| { ok: true; value: T }
|
||||
| { ok: false; error: E }
|
||||
|
||||
function ok<T>(value: T): Result<T> {
|
||||
return { ok: true, value }
|
||||
}
|
||||
|
||||
function err<E>(error: E): Result<never, E> {
|
||||
return { ok: false, error }
|
||||
}
|
||||
|
||||
// Usage
|
||||
async function fetchUser(id: string): Promise<Result<User>> {
|
||||
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<Props, State> {
|
||||
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
|
||||
<ErrorBoundary fallback={<p>Something went wrong. Please refresh.</p>}>
|
||||
<MyComponent />
|
||||
</ErrorBoundary>
|
||||
```
|
||||
|
||||
## 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<T>(
|
||||
fn: () => Promise<T>,
|
||||
options: RetryOptions = {},
|
||||
): Promise<T> {
|
||||
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<string, string> = {
|
||||
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
|
||||
575
skills/motion-ui/SKILL.md
Normal file
575
skills/motion-ui/SKILL.md
Normal file
@ -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"
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: motionTokens.distance.md }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{
|
||||
duration: motionTokens.duration.normal,
|
||||
ease: motionTokens.easing.smooth
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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 (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: reduce ? 0 : 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### CSS
|
||||
|
||||
```css
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.motion-safe-transition {
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.motion-reduce-transform {
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Tailwind
|
||||
|
||||
```html
|
||||
<div class="motion-safe:animate-fade motion-reduce:opacity-100"></div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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"
|
||||
<AnimatePresence mode="wait">
|
||||
{open && <Modal key="modal" />}
|
||||
</AnimatePresence>
|
||||
|
||||
// Dismissible list item — use "popLayout"
|
||||
<AnimatePresence mode="popLayout">
|
||||
{items.map(item => <Card key={item.id} />)}
|
||||
</AnimatePresence>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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<HTMLDivElement | null>, active: boolean) {
|
||||
useEffect(() => {
|
||||
if (!active || !ref.current) return
|
||||
const el = ref.current
|
||||
const focusable = el.querySelectorAll<HTMLElement>(
|
||||
'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<HTMLDivElement>(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
|
||||
<AnimatePresence mode="wait">
|
||||
{open && (
|
||||
<motion.div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="modal-title"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="fixed inset-0 flex items-center justify-center bg-black/40"
|
||||
>
|
||||
<motion.div
|
||||
ref={ref}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0.95, opacity: 0 }}
|
||||
transition={{ duration: 0.2, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="bg-white p-6 rounded"
|
||||
>
|
||||
<h2 id="modal-title">Dialog Title</h2>
|
||||
<button onClick={closeModal}>Close</button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
||||
export function Example() {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setOpen(true)}>Open</button>
|
||||
<Modal open={open} closeModal={() => 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 (
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
transition={{ duration: 0.15, ease: [0.4, 0, 0.2, 1] }}
|
||||
>
|
||||
Click me
|
||||
</motion.button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Reduced Motion Example
|
||||
|
||||
```tsx
|
||||
import { motion, useReducedMotion } from "motion/react"
|
||||
|
||||
export function FadeIn() {
|
||||
const reduce = useReducedMotion()
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: reduce ? 0 : 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: reduce ? 0.1 : 0.35, ease: [0.22, 1, 0.36, 1] }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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 (
|
||||
<motion.ul variants={container} initial="hidden" animate="visible">
|
||||
{[1, 2, 3].map(i => (
|
||||
<motion.li key={i} variants={item}>Item {i}</motion.li>
|
||||
))}
|
||||
</motion.ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Modal with AnimatePresence
|
||||
|
||||
```tsx
|
||||
import { motion, AnimatePresence } from "motion/react"
|
||||
|
||||
export function Modal({ open }: { open: boolean }) {
|
||||
return (
|
||||
<AnimatePresence mode="wait">
|
||||
{open && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
transition={{ duration: 0.2, ease: [0.22, 1, 0.36, 1] }}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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 <motion.div style={{ y }} />
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Skeleton Loading
|
||||
|
||||
```tsx
|
||||
import { motion } from "motion/react"
|
||||
|
||||
export function Skeleton() {
|
||||
return (
|
||||
<motion.div
|
||||
className="bg-gray-200 h-6 w-full rounded"
|
||||
animate={{ opacity: [0.5, 1, 0.5] }}
|
||||
transition={{
|
||||
duration: 1.5, // comfortable pulse — was missing, caused fast flash
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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 <motion.div layoutId="shared" />
|
||||
}
|
||||
```
|
||||
Loading…
x
Reference in New Issue
Block a user