Page Component Mapper
Given a route, produce a depth-limited component tree with file paths, server/client boundaries, props, hooks, state management, data fetching patterns, and styling metadata. Output a ComponentMap artifact consumed by all downstream skills.
Scope Constraints
- Read-only access to source files,
tsconfig.json, package.json, next.config.*
- Write access limited to
.claude/qa-cache/component-maps/ for ComponentMap persistence
- Does not modify source code, run test commands, or execute build tools
- Traces imports up to configurable depth limit (default 3) — stops at external packages and depth boundary
Inputs
- Route path (required): The Next.js route to map (e.g.,
/dashboard/settings)
- Depth (optional, default 3): Maximum import tracing depth. Override with
--depth N
- Fresh flag (optional):
--fresh to bypass cache and rebuild
Input Sanitization
- Route paths are used for file resolution via
app/{route}/page.tsx or pages/{route}.tsx. Validate that the route contains only alphanumeric characters, hyphens, underscores, forward slashes, and brackets (for dynamic segments like [id]). Reject routes containing .., shell metacharacters, or null bytes.
- Depth values must be positive integers between 1 and 10.
Procedure
Copy this checklist and update as you complete each step:
Progress:
- [ ] Step 0: Check Cache
- [ ] Step 1: Resolve tsconfig Path Aliases
- [ ] Step 2: Identify Route Entry Points
- [ ] Step 3: Trace Imports (Depth-Limited)
- [ ] Step 4: Classify Completeness
- [ ] Step 5: Persist and Present
Note: If you've lost context of previous steps (e.g., after context compaction), check the progress checklist above. Resume from the last unchecked item. Re-read relevant reference files if needed.
Step 0: Check Cache
- Look for
.claude/qa-cache/component-maps/{route-slug}.json.
- If found, follow the caching protocol in
references/caching-protocol.md.
- If cache is valid, return the cached map and print:
Using cached map ({age} old, {N} components, completeness: {level}) | --fresh to rebuild
- If
--fresh flag provided, skip cache entirely.
Step 1: Resolve tsconfig Path Aliases (PREREQUISITE)
- Read
tsconfig.json at the project root. If it has an extends field, read the parent config too.
- Extract
compilerOptions.paths and compilerOptions.baseUrl.
- Build a lookup table mapping each alias pattern to its resolved directory.
Example:
{ "@/*": "./src/*", "~/lib/*": "./lib/*" }.
- Keep this table in working memory -- use it to resolve every aliased import in Steps 2-3.
- If
tsconfig.json is missing, warn the user and proceed with relative-path-only resolution.
Step 2: Identify Route Entry Points
App Router (default -- app/ directory exists):
- Locate
app/{route}/page.tsx (or .ts, .jsx, .js).
- Also locate if present:
layout.tsx, loading.tsx, error.tsx, not-found.tsx in the same directory and parent directories up to app/layout.tsx.
- The trace order is: root layout -> nested layouts -> loading -> error -> page.
Pages Router (fallback -- pages/ directory, no app/):
- Locate
pages/{route}.tsx (or pages/{route}/index.tsx).
- Also check
pages/_app.tsx and pages/_document.tsx.
- Look for
getServerSideProps or getStaticProps exports.
Step 3: Trace Imports (Depth-Limited)
Default depth: 3 levels from route entry. Override with --depth N.
For each file, starting from the entry points found in Step 2:
- Read the file. Scan the first 5 lines for
'use client' or 'use server' directives.
- Extract all import statements. For each import, resolve the target:
- Relative (
./Button): try .tsx, .ts, .jsx, .js, /index.tsx, /index.ts
- Aliased (
@/components/Button): resolve using the Step 1 lookup table, then try extensions
- External (
react, next/link, @radix-ui/*): record as external, stop tracing
- Dynamic (
next/dynamic(() => import(...)), React.lazy): trace literal paths, flag isDynamic: true
- Barrel (
import { X } from './components'): follow re-export through index files, cap at 5 levels
- For detailed resolution rules and edge cases, read
references/import-tracing-protocol.md.
- For each resolved component file, extract:
displayName: the component function/const name
boundary: "server" (has 'use server' or in app/ without directive), "client" (has 'use client'), "shared" (no directive, outside app/)
props: from TypeScript type annotation on the component parameter
hooks: all useXxx( calls
stateManagement: imports from zustand, jotai, recoil, @tanstack/react-query, or React context usage
dataFetching: fetch() calls, server actions, tRPC calls, React Query hooks, SWR hooks
styling_approach: based on import type (.module.css -> css-modules, tailwind classes in JSX -> tailwind)
has_conditional_classes: clsx(, cn(, classNames(, or ternary in className
design_system_component: true if imported from a known UI library
accepts_className_prop: true if props type includes className
layout_role: infer from file location and component name
- If at the depth limit, record remaining imports with
unresolved: true, unresolvedReason: "depth-limit".
Step 4: Classify Completeness
"full": unresolvedImports array is empty
"shallow": all unresolved entries have reason "depth-limit"
"partial": any unresolved entry has a non-depth reason ("dynamic-computed", "barrel-depth-exceeded", "not-found")
Step 5: Persist and Present
- Collect all resolved file paths into
cachedFiles array.
- Write the ComponentMap to
.claude/qa-cache/component-maps/{route-slug}.json.
- Present a summary to the user:
Component Map: /dashboard/settings (depth 3, shallow)
Entry: app/dashboard/settings/page.tsx [server]
Layout: app/dashboard/layout.tsx [server]
23 components mapped | 4 unresolved (depth-limit) | 2 external (shadcn)
Server (8): SettingsPage, SettingsLayout, AccountSection, ...
Client (12): ThemeToggle, NotificationForm, ProfileEditor, ...
Shared (3): Button, Card, Input
Data flow: 2 server actions, 1 React Query hook, 3 prop chains
Hooks: useState(5), useEffect(3), useForm(2), useQuery(1)
- Wait for user confirmation before the coordinator proceeds to diagnosis.
Output Format
The ComponentMap JSON follows the schema in the design document. Key fields:
{
"version": "1.0",
"route": "/dashboard/settings",
"depth": 3,
"completeness": "shallow",
"framework": { "name": "next", "routerType": "app", "version": "14.1.0" },
"entryComponent": {
"filePath": "/abs/path/app/dashboard/settings/page.tsx",
"displayName": "SettingsPage",
"boundary": "server",
"imports": [{ "targetPath": "...", "importType": "component" }]
},
"unresolvedImports": [{ "sourcePath": "...", "importSpecifier": "./DeepChild", "reason": "depth-limit" }],
"cachedFiles": ["..."]
}
Handoff
Pass the ComponentMap artifact path to qa-coordinator. The map contains the full component tree with boundary, hooks, dataFetching, and styling_approach fields consumed by ui-bug-investigator and css-layout-debugger.
References
| Path |
Load Condition |
Content Summary |
references/caching-protocol.md |
Step 0, when cache file exists |
Cache validity rules, TTL, invalidation triggers |
references/import-tracing-protocol.md |
Step 3, always |
Path alias resolution, barrel handling, dynamic imports, boundary rules |
1---2name: page-component-mapper3description: >4 Use when investigating any frontend issue before diagnosis or debugging — maps a5 Next.js route to its full component tree. Resolves "what components render on this6 page," "show me the component tree for /dashboard," or "trace imports for this7 route." Produces a ComponentMap artifact with server/client boundaries, props, hooks,8 data fetching, and styling metadata consumed by all downstream QA skills. Not for9 diagnosing bugs, fixing code, or generating tests — those are handled by specialist10 skills after mapping is complete.11model:12 preferred: sonnet13 acceptable: [sonnet, opus]14 minimum: sonnet15 allow_downgrade: false16 reasoning_demand: medium17---1819# Page Component Mapper2021Given a route, produce a depth-limited component tree with file paths, server/client boundaries, props, hooks, state management, data fetching patterns, and styling metadata. Output a ComponentMap artifact consumed by all downstream skills.2223## Scope Constraints2425- Read-only access to source files, `tsconfig.json`, `package.json`, `next.config.*`26- Write access limited to `.claude/qa-cache/component-maps/` for ComponentMap persistence27- Does not modify source code, run test commands, or execute build tools28- Traces imports up to configurable depth limit (default 3) — stops at external packages and depth boundary2930## Inputs3132- **Route path** (required): The Next.js route to map (e.g., `/dashboard/settings`)33- **Depth** (optional, default 3): Maximum import tracing depth. Override with `--depth N`34- **Fresh flag** (optional): `--fresh` to bypass cache and rebuild3536## Input Sanitization3738- Route paths are used for file resolution via `app/{route}/page.tsx` or `pages/{route}.tsx`. Validate that the route contains only alphanumeric characters, hyphens, underscores, forward slashes, and brackets (for dynamic segments like `[id]`). Reject routes containing `..`, shell metacharacters, or null bytes.39- Depth values must be positive integers between 1 and 10.4041## Procedure4243Copy this checklist and update as you complete each step:44```45Progress:46- [ ] Step 0: Check Cache47- [ ] Step 1: Resolve tsconfig Path Aliases48- [ ] Step 2: Identify Route Entry Points49- [ ] Step 3: Trace Imports (Depth-Limited)50- [ ] Step 4: Classify Completeness51- [ ] Step 5: Persist and Present52```5354Note: If you've lost context of previous steps (e.g., after context compaction), check the progress checklist above. Resume from the last unchecked item. Re-read relevant reference files if needed.5556### Step 0: Check Cache57581. Look for `.claude/qa-cache/component-maps/{route-slug}.json`.592. If found, follow the caching protocol in `references/caching-protocol.md`.603. If cache is valid, return the cached map and print:61 `Using cached map ({age} old, {N} components, completeness: {level}) | --fresh to rebuild`624. If `--fresh` flag provided, skip cache entirely.6364### Step 1: Resolve tsconfig Path Aliases (PREREQUISITE)65661. Read `tsconfig.json` at the project root. If it has an `extends` field, read the parent config too.672. Extract `compilerOptions.paths` and `compilerOptions.baseUrl`.683. Build a lookup table mapping each alias pattern to its resolved directory.69 Example: `{ "@/*": "./src/*", "~/lib/*": "./lib/*" }`.704. Keep this table in working memory -- use it to resolve every aliased import in Steps 2-3.715. If `tsconfig.json` is missing, warn the user and proceed with relative-path-only resolution.7273### Step 2: Identify Route Entry Points7475**App Router** (default -- `app/` directory exists):761. Locate `app/{route}/page.tsx` (or `.ts`, `.jsx`, `.js`).772. Also locate if present: `layout.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx` in the same directory and parent directories up to `app/layout.tsx`.783. The trace order is: root layout -> nested layouts -> loading -> error -> page.7980**Pages Router** (fallback -- `pages/` directory, no `app/`):811. Locate `pages/{route}.tsx` (or `pages/{route}/index.tsx`).822. Also check `pages/_app.tsx` and `pages/_document.tsx`.833. Look for `getServerSideProps` or `getStaticProps` exports.8485### Step 3: Trace Imports (Depth-Limited)8687Default depth: **3 levels** from route entry. Override with `--depth N`.8889For each file, starting from the entry points found in Step 2:90911. Read the file. Scan the first 5 lines for `'use client'` or `'use server'` directives.922. Extract all import statements. For each import, resolve the target:93 - **Relative** (`./Button`): try `.tsx`, `.ts`, `.jsx`, `.js`, `/index.tsx`, `/index.ts`94 - **Aliased** (`@/components/Button`): resolve using the Step 1 lookup table, then try extensions95 - **External** (`react`, `next/link`, `@radix-ui/*`): record as external, stop tracing96 - **Dynamic** (`next/dynamic(() => import(...))`, `React.lazy`): trace literal paths, flag `isDynamic: true`97 - **Barrel** (`import { X } from './components'`): follow re-export through index files, cap at 5 levels983. For detailed resolution rules and edge cases, read `references/import-tracing-protocol.md`.994. For each resolved component file, extract:100 - `displayName`: the component function/const name101 - `boundary`: `"server"` (has `'use server'` or in `app/` without directive), `"client"` (has `'use client'`), `"shared"` (no directive, outside `app/`)102 - `props`: from TypeScript type annotation on the component parameter103 - `hooks`: all `useXxx(` calls104 - `stateManagement`: imports from `zustand`, `jotai`, `recoil`, `@tanstack/react-query`, or React context usage105 - `dataFetching`: `fetch()` calls, server actions, tRPC calls, React Query hooks, SWR hooks106 - `styling_approach`: based on import type (`.module.css` -> css-modules, tailwind classes in JSX -> tailwind)107 - `has_conditional_classes`: `clsx(`, `cn(`, `classNames(`, or ternary in className108 - `design_system_component`: true if imported from a known UI library109 - `accepts_className_prop`: true if props type includes `className`110 - `layout_role`: infer from file location and component name1115. If at the depth limit, record remaining imports with `unresolved: true, unresolvedReason: "depth-limit"`.112113### Step 4: Classify Completeness114115- `"full"`: `unresolvedImports` array is empty116- `"shallow"`: all unresolved entries have reason `"depth-limit"`117- `"partial"`: any unresolved entry has a non-depth reason (`"dynamic-computed"`, `"barrel-depth-exceeded"`, `"not-found"`)118119### Step 5: Persist and Present1201211. Collect all resolved file paths into `cachedFiles` array.1222. Write the ComponentMap to `.claude/qa-cache/component-maps/{route-slug}.json`.1233. Present a summary to the user:124125```126Component Map: /dashboard/settings (depth 3, shallow)127Entry: app/dashboard/settings/page.tsx [server]128 Layout: app/dashboard/layout.tsx [server]12913023 components mapped | 4 unresolved (depth-limit) | 2 external (shadcn)131132Server (8): SettingsPage, SettingsLayout, AccountSection, ...133Client (12): ThemeToggle, NotificationForm, ProfileEditor, ...134Shared (3): Button, Card, Input135136Data flow: 2 server actions, 1 React Query hook, 3 prop chains137Hooks: useState(5), useEffect(3), useForm(2), useQuery(1)138```1391404. Wait for user confirmation before the coordinator proceeds to diagnosis.141142## Output Format143144The ComponentMap JSON follows the schema in the design document. Key fields:145146```json147{148 "version": "1.0",149 "route": "/dashboard/settings",150 "depth": 3,151 "completeness": "shallow",152 "framework": { "name": "next", "routerType": "app", "version": "14.1.0" },153 "entryComponent": {154 "filePath": "/abs/path/app/dashboard/settings/page.tsx",155 "displayName": "SettingsPage",156 "boundary": "server",157 "imports": [{ "targetPath": "...", "importType": "component" }]158 },159 "unresolvedImports": [{ "sourcePath": "...", "importSpecifier": "./DeepChild", "reason": "depth-limit" }],160 "cachedFiles": ["..."]161}162```163164## Handoff165166Pass the ComponentMap artifact path to qa-coordinator. The map contains the full component tree with `boundary`, `hooks`, `dataFetching`, and `styling_approach` fields consumed by `ui-bug-investigator` and `css-layout-debugger`.167168## References169170| Path | Load Condition | Content Summary |171|------|---------------|-----------------|172| `references/caching-protocol.md` | Step 0, when cache file exists | Cache validity rules, TTL, invalidation triggers |173| `references/import-tracing-protocol.md` | Step 3, always | Path alias resolution, barrel handling, dynamic imports, boundary rules |