From f54846e6b3edf042e79e3bea8900b5b4d04e9c52 Mon Sep 17 00:00:00 2001 From: warfield Date: Sun, 5 Apr 2026 19:55:22 +0800 Subject: [PATCH 1/4] Add Brain Map tab: radial SVG concept graph with production context A new sixth tab that visualizes all 30 concepts across the 5 CCA-F domains as an interactive radial graph, with cross-domain connection arcs showing how concepts relate across exam scenarios. Click any concept to open a detail panel with six sections per concept: - Why it matters (production impact, 1-2 sentences) - Used in (concrete production apps) - Connects to (clickable links to related concepts across domains) - Learn more (curated Skilljar + docs links) - Watch for (common failure modes and anti-patterns) - Exam signals (distractor-trap phrases that point to this concept) Implementation notes: - Pure SVG with polar-coordinate layout (no d3, keeps zero external UI deps per project convention) - 3-ring design: root at center, 5 domain nodes sized by weight, 30 concept nodes clustered by domain with angular spread proportional to domain size - 47 cross-domain arcs rendered as quadratic Bezier curves, highlighted on hover/selection, dimmed otherwise - Brighter label colors for D1 (#e74c3c) and D5 (#a569bd) to fix WCAG AA contrast failures against the #0c0c10 background - Detail panel expands below the SVG (no modal overlay), closes on Escape - Mastery toggle persisted to localStorage with bm-{id} key format - Keyboard accessible (Tab focus, Enter opens panel, Esc closes) - Renders cleanly at both mobile (375px) and desktop widths --- README.md | 8 +- src/App.jsx | 763 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 768 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 92c3d63..8633191 100644 --- a/README.md +++ b/README.md @@ -15,17 +15,19 @@ A week-by-week battle plan covering all 5 exam domains, organized into 4 phases: ### Features -- **5 tabs**: Weekly Plan, Concepts (31 nodes), Projects, Decision Rules (11 rules), Cheat Sheet +- **6 tabs**: Weekly Plan, **Brain Map**, Concepts (30 nodes), Projects, Decision Rules (11 rules), Cheat Sheet - **Daily KPIs** with measurable targets for each study day - **Daily closure assessments** linking to specific quiz engine modes - **Progress tracking** with localStorage-persisted checkboxes on tasks and concepts - **Completion percentages** per tab displayed in the header -### The 5 tabs +### The 6 tabs **Weekly Plan** -- Expand any week, then individual days. Each day lists what to study, a KPI, and a closure quiz reference. Colored borders indicate the domain. -**Concepts** -- 31 testable concepts organized by domain with weight percentages. Your self-test: explain any leaf node from memory. +**Brain Map** -- Radial SVG view of all 30 concepts across 5 domains with cross-domain connection arcs. Click any concept to see why it matters in production, related concepts across domains, curated learning resources, common failure modes, and exam distractor-trap phrases. Designed for scenario questions that span multiple domains. + +**Concepts** -- 30 testable concepts organized by domain with weight percentages. Your self-test: explain any leaf node from memory. **Projects** -- 4 production-style apps mapped to exam scenarios. diff --git a/src/App.jsx b/src/App.jsx index 03822c3..8632dc7 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -359,6 +359,361 @@ const CONCEPT_TREE = [ }, ]; +/* ─── ENRICHED_CONCEPTS: brain-map metadata (why/production/links/failures/signals) ─── */ +const ENRICHED_CONCEPTS = [ + // ═══════════════════════════════════════════════════════════ + // D1 — Agentic Architecture (7 concepts, 27% weight) + // ═══════════════════════════════════════════════════════════ + { + id: "d1-agentic-loop", name: "Agentic Loop", domain: "D1", task: "1.1", + whyItMatters: "The loop is Claude's execution model. Check text content instead of stop_reason and your agent stops mid-workflow — claims don't get approved, support tickets sit half-resolved, extraction pipelines go silent. Tested heavily because it breaks quietly in prod.", + productionExamples: ["Customer support ticket resolution", "Insurance claims processing (retrieve → assess → approve)", "Document extraction pipelines"], + relatedConcepts: ["d2-structured-errors", "d4-structured-output", "d5-error-propagation"], + resources: [ + { label: "Anthropic Academy: Building with the Claude API", url: "https://anthropic.skilljar.com", type: "course" }, + { label: "Agent SDK Overview", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + ], + failureModes: ["Checking text content for termination → stops before tools finish", "NL parsing of tool results → brittle, unreliable routing", "Iteration cap without monitoring → silent task truncation"], + examSignals: ["agent stops mid-workflow", "reliability issue", "loop termination", "not calling expected tool"], + }, + { + id: "d1-multi-agent", name: "Multi-Agent Orchestration", domain: "D1", task: "1.2", + whyItMatters: "Complex work exceeds one agent's context and tool budget. Hub-and-spoke — a coordinator delegating to specialized subagents — scales to research, investigation, and cross-source synthesis. Overly narrow decomposition recreates the problem: fragmented context, no one can synthesize.", + productionExamples: ["Research systems querying multiple sources", "Codebase-wide refactors (per-file + cross-file passes)", "Compliance sweeps across heterogeneous datastores"], + relatedConcepts: ["d1-subagent-mgmt", "d1-task-decomp", "d5-context-mgmt"], + resources: [ + { label: "Agent SDK: Subagents", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + { label: "Claude Code Full Stack Guide", url: "https://alexop.dev/posts/understanding-claude-code-full-stack/", type: "blog" }, + ], + failureModes: ["Too-narrow subagents → fragmented context, no synthesis possible", "Coordinator doing execution work instead of delegating", "Bypassing the coordinator for inter-subagent chat"], + examSignals: ["hub-and-spoke", "coordinator", "subagent selection", "complex investigation"], + }, + { + id: "d1-subagent-mgmt", name: "Subagent Management", domain: "D1", task: "1.3", + whyItMatters: "Subagents don't inherit parent context. If you don't pass complete findings explicitly in the subagent's prompt, the synthesis step sees half a story. This is the #1 multi-agent bug: the synthesis agent has no idea what was already found.", + productionExamples: ["Parallel codebase investigations", "Cross-department research (legal + finance + ops)", "Per-file review + cross-file integration review"], + relatedConcepts: ["d1-multi-agent", "d5-context-mgmt", "d5-provenance"], + resources: [ + { label: "Agent SDK: Task tool", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + ], + failureModes: ["Assuming context inheritance → synthesis agent lacks info", "Sequential Task calls when they could run in parallel", "Over-broad allowedTools → subagent drifts out of scope"], + examSignals: ["subagent", "context passing", "synthesis agent lacks info", "parallel Task"], + }, + { + id: "d1-workflow-enforce", name: "Workflow Enforcement", domain: "D1", task: "1.4", + whyItMatters: "For financial ops, identity verification, or policy rules, prompts like 'always check X first' have non-zero failure rates. Hooks and programmatic prerequisites enforce these deterministically. The exam loves this distractor: 'occasionally fails' means you need a hook, not better wording.", + productionExamples: ["KYC checks before account creation", "Policy validation before refund", "PII redaction before logging"], + relatedConcepts: ["d1-hooks", "d2-tool-design", "d3-claude-md"], + resources: [ + { label: "Agent SDK: Hooks", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + ], + failureModes: ["Relying on prompts for compliance → 5% failure rate compounds", "Hook runs after the tool, not before → irreversible damage done", "Redundant prompt-and-hook instructions → confusion, not safety"], + examSignals: ["reliability issue", "occasionally", "non-zero failure rate", "compliance must be guaranteed"], + }, + { + id: "d1-hooks", name: "Agent SDK Hooks", domain: "D1", task: "1.5", + whyItMatters: "Hooks (PostToolUse, PreToolUse) run deterministic code around every tool call. They're how you intercept, normalize, enforce, and redirect — without relying on the model. The desktop app can't do this; the Agent SDK can.", + productionExamples: ["PII scrubbing on every file read", "Audit logging every DB write", "Blocking writes to protected paths"], + relatedConcepts: ["d1-workflow-enforce", "d3-claude-md", "d2-tool-design"], + resources: [ + { label: "Agent SDK: Hooks reference", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + { label: "Claude Code Features Explained", url: "https://muneebsa.medium.com/claude-code-extensions-explained-skills-mcp-hooks-subagents-agent-teams-plugins-9294907e84ff", type: "blog" }, + ], + failureModes: ["Using prompts to enforce what hooks should enforce", "Hooks with side effects + retries → double writes", "Forgetting hooks run in subagents too"], + examSignals: ["policy enforcement", "deterministic compliance", "tool call interception", "data normalization"], + }, + { + id: "d1-task-decomp", name: "Task Decomposition", domain: "D1", task: "1.6", + whyItMatters: "Fixed prompt chains work for predictable flows; adaptive decomposition (map structure → identify high-impact → prioritized plan) works for open-ended tasks. Choosing wrong costs tokens and time, or worse, misses the high-impact issues entirely.", + productionExamples: ["Codebase security audits (adaptive)", "ETL pipelines (fixed chain)", "Incident investigations (adaptive)"], + relatedConcepts: ["d1-multi-agent", "d3-plan-vs-direct", "d4-multi-pass-review"], + resources: [ + { label: "Anthropic: Prompt chaining", url: "https://platform.claude.com/docs", type: "docs" }, + ], + failureModes: ["Fixed chain on open-ended task → misses the point", "Adaptive decomposition on a deterministic pipeline → wasted planning", "Per-file only (no cross-file pass) → integration bugs missed"], + examSignals: ["adaptive decomposition", "per-file + cross-file", "prompt chaining", "map structure first"], + }, + { + id: "d1-session-state", name: "Session State & Forking", domain: "D1", task: "1.7", + whyItMatters: "Long sessions degrade — answers get vague, context fills up. `fork_session` lets you explore alternatives from a shared baseline. `--resume` vs fresh + summary is a real tradeoff: resume keeps reasoning but compounds degradation; fresh loses nuance but starts clean.", + productionExamples: ["Architecture exploration (fork for alternatives)", "Long debugging sessions", "A/B comparing refactors from a checkpoint"], + relatedConcepts: ["d5-context-mgmt", "d5-codebase-exploration", "d3-iterative-refinement"], + resources: [ + { label: "Claude Code: Resume sessions", url: "https://code.claude.com/docs", type: "docs" }, + ], + failureModes: ["Always resuming → context degrades permanently", "Always starting fresh → losing valuable context repeatedly", "Forking without informing about diverged changes"], + examSignals: ["--resume", "fork_session", "divergent exploration", "session merging"], + }, + // ═══════════════════════════════════════════════════════════ + // D2 — Tool Design & MCP (5 concepts, 18% weight) + // ═══════════════════════════════════════════════════════════ + { + id: "d2-tool-design", name: "Tool Interface Design", domain: "D2", task: "2.1", + whyItMatters: "Claude picks tools based on DESCRIPTIONS, not names. If two tools have overlapping descriptions, selection becomes unreliable and wrong tools fire. Splitting a generic tool into purpose-specific ones (with boundaries spelled out) fixes this.", + productionExamples: ["Billing API with separate refund/adjustment/dispute endpoints", "Knowledge base tools split by authoritativeness", "Split read-only vs write-allowed file tools"], + relatedConcepts: ["d2-tool-distribution", "d2-structured-errors", "d1-hooks"], + resources: [ + { label: "Agent SDK: Tool design", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + ], + failureModes: ["Overlapping tool descriptions → wrong tool called", "Ghost associations from system prompt keywords", "Generic tools used in specialized contexts → incorrect operations"], + examSignals: ["wrong tool called", "tool selection unreliable", "descriptions drive selection", "tool boundaries"], + }, + { + id: "d2-structured-errors", name: "Structured Error Responses", domain: "D2", task: "2.2", + whyItMatters: "`errorCategory + isRetryable + description` lets the coordinator decide: retry, fall back, or escalate. Generic 'unavailable' hides information. The big one: access failure vs valid empty result — if the agent can't distinguish 'DB down' from 'no matching rows', it reports all-clear on broken infra.", + productionExamples: ["Compliance monitoring (empty ≠ access failure)", "Retry logic with exponential backoff", "Escalation routing by error category"], + relatedConcepts: ["d5-error-propagation", "d1-agentic-loop", "d5-escalation"], + resources: [ + { label: "Agent SDK: Error handling", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + ], + failureModes: ["Access failure reported as 'no results' → false all-clear", "isRetryable=true on deterministic failures → infinite loops", "Generic errors → coordinator makes blind decisions"], + examSignals: ["error handling", "recovery", "coordinator decisions", "access failure vs empty"], + }, + { + id: "d2-tool-distribution", name: "Tool Distribution & tool_choice", domain: "D2", task: "2.3", + whyItMatters: "4-5 tools per agent works; 18 degrades selection reliability. Scope tools to the agent's role. `tool_choice` (auto/any/forced) controls when a call is required — forced tool is how you sequence (extract before enrich, verify before act).", + productionExamples: ["Customer support agent (5 tools)", "Research agent (7 scoped tools)", "Forced 'verify_identity' before 'process_refund'"], + relatedConcepts: ["d2-tool-design", "d4-structured-output", "d1-subagent-mgmt"], + resources: [ + { label: "Agent SDK: tool_choice", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + ], + failureModes: ["18+ tools per agent → selection unreliable", "tool_choice='any' when you need 'forced'", "Cross-role tools everywhere → drift from role"], + examSignals: ["too many tools", "tool selection unreliable", "wrong tool called", "forced tool sequencing"], + }, + { + id: "d2-mcp", name: "MCP Integration", domain: "D2", task: "2.4", + whyItMatters: "`.mcp.json` (project-level, version-controlled) vs `~/.claude.json` (user-level, private). Env var expansion `${TOKEN}` keeps credentials out of config. Key distinction: MCP **tools** perform actions; MCP **resources** expose content catalogs — resources reduce exploratory tool calls.", + productionExamples: ["Shared team MCP servers (via .mcp.json)", "Personal tokens in ~/.claude.json", "Doc catalog exposed as MCP resources (no tool call needed to list)"], + relatedConcepts: ["d2-tool-design", "d3-claude-md", "d2-builtin-tools"], + resources: [ + { label: "Model Context Protocol Docs", url: "https://modelcontextprotocol.io/docs", type: "docs" }, + { label: "MCP: Universal Connectivity for LLMs", url: "https://www.zenml.io/llmops-database/model-context-protocol-mcp-building-universal-connectivity-for-llms-in-production", type: "blog" }, + ], + failureModes: ["Credentials in .mcp.json committed to git", "Custom MCP server for standard integration → maintenance burden", "Using tools when resources would work better"], + examSignals: ["content catalog", "reduce tool calls", "give visibility", "project vs user config"], + }, + { + id: "d2-builtin-tools", name: "Built-in Tools", domain: "D2", task: "2.5", + whyItMatters: "Grep for content search, Glob for paths, Read/Write/Edit for files. Edit fails → fall back to Read + Write. Incremental Grep → Read tracing beats loading whole directories. Knowing the fallback patterns keeps sessions efficient.", + productionExamples: ["Codebase search workflows", "Large-file editing via surgical Edits", "Tracing symbol usage via Grep chains"], + relatedConcepts: ["d2-tool-distribution", "d3-plan-vs-direct", "d5-codebase-exploration"], + resources: [ + { label: "Claude Code: Built-in tools", url: "https://code.claude.com/docs", type: "docs" }, + ], + failureModes: ["Reading whole files instead of Grep → context waste", "Edit when string not unique → silent failure, use Read+Write", "Glob for content search (wrong tool)"], + examSignals: ["Grep", "Glob", "Edit fails", "fallback pattern", "incremental tracing"], + }, + // ═══════════════════════════════════════════════════════════ + // D3 — Claude Code Config (6 concepts, 20% weight) + // ═══════════════════════════════════════════════════════════ + { + id: "d3-claude-md", name: "CLAUDE.md Hierarchy", domain: "D3", task: "3.1", + whyItMatters: "User (~/.claude/) → project (.claude/) → directory CLAUDE.md files merge into Claude's working context. User-level is NOT version-controlled (private, per-dev). Project-level is team-shared. Get this wrong and teammates diverge silently.", + productionExamples: ["60-person team with shared .claude/ rules", "Personal tool preferences in ~/.claude/", "Directory-scoped rules for legacy code"], + relatedConcepts: ["d3-path-rules", "d3-commands-skills", "d1-workflow-enforce"], + resources: [ + { label: "Claude Code: CLAUDE.md reference", url: "https://code.claude.com/docs", type: "docs" }, + ], + failureModes: ["Committing ~/.claude/ config to git", "Relying on user-level for team standards", "Monolithic CLAUDE.md when modular @imports would be cleaner"], + examSignals: ["CLAUDE.md hierarchy", "version control", "team-shared rules", "merging behavior"], + }, + { + id: "d3-commands-skills", name: "Commands & Skills", domain: "D3", task: "3.2", + whyItMatters: ".claude/commands/ holds reusable slash commands; .claude/skills/ holds skills with SKILL.md frontmatter. `context: fork` isolates verbose skill output so it doesn't pollute the parent conversation. Project-level vs user-level matters for team sharing.", + productionExamples: ["Custom /commit slash command", "A /review-pr skill forked to avoid context bloat", "Personal ~/.claude/commands/ for solo workflows"], + relatedConcepts: ["d3-claude-md", "d3-cli-cicd", "d1-subagent-mgmt"], + resources: [ + { label: "Claude Code: Skills and commands", url: "https://code.claude.com/docs", type: "docs" }, + { label: "Claude Code Extensions Explained", url: "https://muneebsa.medium.com/claude-code-extensions-explained-skills-mcp-hooks-subagents-agent-teams-plugins-9294907e84ff", type: "blog" }, + ], + failureModes: ["Verbose skill without context: fork → parent conversation drowns", "Personal commands committed to project repo", "Missing allowed-tools frontmatter → permission errors"], + examSignals: ["context: fork", "allowed-tools", "argument-hint", "SKILL.md"], + }, + { + id: "d3-path-rules", name: "Path-Specific Rules", domain: "D3", task: "3.3", + whyItMatters: ".claude/rules/ files with YAML frontmatter glob patterns conditionally load rules based on which file Claude is touching. Cross-directory conventions (e.g., all **/*.test.tsx) work here better than scattered directory-level CLAUDE.md files.", + productionExamples: ["Test-file conventions across entire repo", "API-layer rules for src/api/**/*", "Security rules only for auth code"], + relatedConcepts: ["d3-claude-md", "d3-commands-skills", "d1-workflow-enforce"], + resources: [ + { label: "Claude Code: Path-specific rules", url: "https://code.claude.com/docs", type: "docs" }, + ], + failureModes: ["Monolithic CLAUDE.md with rules for every path → noise", "Globs too broad → rules fire in wrong contexts", "Directory CLAUDE.md duplicates path-rule content"], + examSignals: ["glob patterns", "path-scoped", "cross-directory conventions", "conditional loading"], + }, + { + id: "d3-plan-vs-direct", name: "Plan vs Direct Execution", domain: "D3", task: "3.4", + whyItMatters: "Plan mode is for architecture and multi-file changes where you need alignment before coding. Direct mode is for well-scoped single-file fixes. The Explore subagent isolates verbose discovery from the main conversation.", + productionExamples: ["Architecture refactor → plan mode", "Typo fix → direct", "Research task → Explore subagent, then plan"], + relatedConcepts: ["d1-task-decomp", "d3-iterative-refinement", "d1-subagent-mgmt"], + resources: [ + { label: "Claude Code: Planning mode", url: "https://code.claude.com/docs", type: "docs" }, + ], + failureModes: ["Plan mode for trivial fixes → overhead", "Direct mode for architectural changes → half-done work", "Skipping Explore → verbose output pollutes plan context"], + examSignals: ["plan mode", "direct execution", "architectural", "single-file change", "Explore subagent"], + }, + { + id: "d3-iterative-refinement", name: "Iterative Refinement", domain: "D3", task: "3.5", + whyItMatters: "Concrete input/output examples beat prose descriptions. Test-driven iteration (write tests first, share failures) makes ambiguity visible. Interview pattern (Claude asks questions before implementing) avoids wrong assumptions. Independent issues → sequential messages; interacting issues → one combined message.", + productionExamples: ["TDD loops with Claude writing to pass your tests", "Interview pattern for underspecified tickets", "Concrete JSON examples over prose schema descriptions"], + relatedConcepts: ["d4-explicit-criteria", "d4-few-shot", "d3-plan-vs-direct"], + resources: [ + { label: "Claude Code: Best practices", url: "https://code.claude.com/docs", type: "docs" }, + ], + failureModes: ["Prose descriptions when examples would clarify", "Skipping interview → wrong assumptions baked in", "Batched independent issues → tangled fixes"], + examSignals: ["input/output examples", "test-driven", "interview pattern", "sequential vs single message"], + }, + { + id: "d3-cli-cicd", name: "CLI for CI/CD", domain: "D3", task: "3.6", + whyItMatters: "`-p` / `--print` runs Claude Code non-interactively. `--output-format json` + `--json-schema` makes output machine-parseable. CLAUDE.md still applies. Session isolation matters: separate Claude instance for generation vs review prevents self-confirmation bias.", + productionExamples: ["PR review bot", "Release notes generator", "Automated test-failure triage"], + relatedConcepts: ["d4-multi-pass-review", "d4-structured-output", "d3-claude-md"], + resources: [ + { label: "Claude Code: CLI reference", url: "https://code.claude.com/docs", type: "docs" }, + { label: "Claude Code Deployment Patterns (AWS)", url: "https://aws.amazon.com/blogs/machine-learning/claude-code-deployment-patterns-and-best-practices-with-amazon-bedrock/", type: "blog" }, + ], + failureModes: ["Same session for generate + review → biased review", "No --output-format json → unparseable output in CI", "Forgetting CLAUDE.md still loads in -p mode"], + examSignals: ["-p", "--print", "non-interactive", "--output-format json", "CI/CD integration"], + }, + // ═══════════════════════════════════════════════════════════ + // D4 — Prompt Engineering & Structured Output (6 concepts, 20% weight) + // ═══════════════════════════════════════════════════════════ + { + id: "d4-explicit-criteria", name: "Explicit Criteria", domain: "D4", task: "4.1", + whyItMatters: "Specific categorical criteria ('flag only when claimed behavior directly contradicts actual behavior') beats vague ones ('be conservative'). False positives destroy developer trust — a 5% FP rate in code review means 100 spurious warnings/week nobody reads.", + productionExamples: ["Static analysis rules", "Content moderation thresholds", "Code review linters"], + relatedConcepts: ["d4-few-shot", "d4-schema-design", "d5-confidence-review"], + resources: [ + { label: "Anthropic: Prompt engineering", url: "https://platform.claude.com/docs", type: "docs" }, + ], + failureModes: ["Vague 'be careful' instructions → inconsistent output", "High FP rate destroys trust even at high recall", "Disabling whole categories instead of tightening criteria"], + examSignals: ["improve precision", "reduce false positives", "developer trust", "vague instructions"], + }, + { + id: "d4-few-shot", name: "Few-Shot Prompting", domain: "D4", task: "4.2", + whyItMatters: "2-4 examples targeting AMBIGUOUS scenarios teach the model to generalize. Show WHY one action beats alternatives — demonstrate reasoning, not just format. This is how you handle edge cases you can't pre-enumerate.", + productionExamples: ["Classification with unusual edge cases", "Format demonstrations for custom output", "Reasoning patterns for judgment calls"], + relatedConcepts: ["d4-explicit-criteria", "d4-schema-design", "d3-iterative-refinement"], + resources: [ + { label: "Anthropic: Few-shot prompting", url: "https://platform.claude.com/docs", type: "docs" }, + ], + failureModes: ["Examples only showing easy cases → fails on hard ones", "Examples showing output without reasoning → no generalization", "Too many examples (20+) → context bloat + overfitting"], + examSignals: ["ambiguous cases", "show reasoning", "demonstrate format", "generalize to novel patterns"], + }, + { + id: "d4-structured-output", name: "Structured Output", domain: "D4", task: "4.3", + whyItMatters: "`tool_use` + JSON schema guarantees schema compliance (eliminates SYNTAX errors). Semantic errors persist — the field will be present but the value may be wrong. Nullable/optional fields prevent fabrication when source data is absent.", + productionExamples: ["Document extraction to structured DBs", "API-response normalization", "Invoice field extraction"], + relatedConcepts: ["d4-schema-design", "d2-tool-distribution", "d4-batch-validation"], + resources: [ + { label: "Anthropic: Tool use & structured outputs", url: "https://platform.claude.com/docs", type: "docs" }, + ], + failureModes: ["Required fields on optional data → fabricated values", "Trusting schema compliance for semantic correctness", "tool_choice='auto' when you need 'any' or 'forced'"], + examSignals: ["JSON schema", "tool_use", "guaranteed compliance", "semantic errors persist"], + }, + { + id: "d4-schema-design", name: "Schema Design", domain: "D4", task: "4.4", + whyItMatters: "Required vs optional fields matter: required fields on absent data force the model to fabricate. Enum + 'other' + detail string keeps categories extensible. Pydantic (or similar) adds semantic validation the JSON schema can't.", + productionExamples: ["Extensible categorization (enum + other + free text)", "Nullable fields for optional source data", "Pydantic models for cross-field validation"], + relatedConcepts: ["d4-structured-output", "d4-batch-validation", "d5-error-propagation"], + resources: [ + { label: "Pydantic Docs", url: "https://docs.pydantic.dev", type: "docs" }, + ], + failureModes: ["Locked enums → categorization fails on novel cases", "All-required schemas → hallucination on absent data", "Schema without semantic validation → invalid data accepted"], + examSignals: ["nullable fields", "enum + other", "required vs optional", "semantic validation"], + }, + { + id: "d4-batch-validation", name: "Batch & Validation", domain: "D4", task: "4.5", + whyItMatters: "Message Batches API: 50% cost savings, up to 24-hour window, no latency SLA. Perfect for overnight reports, WRONG for pre-merge checks where devs wait. `custom_id` correlates requests to responses. Validation-retry loops need the document + failed extraction + specific error — not just 'try again'.", + productionExamples: ["Overnight batch classification of tickets", "Pre-merge blocking checks (sync, not batch)", "Validation-retry on failed extractions"], + relatedConcepts: ["d4-structured-output", "d4-schema-design", "d2-structured-errors"], + resources: [ + { label: "Anthropic: Message Batches API", url: "https://platform.claude.com/docs", type: "docs" }, + ], + failureModes: ["Batch for blocking checks → devs wait 24hr", "Generic 'try again' retry → same failure", "Missing custom_id → can't correlate"], + examSignals: ["cost savings", "pre-merge", "overnight", "batch processing", "validation-retry"], + }, + { + id: "d4-multi-pass-review", name: "Multi-Pass Review", domain: "D4", task: "4.6", + whyItMatters: "A model that generated output retains reasoning context — it's biased toward its own work. An independent instance (without that context) catches what the generator missed. Per-file passes catch local issues; a cross-file pass catches integration bugs.", + productionExamples: ["Code review with separate Claude instance", "Per-file linting + cross-file consistency", "Confidence-based routing to human review"], + relatedConcepts: ["d1-task-decomp", "d3-cli-cicd", "d5-confidence-review"], + resources: [ + { label: "Anthropic: Self-review limitations", url: "https://platform.claude.com/docs", type: "docs" }, + ], + failureModes: ["Self-review in same session → confirms own reasoning", "Per-file only → integration bugs slip through", "No confidence routing → low-confidence outputs auto-accepted"], + examSignals: ["review quality", "same session", "independent instance", "per-file + cross-file", "confidence routing"], + }, + // ═══════════════════════════════════════════════════════════ + // D5 — Context & Reliability (6 concepts, 15% weight) + // ═══════════════════════════════════════════════════════════ + { + id: "d5-context-mgmt", name: "Context Management", domain: "D5", task: "5.1", + whyItMatters: "Progressive summarization drops facts. Key facts (amounts, dates, IDs) belong in a case-facts block EXCLUDED from summarization. Trim verbose tool outputs BEFORE accumulation. Lost-in-the-middle: put findings at START and END of long inputs — the middle gets underweighted.", + productionExamples: ["Support tickets with preserved case facts", "Long debugging sessions with distilled findings", "Multi-issue conversations with structured layers"], + relatedConcepts: ["d5-codebase-exploration", "d5-provenance", "d1-session-state"], + resources: [ + { label: "Anthropic: Long context handling", url: "https://platform.claude.com/docs", type: "docs" }, + ], + failureModes: ["Progressive summarization → refund amounts go vague", "Accumulating verbose tool output → token bloat", "Key findings buried in middle → ignored"], + examSignals: ["case facts block", "lost-in-the-middle", "progressive summarization", "trim before accumulate"], + }, + { + id: "d5-escalation", name: "Escalation Patterns", domain: "D5", task: "5.2", + whyItMatters: "Honor explicit human requests immediately. Escalate on policy gaps and when stuck. Do NOT escalate on sentiment (angry customer ≠ needs human) — that's an anti-pattern. On ambiguous matches, ask for the identifier; don't heuristic-guess.", + productionExamples: ["Support agents with clear escalation triggers", "Claims systems escalating on policy gaps", "Identity systems asking for specific IDs on collision"], + relatedConcepts: ["d5-error-propagation", "d2-structured-errors", "d4-explicit-criteria"], + resources: [ + { label: "Anthropic: Agent escalation patterns", url: "https://platform.claude.com/docs", type: "docs" }, + ], + failureModes: ["Sentiment-based escalation → unreliable", "Heuristic-matching on ambiguous identifiers → wrong records", "Not honoring explicit human requests immediately"], + examSignals: ["escalation triggers", "customer demands human", "policy gaps", "sentiment-based anti-pattern"], + }, + { + id: "d5-error-propagation", name: "Error Propagation", domain: "D5", task: "5.3", + whyItMatters: "Structured errors (failure_type + attempted_query + partial_results + alternatives) let coordinators decide. Generic errors hide everything. Recover transients locally; propagate unresolvables with coverage annotations — 'this finding was well-supported, this one came from unavailable sources'.", + productionExamples: ["Research pipelines with coverage annotations", "Local retry + propagated-failure patterns", "Contested-findings sections in reports"], + relatedConcepts: ["d2-structured-errors", "d5-provenance", "d1-agentic-loop"], + resources: [ + { label: "Anthropic: Error handling patterns", url: "https://platform.claude.com/docs", type: "docs" }, + ], + failureModes: ["Generic 'failed' error → coordinator blind", "Propagating recoverable errors → unnecessary escalation", "No coverage annotations → findings indistinguishable from gaps"], + examSignals: ["structured errors", "access failure vs empty", "local recovery", "coverage annotations"], + }, + { + id: "d5-codebase-exploration", name: "Codebase Exploration Context", domain: "D5", task: "5.4", + whyItMatters: "Long exploration sessions degrade: Claude starts referencing 'typical patterns' instead of what it actually found. Scratchpad files persist key findings across context boundaries. `/compact` reduces context mid-session. State manifests enable crash recovery.", + productionExamples: ["Large codebase audits with scratchpads", "Long investigations using /compact", "Crash-recovery manifests for agent state"], + relatedConcepts: ["d5-context-mgmt", "d1-session-state", "d2-builtin-tools"], + resources: [ + { label: "Claude Code: Managing context", url: "https://code.claude.com/docs", type: "docs" }, + ], + failureModes: ["No scratchpad → findings evaporate after /compact", "Inconsistent answers in extended sessions", "No crash recovery → restart from scratch"], + examSignals: ["inconsistent answers", "typical patterns", "context degradation", "extended session", "scratchpad"], + }, + { + id: "d5-confidence-review", name: "Confidence & Review", domain: "D5", task: "5.5", + whyItMatters: "Aggregate 97% accuracy can hide terrible performance on specific doc types. Stratified sampling by doc type measures true error rate. Field-level confidence needs calibration against labeled validation — not self-reported scores alone.", + productionExamples: ["Stratified random sampling of extractions", "Per-doc-type accuracy dashboards", "Human review routing by confidence threshold"], + relatedConcepts: ["d4-multi-pass-review", "d5-provenance", "d4-explicit-criteria"], + resources: [ + { label: "Anthropic: Evaluating model outputs", url: "https://platform.claude.com/docs", type: "docs" }, + ], + failureModes: ["Aggregate accuracy hides per-type failures", "Trusting self-reported confidence without calibration", "No stratified sampling → systemic errors invisible"], + examSignals: ["aggregate accuracy", "stratified sampling", "field-level confidence", "calibration"], + }, + { + id: "d5-provenance", name: "Provenance", domain: "D5", task: "5.6", + whyItMatters: "Claim-source mappings must be preserved through synthesis. Temporal data needs publication/collection dates. Conflicts need source attribution — don't arbitrarily pick one. Structured reports separate well-supported from contested findings explicitly.", + productionExamples: ["Research reports with citation tracking", "Temporal-sensitive analysis with dates", "Contested-findings sections for disputed claims"], + relatedConcepts: ["d5-error-propagation", "d5-confidence-review", "d1-subagent-mgmt"], + resources: [ + { label: "Anthropic: Provenance & citations", url: "https://platform.claude.com/docs", type: "docs" }, + ], + failureModes: ["Synthesis drops source attribution → unverifiable claims", "Arbitrary conflict resolution → silent bias", "No temporal data → stale findings presented as current"], + examSignals: ["claim-source mappings", "temporal data", "conflict annotation", "contested findings"], + }, +]; + /* ─── DECISION RULES ─── */ const RULES = [ { @@ -600,6 +955,7 @@ export default function App() { const tabs = [ { id: "plan", label: "Weekly Plan" }, + { id: "brain", label: "Brain Map" }, { id: "tree", label: "Concepts" }, { id: "builds", label: "Projects" }, { id: "rules", label: "Decision Rules" }, @@ -702,6 +1058,7 @@ export default function App() {
{tab === "plan" && } + {tab === "brain" && } {tab === "tree" && } {tab === "builds" && } {tab === "rules" && } @@ -1024,6 +1381,412 @@ function TreeTab({ domains, openDomain, setOpenDomain, progress, toggle }) { ); } +/* ─── BrainMapTab ─── */ +// Brighter label colors for contrast on dark bg (D1/D5 fail WCAG AA otherwise) +const LABEL_COLORS = { + D1: "#e74c3c", D2: "#e67e22", D3: "#2ecc71", D4: "#3498db", D5: "#a569bd", +}; +const DOMAIN_FILL = { + D1: "#c0392b", D2: "#d35400", D3: "#27ae60", D4: "#2980b9", D5: "#8e44ad", +}; +const DOMAIN_WEIGHTS = { D1: 27, D2: 18, D3: 20, D4: 20, D5: 15 }; + +function polar(cx, cy, r, angleDeg) { + const rad = (angleDeg - 90) * Math.PI / 180; + return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) }; +} + +function computeLayout() { + const cx = 360, cy = 320; + const rDomain = 130, rConcept = 245; + const domainIds = ["D1", "D2", "D3", "D4", "D5"]; + const totalWeight = 100; + const gapDeg = 4; + const availDeg = 360 - gapDeg * 5; + + const domains = {}; + let angleCursor = 0; + domainIds.forEach(did => { + const span = (DOMAIN_WEIGHTS[did] / totalWeight) * availDeg; + const start = angleCursor; + const mid = start + span / 2; + domains[did] = { id: did, angleStart: start, angleEnd: start + span, angleMid: mid, span }; + angleCursor += span + gapDeg; + }); + + const conceptsByDomain = {}; + ENRICHED_CONCEPTS.forEach(c => { + if (!conceptsByDomain[c.domain]) conceptsByDomain[c.domain] = []; + conceptsByDomain[c.domain].push(c); + }); + + const concepts = {}; + domainIds.forEach(did => { + const list = conceptsByDomain[did] || []; + const { angleStart, angleEnd } = domains[did]; + const pad = 2; + const usable = (angleEnd - angleStart) - pad * 2; + const step = list.length > 1 ? usable / (list.length - 1) : 0; + const base = angleStart + pad; + list.forEach((c, i) => { + const angle = list.length === 1 ? (angleStart + angleEnd) / 2 : base + step * i; + const pos = polar(cx, cy, rConcept, angle); + concepts[c.id] = { ...c, x: pos.x, y: pos.y, angle }; + }); + const midPos = polar(cx, cy, rDomain, domains[did].angleMid); + domains[did].x = midPos.x; + domains[did].y = midPos.y; + domains[did].radius = 18 + (DOMAIN_WEIGHTS[did] / 27) * 16; + }); + + return { cx, cy, domains, concepts, domainIds }; +} + +const BRAIN_LAYOUT = computeLayout(); + +function BrainMapTab({ progress, toggle }) { + const [selectedId, setSelectedId] = useState(null); + const [hoveredId, setHoveredId] = useState(null); + const { cx, cy, domains, concepts, domainIds } = BRAIN_LAYOUT; + + useEffect(() => { + const onKey = (e) => { if (e.key === "Escape") setSelectedId(null); }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, []); + + const selected = selectedId ? concepts[selectedId] : null; + const activeId = hoveredId || selectedId; + const activeConcept = activeId ? concepts[activeId] : null; + const activeRelatedSet = activeConcept ? new Set(activeConcept.relatedConcepts) : null; + + const arcs = []; + ENRICHED_CONCEPTS.forEach(c => { + (c.relatedConcepts || []).forEach(rid => { + if (c.id < rid) arcs.push({ a: c.id, b: rid }); + }); + }); + + return ( +
+

+ Radial view of all 30 concepts across 5 domains. Arcs show cross-domain relationships. Click any concept for production context. +

+
+ + {/* Background rings (faint guides) */} + + + + {/* Cross-domain arcs */} + {arcs.map((arc, i) => { + const a = concepts[arc.a], b = concepts[arc.b]; + if (!a || !b) return null; + const isActive = activeId && (activeId === arc.a || activeId === arc.b); + const mx = (a.x + b.x) / 2, my = (a.y + b.y) / 2; + const pullX = cx + (mx - cx) * 0.25; + const pullY = cy + (my - cy) * 0.25; + const stroke = isActive ? LABEL_COLORS[a.domain] : "#2a2a38"; + const opacity = activeId ? (isActive ? 0.55 : 0.05) : 0.15; + return ( + + ); + })} + + {/* Root node connector lines */} + {domainIds.map(did => { + const d = domains[did]; + const isDim = activeId && activeConcept && activeConcept.domain !== did; + return ( + + ); + })} + + {/* Domain → concept connector lines */} + {Object.values(concepts).map(c => { + const d = domains[c.domain]; + const dim = activeId && activeConcept && !(activeId === c.id || activeRelatedSet.has(c.id)); + return ( + + ); + })} + + {/* Domain nodes */} + {domainIds.map(did => { + const d = domains[did]; + const dim = activeId && activeConcept && activeConcept.domain !== did; + return ( + + + + {did} + + + {DOMAIN_WEIGHTS[did]}% + + + ); + })} + + {/* Root node (center) */} + + + CCA-F + FOUNDATIONS + + + {/* Concept nodes */} + {Object.values(concepts).map(c => { + const isSelected = selectedId === c.id; + const isHovered = hoveredId === c.id; + const isRelated = activeRelatedSet && activeRelatedSet.has(c.id); + const dim = activeId && !(activeId === c.id || isRelated); + const isComplete = !!progress[`bm-${c.id}`]; + const nodeR = isSelected ? 10 : isHovered ? 9 : 7; + const fill = isComplete ? "#0c0c10" : DOMAIN_FILL[c.domain]; + const strokeW = isSelected ? 2.5 : isComplete ? 2 : 1; + const labelOutside = c.x >= cx - 20; + return ( + setSelectedId(c.id)} + onMouseEnter={() => setHoveredId(c.id)} + onMouseLeave={() => setHoveredId(null)} + onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setSelectedId(c.id); } }} + tabIndex={0} + role="treeitem" + aria-label={`${c.name}, domain ${c.domain}, task ${c.task}`} + style={{ cursor: "pointer", outline: "none", transition: "opacity 0.18s" }} + opacity={dim ? 0.25 : 1} + > + + + {c.task} {c.name} + + + ); + })} + + + {/* Legend */} +
+ {domainIds.map(did => ( + + + {did} {DOMAIN_WEIGHTS[did]}% + + ))} + + Hollow = mastered · Click a node + +
+
+ + {/* Detail Panel */} + {selected && ( + setSelectedId(null)} + onNavigate={(id) => setSelectedId(id)} + /> + )} +
+ ); +} + +/* ─── DetailPanel: expands below SVG (mobile-first, no modal overlay) ─── */ +function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate }) { + const domainFill = DOMAIN_FILL[concept.domain]; + const labelColor = LABEL_COLORS[concept.domain]; + const isComplete = !!progress[`bm-${concept.id}`]; + const related = (concept.relatedConcepts || []).map(id => concepts[id]).filter(Boolean); + + return ( +
+
+
+
+ {concept.domain} + TASK {concept.task} +
+

+ {concept.name} +

+
+
+ + +
+
+ + +

{concept.whyItMatters}

+
+ + +
    + {concept.productionExamples.map((ex, i) =>
  • {ex}
  • )} +
+
+ + {related.length > 0 && ( + +
+ {related.map(r => ( + + ))} +
+
+ )} + + +
+ {concept.resources.map((r, i) => ( + + {r.type} + {r.label} + + + ))} +
+
+ + +
    + {concept.failureModes.map((fm, i) =>
  • {fm}
  • )} +
+
+ + +
+ {concept.examSignals.map((sig, i) => ( + "{sig}" + ))} +
+
+
+ ); +} + +function DetailSection({ label, color, children, last }) { + return ( +
+
{label}
+ {children} +
+ ); +} + /* ─── BuildsTab ─── */ function BuildsTab({ projects }) { return ( From c967b8bd193a1133c46852d6748041568fe92f10 Mon Sep 17 00:00:00 2001 From: warfield Date: Sun, 5 Apr 2026 20:30:50 +0800 Subject: [PATCH 2/4] Add theme toggle: dark / grey / light screen modes A button in the header cycles through three themes. Selection persists in localStorage (key: cca-theme) and auto-restores on next visit. Themes: - dark (default): current #0c0c10 root, matches prior design - grey: mid-tone #1f2026 root for reduced eye strain - light: #fafafa root with dark text, for daytime / bright environments Implementation: CSS custom properties injected into the root container via inline style. Components reference colors through var(--bg-root), var(--text-primary), etc. Accent colors (domain reds, oranges, greens) remain fixed across themes to preserve visual identity and the existing WCAG contrast work. Minimal refactor: replaced ~60 hardcoded hex literals with CSS variable references across the whole component tree. Kept ~4 literal colors where they're intentionally contrast on colored fills (domain badges), since those need to stay dark regardless of theme. --- README.md | 1 + src/App.jsx | 301 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 185 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index 8633191..f62d724 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ A week-by-week battle plan covering all 5 exam domains, organized into 4 phases: ### Features - **6 tabs**: Weekly Plan, **Brain Map**, Concepts (30 nodes), Projects, Decision Rules (11 rules), Cheat Sheet +- **Theme toggle** (top-right): cycle dark → grey → light; choice persists in localStorage - **Daily KPIs** with measurable targets for each study day - **Daily closure assessments** linking to specific quiz engine modes - **Progress tracking** with localStorage-persisted checkboxes on tasks and concepts diff --git a/src/App.jsx b/src/App.jsx index 8632dc7..dbfd82a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,11 +2,49 @@ import { useState, useEffect, useMemo } from "react"; /* ─── localStorage progress tracking ─── */ const STORAGE_KEY = "cca-study-progress"; +const THEME_KEY = "cca-theme"; function loadProgress() { try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || {}; } catch { return {}; } } function saveProgress(p) { localStorage.setItem(STORAGE_KEY, JSON.stringify(p)); } +function loadTheme() { + try { return localStorage.getItem(THEME_KEY) || "dark"; } + catch { return "dark"; } +} +function saveTheme(t) { try { localStorage.setItem(THEME_KEY, t); } catch {} } + +/* ─── THEMES: three palettes (dark / grey / light) driving CSS variables ─── */ +const THEMES = { + dark: { + bgRoot: "#0c0c10", bgPanel: "var(--bg-panel)", bgPanelAlt: "var(--bg-panel-alt)", + border: "#1c1c28", borderSoft: "var(--border-soft)", borderMed: "var(--border-med)", + textPrimary: "var(--text-primary)", textBody: "var(--text-body)", textSoft: "var(--text-soft)", + textMuted: "var(--text-muted)", textFaint: "var(--text-faint)", textDim: "var(--text-dim)", + }, + grey: { + bgRoot: "#1f2026", bgPanel: "#272830", bgPanelAlt: "#2e2f38", + border: "#363742", borderSoft: "#323340", borderMed: "#3e3f4c", + textPrimary: "#f5f5f7", textBody: "#d8d8dd", textSoft: "#a8a8b0", + textMuted: "#888892", textFaint: "#666670", textDim: "#4a4a55", + }, + light: { + bgRoot: "#fafafa", bgPanel: "#ffffff", bgPanelAlt: "#f2f2f4", + border: "#d8d8dd", borderSoft: "#e4e4e8", borderMed: "var(--text-body)", + textPrimary: "#0c0c10", textBody: "#2a2a32", textSoft: "#4a4a55", + textMuted: "#6a6a75", textFaint: "#8a8a95", textDim: "#b0b0b8", + }, +}; + +function themeVars(theme) { + const t = THEMES[theme] || THEMES.dark; + return { + "--bg-root": t.bgRoot, "--bg-panel": t.bgPanel, "--bg-panel-alt": t.bgPanelAlt, + "--border": t.border, "--border-soft": t.borderSoft, "--border-med": t.borderMed, + "--text-primary": t.textPrimary, "--text-body": t.textBody, "--text-soft": t.textSoft, + "--text-muted": t.textMuted, "--text-faint": t.textFaint, "--text-dim": t.textDim, + }; +} /* ─── WEEKS ─── */ const WEEKS = [ @@ -14,7 +52,7 @@ const WEEKS = [ id: 1, title: "Foundation Sprint", subtitle: "Core mental models + baseline diagnostic", - theme: "Build the skeleton — understand HOW Claude thinks", + theme: "Build the skeleton — understand HOW the system thinks", days: [ { day: "Days 1–2", @@ -35,7 +73,7 @@ const WEEKS = [ focus: "Tool Design + MCP Foundations", domain: "D2", tasks: [ - "Study tool descriptions as THE mechanism for LLM tool selection", + "Study tool descriptions as THE mechanism for tool selection", "Learn tool splitting: generic → purpose-specific (analyze_document → extract_data_points + summarize_content + verify_claim)", "MCP: tools (actions) vs resources (content catalogs) — resources reduce exploratory calls", "Config: .mcp.json (project/shared) vs ~/.claude.json (user/personal), env var expansion ${TOKEN}", @@ -311,7 +349,7 @@ const CONCEPT_TREE = [ weight: "18%", accent: "#d35400", concepts: [ - { name: "Tool Interface Design", children: ["descriptions drive LLM tool selection", "include input formats, examples, edge cases, boundaries", "rename + update descriptions to eliminate overlap", "splitting generic → purpose-specific tools"] }, + { name: "Tool Interface Design", children: ["descriptions drive tool selection", "include input formats, examples, edge cases, boundaries", "rename + update descriptions to eliminate overlap", "splitting generic → purpose-specific tools"] }, { name: "Structured Error Responses", children: ["errorCategory: transient/validation/permission", "isRetryable boolean prevents wasted retries", "isError flag in MCP for tool failures", "access failure vs valid empty result distinction"] }, { name: "Tool Distribution & tool_choice", children: ["4-5 tools per agent; 18 degrades selection", "scoped cross-role tools for high-frequency needs", "tool_choice: auto / any / forced selection", "forced tool for sequencing (extract before enrich)"] }, { name: "MCP Integration", children: [".mcp.json (project) vs ~/.claude.json (user)", "env var expansion ${TOKEN} for credentials", "community servers over custom for standard integrations", "resources = content catalogs; tools = actions"] }, @@ -327,7 +365,7 @@ const CONCEPT_TREE = [ { name: "Commands & Skills", children: [".claude/commands/ (project) vs ~/.claude/commands/ (personal)", ".claude/skills/ + SKILL.md with frontmatter config", "context: fork isolates verbose skill output", "allowed-tools + argument-hint in frontmatter"] }, { name: "Path-Specific Rules", children: [".claude/rules/ with YAML frontmatter glob patterns", "paths: ['src/api/**/*'] for conditional loading", "glob patterns span directories (e.g., **/*.test.tsx)", "path rules > directory CLAUDE.md for cross-directory conventions"] }, { name: "Plan vs Direct Execution", children: ["plan mode: large-scale, multiple approaches, architectural", "direct: well-scoped single-file changes", "Explore subagent isolates verbose discovery", "combine: plan for investigation, direct for implementation"] }, - { name: "Iterative Refinement", children: ["concrete input/output examples > prose descriptions", "test-driven: write tests first, share failures to iterate", "interview pattern: Claude asks questions before implementing", "single message for interacting issues; sequential for independent"] }, + { name: "Iterative Refinement", children: ["concrete input/output examples > prose descriptions", "test-driven: write tests first, share failures to iterate", "interview pattern: agent asks questions before implementing", "single message for interacting issues; sequential for independent"] }, { name: "CLI for CI/CD", children: ["-p / --print: non-interactive mode", "--output-format json + --json-schema", "CLAUDE.md provides project context to CI-invoked Claude", "session isolation: separate instance for review vs generation"] }, ], }, @@ -366,12 +404,12 @@ const ENRICHED_CONCEPTS = [ // ═══════════════════════════════════════════════════════════ { id: "d1-agentic-loop", name: "Agentic Loop", domain: "D1", task: "1.1", - whyItMatters: "The loop is Claude's execution model. Check text content instead of stop_reason and your agent stops mid-workflow — claims don't get approved, support tickets sit half-resolved, extraction pipelines go silent. Tested heavily because it breaks quietly in prod.", + whyItMatters: "The loop is the core execution model. Check text content instead of stop_reason and your agent stops mid-workflow — claims don't get approved, support tickets sit half-resolved, extraction pipelines go silent. Tested heavily because it breaks quietly in prod.", productionExamples: ["Customer support ticket resolution", "Insurance claims processing (retrieve → assess → approve)", "Document extraction pipelines"], relatedConcepts: ["d2-structured-errors", "d4-structured-output", "d5-error-propagation"], resources: [ - { label: "Anthropic Academy: Building with the Claude API", url: "https://anthropic.skilljar.com", type: "course" }, - { label: "Agent SDK Overview", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + { label: "Building with the API (Skilljar Course)", url: "https://anthropic.skilljar.com", type: "course" }, + { label: "SDK Overview", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, ], failureModes: ["Checking text content for termination → stops before tools finish", "NL parsing of tool results → brittle, unreliable routing", "Iteration cap without monitoring → silent task truncation"], examSignals: ["agent stops mid-workflow", "reliability issue", "loop termination", "not calling expected tool"], @@ -382,8 +420,8 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Research systems querying multiple sources", "Codebase-wide refactors (per-file + cross-file passes)", "Compliance sweeps across heterogeneous datastores"], relatedConcepts: ["d1-subagent-mgmt", "d1-task-decomp", "d5-context-mgmt"], resources: [ - { label: "Agent SDK: Subagents", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, - { label: "Claude Code Full Stack Guide", url: "https://alexop.dev/posts/understanding-claude-code-full-stack/", type: "blog" }, + { label: "Subagent Docs", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + { label: "Full Stack Guide (alexop.dev)", url: "https://alexop.dev/posts/understanding-claude-code-full-stack/", type: "blog" }, ], failureModes: ["Too-narrow subagents → fragmented context, no synthesis possible", "Coordinator doing execution work instead of delegating", "Bypassing the coordinator for inter-subagent chat"], examSignals: ["hub-and-spoke", "coordinator", "subagent selection", "complex investigation"], @@ -394,7 +432,7 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Parallel codebase investigations", "Cross-department research (legal + finance + ops)", "Per-file review + cross-file integration review"], relatedConcepts: ["d1-multi-agent", "d5-context-mgmt", "d5-provenance"], resources: [ - { label: "Agent SDK: Task tool", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + { label: "Task Tool Docs", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, ], failureModes: ["Assuming context inheritance → synthesis agent lacks info", "Sequential Task calls when they could run in parallel", "Over-broad allowedTools → subagent drifts out of scope"], examSignals: ["subagent", "context passing", "synthesis agent lacks info", "parallel Task"], @@ -405,7 +443,7 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["KYC checks before account creation", "Policy validation before refund", "PII redaction before logging"], relatedConcepts: ["d1-hooks", "d2-tool-design", "d3-claude-md"], resources: [ - { label: "Agent SDK: Hooks", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + { label: "Hooks Docs", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, ], failureModes: ["Relying on prompts for compliance → 5% failure rate compounds", "Hook runs after the tool, not before → irreversible damage done", "Redundant prompt-and-hook instructions → confusion, not safety"], examSignals: ["reliability issue", "occasionally", "non-zero failure rate", "compliance must be guaranteed"], @@ -416,8 +454,8 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["PII scrubbing on every file read", "Audit logging every DB write", "Blocking writes to protected paths"], relatedConcepts: ["d1-workflow-enforce", "d3-claude-md", "d2-tool-design"], resources: [ - { label: "Agent SDK: Hooks reference", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, - { label: "Claude Code Features Explained", url: "https://muneebsa.medium.com/claude-code-extensions-explained-skills-mcp-hooks-subagents-agent-teams-plugins-9294907e84ff", type: "blog" }, + { label: "Hooks Reference", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + { label: "Features Explained (Medium)", url: "https://muneebsa.medium.com/claude-code-extensions-explained-skills-mcp-hooks-subagents-agent-teams-plugins-9294907e84ff", type: "blog" }, ], failureModes: ["Using prompts to enforce what hooks should enforce", "Hooks with side effects + retries → double writes", "Forgetting hooks run in subagents too"], examSignals: ["policy enforcement", "deterministic compliance", "tool call interception", "data normalization"], @@ -428,7 +466,7 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Codebase security audits (adaptive)", "ETL pipelines (fixed chain)", "Incident investigations (adaptive)"], relatedConcepts: ["d1-multi-agent", "d3-plan-vs-direct", "d4-multi-pass-review"], resources: [ - { label: "Anthropic: Prompt chaining", url: "https://platform.claude.com/docs", type: "docs" }, + { label: "Prompt Chaining Docs", url: "https://platform.claude.com/docs", type: "docs" }, ], failureModes: ["Fixed chain on open-ended task → misses the point", "Adaptive decomposition on a deterministic pipeline → wasted planning", "Per-file only (no cross-file pass) → integration bugs missed"], examSignals: ["adaptive decomposition", "per-file + cross-file", "prompt chaining", "map structure first"], @@ -439,7 +477,7 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Architecture exploration (fork for alternatives)", "Long debugging sessions", "A/B comparing refactors from a checkpoint"], relatedConcepts: ["d5-context-mgmt", "d5-codebase-exploration", "d3-iterative-refinement"], resources: [ - { label: "Claude Code: Resume sessions", url: "https://code.claude.com/docs", type: "docs" }, + { label: "Resume Sessions Docs", url: "https://code.claude.com/docs", type: "docs" }, ], failureModes: ["Always resuming → context degrades permanently", "Always starting fresh → losing valuable context repeatedly", "Forking without informing about diverged changes"], examSignals: ["--resume", "fork_session", "divergent exploration", "session merging"], @@ -449,11 +487,11 @@ const ENRICHED_CONCEPTS = [ // ═══════════════════════════════════════════════════════════ { id: "d2-tool-design", name: "Tool Interface Design", domain: "D2", task: "2.1", - whyItMatters: "Claude picks tools based on DESCRIPTIONS, not names. If two tools have overlapping descriptions, selection becomes unreliable and wrong tools fire. Splitting a generic tool into purpose-specific ones (with boundaries spelled out) fixes this.", + whyItMatters: "Tools are selected based on DESCRIPTIONS, not names. If two tools have overlapping descriptions, selection becomes unreliable and wrong tools fire. Splitting a generic tool into purpose-specific ones (with boundaries spelled out) fixes this.", productionExamples: ["Billing API with separate refund/adjustment/dispute endpoints", "Knowledge base tools split by authoritativeness", "Split read-only vs write-allowed file tools"], relatedConcepts: ["d2-tool-distribution", "d2-structured-errors", "d1-hooks"], resources: [ - { label: "Agent SDK: Tool design", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + { label: "Tool Design Docs", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, ], failureModes: ["Overlapping tool descriptions → wrong tool called", "Ghost associations from system prompt keywords", "Generic tools used in specialized contexts → incorrect operations"], examSignals: ["wrong tool called", "tool selection unreliable", "descriptions drive selection", "tool boundaries"], @@ -464,7 +502,7 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Compliance monitoring (empty ≠ access failure)", "Retry logic with exponential backoff", "Escalation routing by error category"], relatedConcepts: ["d5-error-propagation", "d1-agentic-loop", "d5-escalation"], resources: [ - { label: "Agent SDK: Error handling", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + { label: "Error Handling Docs", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, ], failureModes: ["Access failure reported as 'no results' → false all-clear", "isRetryable=true on deterministic failures → infinite loops", "Generic errors → coordinator makes blind decisions"], examSignals: ["error handling", "recovery", "coordinator decisions", "access failure vs empty"], @@ -475,7 +513,7 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Customer support agent (5 tools)", "Research agent (7 scoped tools)", "Forced 'verify_identity' before 'process_refund'"], relatedConcepts: ["d2-tool-design", "d4-structured-output", "d1-subagent-mgmt"], resources: [ - { label: "Agent SDK: tool_choice", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, + { label: "tool_choice Docs", url: "https://platform.claude.com/docs/en/agent-sdk/overview", type: "docs" }, ], failureModes: ["18+ tools per agent → selection unreliable", "tool_choice='any' when you need 'forced'", "Cross-role tools everywhere → drift from role"], examSignals: ["too many tools", "tool selection unreliable", "wrong tool called", "forced tool sequencing"], @@ -487,7 +525,7 @@ const ENRICHED_CONCEPTS = [ relatedConcepts: ["d2-tool-design", "d3-claude-md", "d2-builtin-tools"], resources: [ { label: "Model Context Protocol Docs", url: "https://modelcontextprotocol.io/docs", type: "docs" }, - { label: "MCP: Universal Connectivity for LLMs", url: "https://www.zenml.io/llmops-database/model-context-protocol-mcp-building-universal-connectivity-for-llms-in-production", type: "blog" }, + { label: "MCP: Universal Connectivity (ZenML)", url: "https://www.zenml.io/llmops-database/model-context-protocol-mcp-building-universal-connectivity-for-llms-in-production", type: "blog" }, ], failureModes: ["Credentials in .mcp.json committed to git", "Custom MCP server for standard integration → maintenance burden", "Using tools when resources would work better"], examSignals: ["content catalog", "reduce tool calls", "give visibility", "project vs user config"], @@ -498,21 +536,21 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Codebase search workflows", "Large-file editing via surgical Edits", "Tracing symbol usage via Grep chains"], relatedConcepts: ["d2-tool-distribution", "d3-plan-vs-direct", "d5-codebase-exploration"], resources: [ - { label: "Claude Code: Built-in tools", url: "https://code.claude.com/docs", type: "docs" }, + { label: "Built-in Tools Docs", url: "https://code.claude.com/docs", type: "docs" }, ], failureModes: ["Reading whole files instead of Grep → context waste", "Edit when string not unique → silent failure, use Read+Write", "Glob for content search (wrong tool)"], examSignals: ["Grep", "Glob", "Edit fails", "fallback pattern", "incremental tracing"], }, // ═══════════════════════════════════════════════════════════ - // D3 — Claude Code Config (6 concepts, 20% weight) + // D3 — Config & Workflows (6 concepts, 20% weight) // ═══════════════════════════════════════════════════════════ { id: "d3-claude-md", name: "CLAUDE.md Hierarchy", domain: "D3", task: "3.1", - whyItMatters: "User (~/.claude/) → project (.claude/) → directory CLAUDE.md files merge into Claude's working context. User-level is NOT version-controlled (private, per-dev). Project-level is team-shared. Get this wrong and teammates diverge silently.", + whyItMatters: "User (~/.claude/) → project (.claude/) → directory CLAUDE.md files merge into working context. User-level is NOT version-controlled (private, per-dev). Project-level is team-shared. Get this wrong and teammates diverge silently.", productionExamples: ["60-person team with shared .claude/ rules", "Personal tool preferences in ~/.claude/", "Directory-scoped rules for legacy code"], relatedConcepts: ["d3-path-rules", "d3-commands-skills", "d1-workflow-enforce"], resources: [ - { label: "Claude Code: CLAUDE.md reference", url: "https://code.claude.com/docs", type: "docs" }, + { label: "CLAUDE.md Reference", url: "https://code.claude.com/docs", type: "docs" }, ], failureModes: ["Committing ~/.claude/ config to git", "Relying on user-level for team standards", "Monolithic CLAUDE.md when modular @imports would be cleaner"], examSignals: ["CLAUDE.md hierarchy", "version control", "team-shared rules", "merging behavior"], @@ -523,19 +561,19 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Custom /commit slash command", "A /review-pr skill forked to avoid context bloat", "Personal ~/.claude/commands/ for solo workflows"], relatedConcepts: ["d3-claude-md", "d3-cli-cicd", "d1-subagent-mgmt"], resources: [ - { label: "Claude Code: Skills and commands", url: "https://code.claude.com/docs", type: "docs" }, - { label: "Claude Code Extensions Explained", url: "https://muneebsa.medium.com/claude-code-extensions-explained-skills-mcp-hooks-subagents-agent-teams-plugins-9294907e84ff", type: "blog" }, + { label: "Skills & Commands Docs", url: "https://code.claude.com/docs", type: "docs" }, + { label: "Extensions Explained (Medium)", url: "https://muneebsa.medium.com/claude-code-extensions-explained-skills-mcp-hooks-subagents-agent-teams-plugins-9294907e84ff", type: "blog" }, ], failureModes: ["Verbose skill without context: fork → parent conversation drowns", "Personal commands committed to project repo", "Missing allowed-tools frontmatter → permission errors"], examSignals: ["context: fork", "allowed-tools", "argument-hint", "SKILL.md"], }, { id: "d3-path-rules", name: "Path-Specific Rules", domain: "D3", task: "3.3", - whyItMatters: ".claude/rules/ files with YAML frontmatter glob patterns conditionally load rules based on which file Claude is touching. Cross-directory conventions (e.g., all **/*.test.tsx) work here better than scattered directory-level CLAUDE.md files.", + whyItMatters: ".claude/rules/ files with YAML frontmatter glob patterns conditionally load rules based on which file is being touched. Cross-directory conventions (e.g., all **/*.test.tsx) work here better than scattered directory-level CLAUDE.md files.", productionExamples: ["Test-file conventions across entire repo", "API-layer rules for src/api/**/*", "Security rules only for auth code"], relatedConcepts: ["d3-claude-md", "d3-commands-skills", "d1-workflow-enforce"], resources: [ - { label: "Claude Code: Path-specific rules", url: "https://code.claude.com/docs", type: "docs" }, + { label: "Path-Specific Rules Docs", url: "https://code.claude.com/docs", type: "docs" }, ], failureModes: ["Monolithic CLAUDE.md with rules for every path → noise", "Globs too broad → rules fire in wrong contexts", "Directory CLAUDE.md duplicates path-rule content"], examSignals: ["glob patterns", "path-scoped", "cross-directory conventions", "conditional loading"], @@ -546,30 +584,30 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Architecture refactor → plan mode", "Typo fix → direct", "Research task → Explore subagent, then plan"], relatedConcepts: ["d1-task-decomp", "d3-iterative-refinement", "d1-subagent-mgmt"], resources: [ - { label: "Claude Code: Planning mode", url: "https://code.claude.com/docs", type: "docs" }, + { label: "Planning Mode Docs", url: "https://code.claude.com/docs", type: "docs" }, ], failureModes: ["Plan mode for trivial fixes → overhead", "Direct mode for architectural changes → half-done work", "Skipping Explore → verbose output pollutes plan context"], examSignals: ["plan mode", "direct execution", "architectural", "single-file change", "Explore subagent"], }, { id: "d3-iterative-refinement", name: "Iterative Refinement", domain: "D3", task: "3.5", - whyItMatters: "Concrete input/output examples beat prose descriptions. Test-driven iteration (write tests first, share failures) makes ambiguity visible. Interview pattern (Claude asks questions before implementing) avoids wrong assumptions. Independent issues → sequential messages; interacting issues → one combined message.", - productionExamples: ["TDD loops with Claude writing to pass your tests", "Interview pattern for underspecified tickets", "Concrete JSON examples over prose schema descriptions"], + whyItMatters: "Concrete input/output examples beat prose descriptions. Test-driven iteration (write tests first, share failures) makes ambiguity visible. Interview pattern (agent asks clarifying questions before implementing) avoids wrong assumptions. Independent issues → sequential messages; interacting issues → one combined message.", + productionExamples: ["TDD loops with agents writing to pass your tests", "Interview pattern for underspecified tickets", "Concrete JSON examples over prose schema descriptions"], relatedConcepts: ["d4-explicit-criteria", "d4-few-shot", "d3-plan-vs-direct"], resources: [ - { label: "Claude Code: Best practices", url: "https://code.claude.com/docs", type: "docs" }, + { label: "Best Practices Docs", url: "https://code.claude.com/docs", type: "docs" }, ], failureModes: ["Prose descriptions when examples would clarify", "Skipping interview → wrong assumptions baked in", "Batched independent issues → tangled fixes"], examSignals: ["input/output examples", "test-driven", "interview pattern", "sequential vs single message"], }, { id: "d3-cli-cicd", name: "CLI for CI/CD", domain: "D3", task: "3.6", - whyItMatters: "`-p` / `--print` runs Claude Code non-interactively. `--output-format json` + `--json-schema` makes output machine-parseable. CLAUDE.md still applies. Session isolation matters: separate Claude instance for generation vs review prevents self-confirmation bias.", + whyItMatters: "`-p` / `--print` runs the CLI non-interactively. `--output-format json` + `--json-schema` makes output machine-parseable. CLAUDE.md still applies. Session isolation matters: separate instance for generation vs review prevents self-confirmation bias.", productionExamples: ["PR review bot", "Release notes generator", "Automated test-failure triage"], relatedConcepts: ["d4-multi-pass-review", "d4-structured-output", "d3-claude-md"], resources: [ - { label: "Claude Code: CLI reference", url: "https://code.claude.com/docs", type: "docs" }, - { label: "Claude Code Deployment Patterns (AWS)", url: "https://aws.amazon.com/blogs/machine-learning/claude-code-deployment-patterns-and-best-practices-with-amazon-bedrock/", type: "blog" }, + { label: "CLI Reference Docs", url: "https://code.claude.com/docs", type: "docs" }, + { label: "Deployment Patterns (AWS Blog)", url: "https://aws.amazon.com/blogs/machine-learning/claude-code-deployment-patterns-and-best-practices-with-amazon-bedrock/", type: "blog" }, ], failureModes: ["Same session for generate + review → biased review", "No --output-format json → unparseable output in CI", "Forgetting CLAUDE.md still loads in -p mode"], examSignals: ["-p", "--print", "non-interactive", "--output-format json", "CI/CD integration"], @@ -583,18 +621,18 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Static analysis rules", "Content moderation thresholds", "Code review linters"], relatedConcepts: ["d4-few-shot", "d4-schema-design", "d5-confidence-review"], resources: [ - { label: "Anthropic: Prompt engineering", url: "https://platform.claude.com/docs", type: "docs" }, + { label: "Prompt Engineering Docs", url: "https://platform.claude.com/docs", type: "docs" }, ], failureModes: ["Vague 'be careful' instructions → inconsistent output", "High FP rate destroys trust even at high recall", "Disabling whole categories instead of tightening criteria"], examSignals: ["improve precision", "reduce false positives", "developer trust", "vague instructions"], }, { id: "d4-few-shot", name: "Few-Shot Prompting", domain: "D4", task: "4.2", - whyItMatters: "2-4 examples targeting AMBIGUOUS scenarios teach the model to generalize. Show WHY one action beats alternatives — demonstrate reasoning, not just format. This is how you handle edge cases you can't pre-enumerate.", + whyItMatters: "2-4 examples targeting AMBIGUOUS scenarios teach generalization. Show WHY one action beats alternatives — demonstrate reasoning, not just format. This is how you handle edge cases you can't pre-enumerate.", productionExamples: ["Classification with unusual edge cases", "Format demonstrations for custom output", "Reasoning patterns for judgment calls"], relatedConcepts: ["d4-explicit-criteria", "d4-schema-design", "d3-iterative-refinement"], resources: [ - { label: "Anthropic: Few-shot prompting", url: "https://platform.claude.com/docs", type: "docs" }, + { label: "Few-Shot Prompting Docs", url: "https://platform.claude.com/docs", type: "docs" }, ], failureModes: ["Examples only showing easy cases → fails on hard ones", "Examples showing output without reasoning → no generalization", "Too many examples (20+) → context bloat + overfitting"], examSignals: ["ambiguous cases", "show reasoning", "demonstrate format", "generalize to novel patterns"], @@ -605,14 +643,14 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Document extraction to structured DBs", "API-response normalization", "Invoice field extraction"], relatedConcepts: ["d4-schema-design", "d2-tool-distribution", "d4-batch-validation"], resources: [ - { label: "Anthropic: Tool use & structured outputs", url: "https://platform.claude.com/docs", type: "docs" }, + { label: "Tool Use & Structured Outputs Docs", url: "https://platform.claude.com/docs", type: "docs" }, ], failureModes: ["Required fields on optional data → fabricated values", "Trusting schema compliance for semantic correctness", "tool_choice='auto' when you need 'any' or 'forced'"], examSignals: ["JSON schema", "tool_use", "guaranteed compliance", "semantic errors persist"], }, { id: "d4-schema-design", name: "Schema Design", domain: "D4", task: "4.4", - whyItMatters: "Required vs optional fields matter: required fields on absent data force the model to fabricate. Enum + 'other' + detail string keeps categories extensible. Pydantic (or similar) adds semantic validation the JSON schema can't.", + whyItMatters: "Required vs optional fields matter: required fields on absent data force fabrication. Enum + 'other' + detail string keeps categories extensible. Pydantic (or similar) adds semantic validation the JSON schema can't.", productionExamples: ["Extensible categorization (enum + other + free text)", "Nullable fields for optional source data", "Pydantic models for cross-field validation"], relatedConcepts: ["d4-structured-output", "d4-batch-validation", "d5-error-propagation"], resources: [ @@ -627,18 +665,18 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Overnight batch classification of tickets", "Pre-merge blocking checks (sync, not batch)", "Validation-retry on failed extractions"], relatedConcepts: ["d4-structured-output", "d4-schema-design", "d2-structured-errors"], resources: [ - { label: "Anthropic: Message Batches API", url: "https://platform.claude.com/docs", type: "docs" }, + { label: "Message Batches API Docs", url: "https://platform.claude.com/docs", type: "docs" }, ], failureModes: ["Batch for blocking checks → devs wait 24hr", "Generic 'try again' retry → same failure", "Missing custom_id → can't correlate"], examSignals: ["cost savings", "pre-merge", "overnight", "batch processing", "validation-retry"], }, { id: "d4-multi-pass-review", name: "Multi-Pass Review", domain: "D4", task: "4.6", - whyItMatters: "A model that generated output retains reasoning context — it's biased toward its own work. An independent instance (without that context) catches what the generator missed. Per-file passes catch local issues; a cross-file pass catches integration bugs.", - productionExamples: ["Code review with separate Claude instance", "Per-file linting + cross-file consistency", "Confidence-based routing to human review"], + whyItMatters: "The generator retains its reasoning context — it's biased toward its own work. An independent review instance (without that context) catches what the generator missed. Per-file passes catch local issues; a cross-file pass catches integration bugs.", + productionExamples: ["Code review with a separate instance", "Per-file linting + cross-file consistency", "Confidence-based routing to human review"], relatedConcepts: ["d1-task-decomp", "d3-cli-cicd", "d5-confidence-review"], resources: [ - { label: "Anthropic: Self-review limitations", url: "https://platform.claude.com/docs", type: "docs" }, + { label: "Self-Review Limitations Docs", url: "https://platform.claude.com/docs", type: "docs" }, ], failureModes: ["Self-review in same session → confirms own reasoning", "Per-file only → integration bugs slip through", "No confidence routing → low-confidence outputs auto-accepted"], examSignals: ["review quality", "same session", "independent instance", "per-file + cross-file", "confidence routing"], @@ -652,7 +690,7 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Support tickets with preserved case facts", "Long debugging sessions with distilled findings", "Multi-issue conversations with structured layers"], relatedConcepts: ["d5-codebase-exploration", "d5-provenance", "d1-session-state"], resources: [ - { label: "Anthropic: Long context handling", url: "https://platform.claude.com/docs", type: "docs" }, + { label: "Long Context Handling Docs", url: "https://platform.claude.com/docs", type: "docs" }, ], failureModes: ["Progressive summarization → refund amounts go vague", "Accumulating verbose tool output → token bloat", "Key findings buried in middle → ignored"], examSignals: ["case facts block", "lost-in-the-middle", "progressive summarization", "trim before accumulate"], @@ -663,7 +701,7 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Support agents with clear escalation triggers", "Claims systems escalating on policy gaps", "Identity systems asking for specific IDs on collision"], relatedConcepts: ["d5-error-propagation", "d2-structured-errors", "d4-explicit-criteria"], resources: [ - { label: "Anthropic: Agent escalation patterns", url: "https://platform.claude.com/docs", type: "docs" }, + { label: "Escalation Patterns Docs", url: "https://platform.claude.com/docs", type: "docs" }, ], failureModes: ["Sentiment-based escalation → unreliable", "Heuristic-matching on ambiguous identifiers → wrong records", "Not honoring explicit human requests immediately"], examSignals: ["escalation triggers", "customer demands human", "policy gaps", "sentiment-based anti-pattern"], @@ -674,18 +712,18 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Research pipelines with coverage annotations", "Local retry + propagated-failure patterns", "Contested-findings sections in reports"], relatedConcepts: ["d2-structured-errors", "d5-provenance", "d1-agentic-loop"], resources: [ - { label: "Anthropic: Error handling patterns", url: "https://platform.claude.com/docs", type: "docs" }, + { label: "Error Handling Patterns Docs", url: "https://platform.claude.com/docs", type: "docs" }, ], failureModes: ["Generic 'failed' error → coordinator blind", "Propagating recoverable errors → unnecessary escalation", "No coverage annotations → findings indistinguishable from gaps"], examSignals: ["structured errors", "access failure vs empty", "local recovery", "coverage annotations"], }, { id: "d5-codebase-exploration", name: "Codebase Exploration Context", domain: "D5", task: "5.4", - whyItMatters: "Long exploration sessions degrade: Claude starts referencing 'typical patterns' instead of what it actually found. Scratchpad files persist key findings across context boundaries. `/compact` reduces context mid-session. State manifests enable crash recovery.", + whyItMatters: "Long exploration sessions degrade: answers start referencing 'typical patterns' instead of actual findings. Scratchpad files persist key findings across context boundaries. `/compact` reduces context mid-session. State manifests enable crash recovery.", productionExamples: ["Large codebase audits with scratchpads", "Long investigations using /compact", "Crash-recovery manifests for agent state"], relatedConcepts: ["d5-context-mgmt", "d1-session-state", "d2-builtin-tools"], resources: [ - { label: "Claude Code: Managing context", url: "https://code.claude.com/docs", type: "docs" }, + { label: "Managing Context Docs", url: "https://code.claude.com/docs", type: "docs" }, ], failureModes: ["No scratchpad → findings evaporate after /compact", "Inconsistent answers in extended sessions", "No crash recovery → restart from scratch"], examSignals: ["inconsistent answers", "typical patterns", "context degradation", "extended session", "scratchpad"], @@ -696,7 +734,7 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Stratified random sampling of extractions", "Per-doc-type accuracy dashboards", "Human review routing by confidence threshold"], relatedConcepts: ["d4-multi-pass-review", "d5-provenance", "d4-explicit-criteria"], resources: [ - { label: "Anthropic: Evaluating model outputs", url: "https://platform.claude.com/docs", type: "docs" }, + { label: "Evaluating Outputs Docs", url: "https://platform.claude.com/docs", type: "docs" }, ], failureModes: ["Aggregate accuracy hides per-type failures", "Trusting self-reported confidence without calibration", "No stratified sampling → systemic errors invisible"], examSignals: ["aggregate accuracy", "stratified sampling", "field-level confidence", "calibration"], @@ -707,7 +745,7 @@ const ENRICHED_CONCEPTS = [ productionExamples: ["Research reports with citation tracking", "Temporal-sensitive analysis with dates", "Contested-findings sections for disputed claims"], relatedConcepts: ["d5-error-propagation", "d5-confidence-review", "d1-subagent-mgmt"], resources: [ - { label: "Anthropic: Provenance & citations", url: "https://platform.claude.com/docs", type: "docs" }, + { label: "Provenance & Citations Docs", url: "https://platform.claude.com/docs", type: "docs" }, ], failureModes: ["Synthesis drops source attribution → unverifiable claims", "Arbitrary conflict resolution → silent bias", "No temporal data → stale findings presented as current"], examSignals: ["claim-source mappings", "temporal data", "conflict annotation", "contested findings"], @@ -767,7 +805,7 @@ const RULES = [ { num: 9, rule: "Independent Review > Self-Review", - detail: "A model retains reasoning context from generation, making it less likely to question its own decisions. A separate Claude instance (without prior reasoning) catches issues the generator misses.", + detail: "The generator retains reasoning context, making it less likely to question its own decisions. A separate instance (without prior reasoning) catches issues the generator misses.", signal: "Look for: 'review quality', 'same session', 'missed issues', 'inconsistent feedback'", }, { @@ -939,6 +977,9 @@ export default function App() { const [openDomain, setOpenDomain] = useState(0); const [openDay, setOpenDay] = useState(null); const [progress, setProgress] = useState(loadProgress); + const [theme, setTheme] = useState(loadTheme); + useEffect(() => { saveTheme(theme); }, [theme]); + const cycleTheme = () => setTheme(t => t === "dark" ? "grey" : t === "grey" ? "light" : "dark"); const toggleProgress = (key) => { setProgress(prev => { @@ -965,15 +1006,17 @@ export default function App() { const s = { root: { fontFamily: "'IBM Plex Mono', 'Menlo', 'Consolas', monospace", - background: "#0c0c10", - color: "#c8c8d0", + background: "var(--bg-root)", + color: "var(--text-body)", minHeight: "100vh", maxWidth: 520, margin: "0 auto", + ...themeVars(theme), }, header: { padding: "20px 16px 12px", - borderBottom: "1px solid #1c1c28", + borderBottom: "1px solid var(--border)", + position: "relative", }, tag: { display: "inline-block", @@ -987,18 +1030,18 @@ export default function App() { h1: { fontSize: 18, fontWeight: 700, - color: "#f0f0f0", + color: "var(--text-primary)", margin: 0, lineHeight: 1.3, }, sub: { fontSize: 11, - color: "#555", + color: "var(--text-faint)", marginTop: 4, }, tabs: { display: "flex", - borderBottom: "1px solid #1c1c28", + borderBottom: "1px solid var(--border)", padding: "0 4px", overflowX: "auto", }, @@ -1009,7 +1052,7 @@ export default function App() { fontFamily: "inherit", fontWeight: active ? 600 : 400, background: "none", - color: active ? "#f0f0f0" : "#4a4a58", + color: active ? "var(--text-primary)" : "var(--text-faint)", border: "none", borderBottom: active ? "2px solid #c0392b" : "2px solid transparent", cursor: "pointer", @@ -1022,11 +1065,27 @@ export default function App() { }, progressBar: { height: 3, - background: "#1a1a26", + background: "var(--border-soft)", borderRadius: 2, overflow: "hidden", marginTop: 8, }, + themeBtn: { + position: "absolute", + top: 16, + right: 14, + padding: "4px 8px", + fontSize: 9, + fontFamily: "inherit", + letterSpacing: 1, + textTransform: "uppercase", + fontWeight: 600, + background: "var(--bg-panel)", + color: "var(--text-soft)", + border: "1px solid var(--border)", + borderRadius: 3, + cursor: "pointer", + }, }; const overallDone = tasksDone + conceptsDone; @@ -1036,13 +1095,21 @@ export default function App() { return (
+
CCA-F Study Plan

4 Weeks to 900+

Scaled 100–1000 / 720 pass / 60 questions / 4 random scenarios

-
+
Overall: {overallDone}/{overallTotal} ({pct}%) Tasks: {tasksDone}/{tasksTotal} | Concepts: {conceptsDone}/{conceptsTotal}
@@ -1070,10 +1137,10 @@ export default function App() { borderTop: "1px solid #1c1c28", textAlign: "center", }}> -
+
Free resources only
-
+
Anthropic Skilljar · CertSafari · ClaudeCertifications · ReadRoost · Claude Partner Network · Anthropic GitHub
@@ -1114,7 +1181,7 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress,
{/* Weight bar */}
-
+
Domain weights
@@ -1123,7 +1190,7 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress, { w: 20, c: "#2980b9" }, { w: 15, c: "#8e44ad" }, ].map((d, i) =>
)}
-
+
D1 27%D2 18%D3 20%D4 20%D5 15%
@@ -1137,13 +1204,13 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress, style={{ width: "100%", padding: "12px 14px", - background: isOpen ? "#12121a" : "#0f0f16", + background: isOpen ? "var(--bg-panel-alt)" : "var(--bg-panel)", border: isOpen ? "1px solid #262636" : "1px solid #1a1a26", borderRadius: 6, cursor: "pointer", textAlign: "left", fontFamily: "inherit", - color: "#f0f0f0", + color: "var(--text-primary)", }} >
@@ -1152,9 +1219,9 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress, Week {week.id}
{week.title}
-
{week.subtitle}
+
{week.subtitle}
- {isOpen ? "−" : "+"} + {isOpen ? "−" : "+"}
{week.theme} @@ -1175,21 +1242,21 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress, width: "100%", padding: "10px 12px", background: dayOpen ? "#141420" : "#0e0e15", - border: `1px solid ${dayOpen ? "#262636" : "#1a1a26"}`, + border: `1px solid ${dayOpen ? "var(--border-med)" : "var(--border-soft)"}`, borderLeft: `3px solid ${dc}`, borderRadius: 4, cursor: "pointer", textAlign: "left", fontFamily: "inherit", - color: "#d0d0d8", + color: "var(--text-body)", }} >
{day.day} - {day.domain} + {day.domain}
- {dayOpen ? "−" : "+"} + {dayOpen ? "−" : "+"}
{day.focus} @@ -1204,7 +1271,7 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress, return (
-

+

Every testable concept organized by domain. Check off leaf nodes as you master them.

{domains.map((d, di) => { @@ -1313,14 +1380,14 @@ function TreeTab({ domains, openDomain, setOpenDomain, progress, toggle }) { style={{ width: "100%", padding: "11px 14px", - background: "#0f0f16", + background: "var(--bg-panel)", border: `1px solid ${d.accent}22`, borderLeft: `3px solid ${d.accent}`, borderRadius: 5, cursor: "pointer", textAlign: "left", fontFamily: "inherit", - color: "#e0e0e8", + color: "var(--text-primary)", fontSize: 12, fontWeight: 600, }} @@ -1329,8 +1396,8 @@ function TreeTab({ domains, openDomain, setOpenDomain, progress, toggle }) { {d.domain}
{d.weight} - {domainDone}/{domainTotal} - {isOpen ? "−" : "+"} + {domainDone}/{domainTotal} + {isOpen ? "−" : "+"}
@@ -1357,7 +1424,7 @@ function TreeTab({ domains, openDomain, setOpenDomain, progress, toggle }) { return (
-

+

Radial view of all 30 concepts across 5 domains. Arcs show cross-domain relationships. Click any concept for production context.

@@ -1482,8 +1549,8 @@ function BrainMapTab({ progress, toggle }) { aria-label="CCA Foundations concept brain map" > {/* Background rings (faint guides) */} - - + + {/* Cross-domain arcs */} {arcs.map((arc, i) => { @@ -1493,7 +1560,7 @@ function BrainMapTab({ progress, toggle }) { const mx = (a.x + b.x) / 2, my = (a.y + b.y) / 2; const pullX = cx + (mx - cx) * 0.25; const pullY = cy + (my - cy) * 0.25; - const stroke = isActive ? LABEL_COLORS[a.domain] : "#2a2a38"; + const stroke = isActive ? LABEL_COLORS[a.domain] : "var(--border-med)"; const opacity = activeId ? (isActive ? 0.55 : 0.05) : 0.15; return ( - - CCA-F - FOUNDATIONS + + CCA-F + FOUNDATIONS {/* Concept nodes */} @@ -1572,7 +1639,7 @@ function BrainMapTab({ progress, toggle }) { const dim = activeId && !(activeId === c.id || isRelated); const isComplete = !!progress[`bm-${c.id}`]; const nodeR = isSelected ? 10 : isHovered ? 9 : 7; - const fill = isComplete ? "#0c0c10" : DOMAIN_FILL[c.domain]; + const fill = isComplete ? "var(--bg-root)" : DOMAIN_FILL[c.domain]; const strokeW = isSelected ? 2.5 : isComplete ? 2 : 1; const labelOutside = c.x >= cx - 20; return ( @@ -1594,7 +1661,7 @@ function BrainMapTab({ progress, toggle }) { y={c.y} textAnchor={labelOutside ? "start" : "end"} dominantBaseline="middle" - fill={isSelected || isHovered ? "#f0f0f0" : "#9a9aa8"} + fill={isSelected || isHovered ? "var(--text-primary)" : "var(--text-soft)"} fontSize={9} fontWeight={isSelected || isHovered ? 600 : 400} style={{ pointerEvents: "none", fontFamily: "inherit" }} @@ -1607,14 +1674,14 @@ function BrainMapTab({ progress, toggle }) { {/* Legend */} -
+
{domainIds.map(did => ( {did} {DOMAIN_WEIGHTS[did]}% ))} - + Hollow = mastered · Click a node
@@ -1649,7 +1716,7 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate style={{ marginTop: 16, padding: "16px", - background: "#0f0f16", + background: "var(--bg-panel)", border: `1px solid ${domainFill}33`, borderLeft: `3px solid ${domainFill}`, borderRadius: 6, @@ -1663,9 +1730,9 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate color: "#0c0c10", background: labelColor, padding: "2px 6px", borderRadius: 3, }}>{concept.domain} - TASK {concept.task} + TASK {concept.task}
-

+

{concept.name}

@@ -1676,8 +1743,8 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate style={{ fontSize: 9, padding: "4px 8px", background: isComplete ? `${domainFill}22` : "transparent", - color: isComplete ? labelColor : "#5a5a68", - border: `1px solid ${isComplete ? domainFill : "#2a2a38"}`, + color: isComplete ? labelColor : "var(--text-faint)", + border: `1px solid ${isComplete ? domainFill : "var(--border-med)"}`, borderRadius: 3, cursor: "pointer", fontFamily: "inherit", }} > @@ -1688,7 +1755,7 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate aria-label="Close details" style={{ fontSize: 14, padding: "2px 8px", - background: "transparent", color: "#5a5a68", + background: "transparent", color: "var(--text-faint)", border: "1px solid #2a2a38", borderRadius: 3, cursor: "pointer", fontFamily: "inherit", }} @@ -1697,11 +1764,11 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate
-

{concept.whyItMatters}

+

{concept.whyItMatters}

-
    +
      {concept.productionExamples.map((ex, i) =>
    • {ex}
    • )}
    @@ -1736,25 +1803,25 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate target="_blank" rel="noopener noreferrer" style={{ - fontSize: 10, color: "#c8c8d0", + fontSize: 10, color: "var(--text-body)", textDecoration: "none", lineHeight: 1.4, display: "flex", alignItems: "center", gap: 6, }} > {r.type} {r.label} - + ))}
-
    +
      {concept.failureModes.map((fm, i) =>
    • {fm}
    • )}
    @@ -1764,7 +1831,7 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate {concept.examSignals.map((sig, i) => ( "{sig}" @@ -1791,29 +1858,29 @@ function DetailSection({ label, color, children, last }) { function BuildsTab({ projects }) { return (
    -

    +

    Each project maps to exam scenarios. Building these gives you muscle memory for the tradeoffs the exam tests.

    {projects.map((p, pi) => (
    -
    {p.name}
    -
    +
    {p.name}
    +
    {p.scenario} · {p.domains}
    -
    +
    {p.desc}
    {p.skills.map((sk, si) => (
    -

    +

    These 11 decision rules help eliminate distractors instantly. Internalize them as reflexes.

    {rules.map((r) => (
    {r.num} - {r.rule} + {r.rule}
    -
    +
    {r.detail}
    -

    +

    Quick-scan decision reference aligned with the 30 exam task statements. If X → then Y.

    {data.map((section, si) => ( @@ -1896,7 +1963,7 @@ function CheatSheetTab({ data }) { {section.category}
    (
    Date: Wed, 8 Apr 2026 11:03:44 +0800 Subject: [PATCH 3/4] Scale layout for desktop: widen container, bump fonts, add breathing room MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - maxWidth 520 → 860 so the app fills a laptop screen instead of looking like a phone column - Base font sizes bumped +2px across the board (9→11, 10→12, 11→13, 13→14, 14→15) — keeps the visual hierarchy intact while making everything readable at arm's length - Card padding widened (12px → 16-20px), border radius increased - Header, tabs, and progress bar scaled proportionally - Theme toggle button and footer spacing adjusted for the wider layout - Day cards use themed backgrounds instead of hardcoded dark hex values --- src/App.jsx | 148 ++++++++++++++++++++++++++-------------------------- 1 file changed, 75 insertions(+), 73 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index dbfd82a..13ad360 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -787,7 +787,7 @@ const RULES = [ { num: 6, rule: "Nullable Prevents Hallucination", - detail: "Make schema fields optional/nullable when the source may not contain the info. Required fields on absent data forces the model to fabricate values to satisfy the schema.", + detail: "Make schema fields optional/nullable when the source may not contain the info. Required fields on absent data forces fabrication to satisfy the schema.", signal: "Look for: 'fabricated values', 'hallucination', 'missing information'", }, { @@ -849,11 +849,11 @@ const PROJECTS = [ name: "CI/CD Code Review Pipeline", scenario: "Scenario 5", domains: "D3 + D4", - desc: "Claude Code in non-interactive CI mode with structured output, explicit review criteria, and multi-pass architecture", + desc: "Non-interactive CI mode with structured output, explicit review criteria, and multi-pass architecture", skills: ["-p flag non-interactive mode", "--output-format json", "Explicit review criteria", "Multi-pass: per-file + cross-file", "False positive category management"], }, { - name: "Claude Code Team Workflow", + name: "Team Workflow", scenario: "Scenario 2", domains: "D3 + D5", desc: "Configure CLAUDE.md hierarchy, path-scoped rules, custom skills with context: fork, MCP server integration, and iterative refinement patterns", @@ -877,7 +877,7 @@ const CHEAT_SHEET = [ { category: "Tool Design Decisions", items: [ - "Generic tool confusing the model → split into purpose-specific tools with distinct descriptions", + "Generic tool causing confusion → split into purpose-specific tools with distinct descriptions", "Similar tools misrouted → rename + expand descriptions with boundaries and examples", "Agent has 18 tools → scope to 4-5 per role; add cross-role tools for high-frequency needs only", "MCP tools = actions (create, update, query); MCP resources = content catalogs (browse without calling)", @@ -1009,46 +1009,48 @@ export default function App() { background: "var(--bg-root)", color: "var(--text-body)", minHeight: "100vh", - maxWidth: 520, + maxWidth: 860, margin: "0 auto", + padding: "0 24px", ...themeVars(theme), }, header: { - padding: "20px 16px 12px", + padding: "28px 0 16px", borderBottom: "1px solid var(--border)", position: "relative", }, tag: { display: "inline-block", - fontSize: 9, + fontSize: 11, letterSpacing: 2.5, color: "#c0392b", textTransform: "uppercase", fontWeight: 600, - marginBottom: 6, + marginBottom: 8, }, h1: { - fontSize: 18, + fontSize: 24, fontWeight: 700, color: "var(--text-primary)", margin: 0, lineHeight: 1.3, }, sub: { - fontSize: 11, + fontSize: 15, color: "var(--text-faint)", - marginTop: 4, + marginTop: 6, }, tabs: { display: "flex", borderBottom: "1px solid var(--border)", - padding: "0 4px", + padding: "0 0", overflowX: "auto", + gap: 4, }, tabBtn: (active) => ({ flex: 1, - padding: "10px 4px", - fontSize: 9, + padding: "12px 8px", + fontSize: 12, fontFamily: "inherit", fontWeight: active ? 600 : 400, background: "none", @@ -1061,21 +1063,21 @@ export default function App() { whiteSpace: "nowrap", }), content: { - padding: "12px 16px 24px", + padding: "20px 0 32px", }, progressBar: { - height: 3, + height: 4, background: "var(--border-soft)", borderRadius: 2, overflow: "hidden", - marginTop: 8, + marginTop: 10, }, themeBtn: { position: "absolute", - top: 16, - right: 14, - padding: "4px 8px", - fontSize: 9, + top: 24, + right: 0, + padding: "6px 12px", + fontSize: 11, fontFamily: "inherit", letterSpacing: 1, textTransform: "uppercase", @@ -1083,7 +1085,7 @@ export default function App() { background: "var(--bg-panel)", color: "var(--text-soft)", border: "1px solid var(--border)", - borderRadius: 3, + borderRadius: 4, cursor: "pointer", }, }; @@ -1109,7 +1111,7 @@ export default function App() {
    -
    +
    Overall: {overallDone}/{overallTotal} ({pct}%) Tasks: {tasksDone}/{tasksTotal} | Concepts: {conceptsDone}/{conceptsTotal}
    @@ -1133,15 +1135,15 @@ export default function App() {
    -
    +
    Free resources only
    -
    - Anthropic Skilljar · CertSafari · ClaudeCertifications · ReadRoost · Claude Partner Network · Anthropic GitHub +
    + Skilljar · CertSafari · CCA Certifications · ReadRoost · Partner Network · GitHub
    @@ -1164,7 +1166,7 @@ function Check({ checked, onToggle }) { background: checked ? "#c0392b22" : "transparent", cursor: "pointer", flexShrink: 0, - fontSize: 9, + fontSize: 11, color: checked ? "#c0392b" : "transparent", marginRight: 8, transition: "all 0.15s", @@ -1181,7 +1183,7 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress,
    {/* Weight bar */}
    -
    +
    Domain weights
    @@ -1190,7 +1192,7 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress, { w: 20, c: "#2980b9" }, { w: 15, c: "#8e44ad" }, ].map((d, i) =>
    )}
    -
    +
    D1 27%D2 18%D3 20%D4 20%D5 15%
    @@ -1198,15 +1200,15 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress, {weeks.map(week => { const isOpen = openWeek === week.id; return ( -
    +
    @@ -1240,11 +1242,11 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress, onClick={() => setOpenDay(dayOpen ? null : dayKey)} style={{ width: "100%", - padding: "10px 12px", - background: dayOpen ? "#141420" : "#0e0e15", + padding: "12px 16px", + background: dayOpen ? "var(--bg-panel-alt)" : "var(--bg-panel)", border: `1px solid ${dayOpen ? "var(--border-med)" : "var(--border-soft)"}`, borderLeft: `3px solid ${dc}`, - borderRadius: 4, + borderRadius: 6, cursor: "pointer", textAlign: "left", fontFamily: "inherit", @@ -1253,8 +1255,8 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress, >
    - {day.day} - {day.domain} + {day.day} + {day.domain}
    {dayOpen ? "−" : "+"}
    @@ -1311,11 +1313,11 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress, background: "#f39c120a", border: "1px solid #f39c1218", borderRadius: 4, - fontSize: 10, + fontSize: 12, color: "#f39c12", lineHeight: 1.5, }}> - KPI: + KPI: {day.kpi}
    )} @@ -1328,7 +1330,7 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress, background: "#2980b90a", border: "1px solid #2980b918", borderRadius: 4, - fontSize: 10, + fontSize: 12, color: "#2980b9", lineHeight: 1.5, display: "flex", @@ -1337,7 +1339,7 @@ function PlanTab({ weeks, openWeek, setOpenWeek, openDay, setOpenDay, progress, }}> {day.closure.label} setOpenDomain(isOpen ? null : di)} style={{ width: "100%", - padding: "11px 14px", + padding: "14px 18px", background: "var(--bg-panel)", border: `1px solid ${d.accent}22`, borderLeft: `3px solid ${d.accent}`, @@ -1395,8 +1397,8 @@ function TreeTab({ domains, openDomain, setOpenDomain, progress, toggle }) {
    {d.domain}
    - {d.weight} - {domainDone}/{domainTotal} + {d.weight} + {domainDone}/{domainTotal} {isOpen ? "−" : "+"}
    @@ -1423,7 +1425,7 @@ function TreeTab({ domains, openDomain, setOpenDomain, progress, toggle }) { const key = `c-${di}-${ci}-${ki}`; return (
    {/* Legend */} -
    +
    {domainIds.map(did => ( @@ -1714,8 +1716,8 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate role="dialog" aria-labelledby={`bm-title-${concept.id}`} style={{ - marginTop: 16, - padding: "16px", + marginTop: 20, + padding: "20px 24px", background: "var(--bg-panel)", border: `1px solid ${domainFill}33`, borderLeft: `3px solid ${domainFill}`, @@ -1730,7 +1732,7 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate color: "#0c0c10", background: labelColor, padding: "2px 6px", borderRadius: 3, }}>{concept.domain} - TASK {concept.task} + TASK {concept.task}

    {concept.name} @@ -1741,7 +1743,7 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate onClick={() => toggle(`bm-${concept.id}`)} aria-label={isComplete ? "Mark incomplete" : "Mark complete"} style={{ - fontSize: 9, padding: "4px 8px", + fontSize: 11, padding: "4px 8px", background: isComplete ? `${domainFill}22` : "transparent", color: isComplete ? labelColor : "var(--text-faint)", border: `1px solid ${isComplete ? domainFill : "var(--border-med)"}`, @@ -1754,7 +1756,7 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate onClick={onClose} aria-label="Close details" style={{ - fontSize: 14, padding: "2px 8px", + fontSize: 15, padding: "2px 8px", background: "transparent", color: "var(--text-faint)", border: "1px solid #2a2a38", borderRadius: 3, cursor: "pointer", fontFamily: "inherit", @@ -1768,7 +1770,7 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate -
      +
        {concept.productionExamples.map((ex, i) =>
      • {ex}
      • )}
      @@ -1781,7 +1783,7 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate key={r.id} onClick={() => onNavigate(r.id)} style={{ - fontSize: 9, padding: "3px 7px", + fontSize: 11, padding: "3px 7px", background: "transparent", color: LABEL_COLORS[r.domain], border: `1px solid ${DOMAIN_FILL[r.domain]}55`, borderRadius: 3, cursor: "pointer", fontFamily: "inherit", @@ -1803,7 +1805,7 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate target="_blank" rel="noopener noreferrer" style={{ - fontSize: 10, color: "var(--text-body)", + fontSize: 12, color: "var(--text-body)", textDecoration: "none", lineHeight: 1.4, display: "flex", alignItems: "center", gap: 6, }} @@ -1821,7 +1823,7 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate -
        +
          {concept.failureModes.map((fm, i) =>
        • {fm}
        • )}
        @@ -1830,7 +1832,7 @@ function DetailPanel({ concept, concepts, progress, toggle, onClose, onNavigate
        {concept.examSignals.map((sig, i) => ( {projects.map((p, pi) => (
        -
        {p.name}
        -
        +
        {p.name}
        +
        {p.scenario} · {p.domains}
        @@ -1879,7 +1881,7 @@ function BuildsTab({ projects }) {
        {p.skills.map((sk, si) => (
        {rules.map((r) => (
        {r.num} - {r.rule} + {r.rule}
        {r.detail}
        (
        {section.items.map((item, ii) => (
        Date: Wed, 8 Apr 2026 19:51:12 +0800 Subject: [PATCH 4/4] Add GitHub Pages deployment: auto-deploy both apps on push to main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GitHub Actions workflow builds study plan (root) and quiz engine (quiz/), merges outputs into a single dist, deploys via Pages - Vite base paths set to /cca-study-plan/ and /cca-study-plan/quiz/ so assets resolve correctly on GitHub Pages After merging to main, enable Pages in repo Settings → Pages → Source: GitHub Actions. Study plan: https://warfield2016.github.io/cca-study-plan/ Quiz engine: https://warfield2016.github.io/cca-study-plan/quiz/ --- .github/workflows/deploy.yml | 50 ++++++++++++++++++++++++++++++++++++ quiz/vite.config.js | 1 + vite.config.js | 1 + 3 files changed, 52 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..3c78088 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,50 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: [main] + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + # Build study plan (root) + - run: npm ci + - run: npm run build + + # Build quiz engine (quiz/) + - run: cd quiz && npm ci && npm run build + + # Merge both builds into one output + - run: | + mkdir -p dist/quiz + cp -r quiz/dist/* dist/quiz/ + + - uses: actions/upload-pages-artifact@v3 + with: + path: dist + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/quiz/vite.config.js b/quiz/vite.config.js index 9ffcc67..f729314 100644 --- a/quiz/vite.config.js +++ b/quiz/vite.config.js @@ -3,4 +3,5 @@ import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], + base: '/cca-study-plan/quiz/', }) diff --git a/vite.config.js b/vite.config.js index 9ffcc67..310f6c6 100644 --- a/vite.config.js +++ b/vite.config.js @@ -3,4 +3,5 @@ import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], + base: '/cca-study-plan/', })