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: Use when investigating any frontend issue before diagnosis or debugging — maps a Next.js route to its full component tree. Resolves "what components render on this page," "show me the component tree for /dashboard," or "trace imports for this route." Produces a ComponentMap artifact with server/client boundaries, props, hooks, data fetching, and styling metadata consumed by all downstream QA skills. Not for diagnosing bugs, fixing code, or generating tests — those are handled by specialist skills after mapping is complete.4---56# Page Component Mapper78Given 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.910## Scope Constraints1112- Read-only access to source files, `tsconfig.json`, `package.json`, `next.config.*`13- Write access limited to `.claude/qa-cache/component-maps/` for ComponentMap persistence14- Does not modify source code, run test commands, or execute build tools15- Traces imports up to configurable depth limit (default 3) — stops at external packages and depth boundary1617## Inputs1819- **Route path** (required): The Next.js route to map (e.g., `/dashboard/settings`)20- **Depth** (optional, default 3): Maximum import tracing depth. Override with `--depth N`21- **Fresh flag** (optional): `--fresh` to bypass cache and rebuild2223## Input Sanitization2425- 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.26- Depth values must be positive integers between 1 and 10.2728## Procedure2930Copy this checklist and update as you complete each step:31```32Progress:33- [ ] Step 0: Check Cache34- [ ] Step 1: Resolve tsconfig Path Aliases35- [ ] Step 2: Identify Route Entry Points36- [ ] Step 3: Trace Imports (Depth-Limited)37- [ ] Step 4: Classify Completeness38- [ ] Step 5: Persist and Present39```4041Note: 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.4243### Step 0: Check Cache44451. Look for `.claude/qa-cache/component-maps/{route-slug}.json`.462. If found, follow the caching protocol in `references/caching-protocol.md`.473. If cache is valid, return the cached map and print:48 `Using cached map ({age} old, {N} components, completeness: {level}) | --fresh to rebuild`494. If `--fresh` flag provided, skip cache entirely.5051### Step 1: Resolve tsconfig Path Aliases (PREREQUISITE)52531. Read `tsconfig.json` at the project root. If it has an `extends` field, read the parent config too.542. Extract `compilerOptions.paths` and `compilerOptions.baseUrl`.553. Build a lookup table mapping each alias pattern to its resolved directory.56 Example: `{ "@/*": "./src/*", "~/lib/*": "./lib/*" }`.574. Keep this table in working memory -- use it to resolve every aliased import in Steps 2-3.585. If `tsconfig.json` is missing, warn the user and proceed with relative-path-only resolution.5960### Step 2: Identify Route Entry Points6162**App Router** (default -- `app/` directory exists):631. Locate `app/{route}/page.tsx` (or `.ts`, `.jsx`, `.js`).642. 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`.653. The trace order is: root layout -> nested layouts -> loading -> error -> page.6667**Pages Router** (fallback -- `pages/` directory, no `app/`):681. Locate `pages/{route}.tsx` (or `pages/{route}/index.tsx`).692. Also check `pages/_app.tsx` and `pages/_document.tsx`.703. Look for `getServerSideProps` or `getStaticProps` exports.7172### Step 3: Trace Imports (Depth-Limited)7374Default depth: **3 levels** from route entry. Override with `--depth N`.7576For each file, starting from the entry points found in Step 2:77781. Read the file. Scan the first 5 lines for `'use client'` or `'use server'` directives.792. Extract all import statements. For each import, resolve the target:80 - **Relative** (`./Button`): try `.tsx`, `.ts`, `.jsx`, `.js`, `/index.tsx`, `/index.ts`81 - **Aliased** (`@/components/Button`): resolve using the Step 1 lookup table, then try extensions82 - **External** (`react`, `next/link`, `@radix-ui/*`): record as external, stop tracing83 - **Dynamic** (`next/dynamic(() => import(...))`, `React.lazy`): trace literal paths, flag `isDynamic: true`84 - **Barrel** (`import { X } from './components'`): follow re-export through index files, cap at 5 levels853. For detailed resolution rules and edge cases, read `references/import-tracing-protocol.md`.864. For each resolved component file, extract:87 - `displayName`: the component function/const name88 - `boundary`: `"server"` (has `'use server'` or in `app/` without directive), `"client"` (has `'use client'`), `"shared"` (no directive, outside `app/`)89 - `props`: from TypeScript type annotation on the component parameter90 - `hooks`: all `useXxx(` calls91 - `stateManagement`: imports from `zustand`, `jotai`, `recoil`, `@tanstack/react-query`, or React context usage92 - `dataFetching`: `fetch()` calls, server actions, tRPC calls, React Query hooks, SWR hooks93 - `styling_approach`: based on import type (`.module.css` -> css-modules, tailwind classes in JSX -> tailwind)94 - `has_conditional_classes`: `clsx(`, `cn(`, `classNames(`, or ternary in className95 - `design_system_component`: true if imported from a known UI library96 - `accepts_className_prop`: true if props type includes `className`97 - `layout_role`: infer from file location and component name985. If at the depth limit, record remaining imports with `unresolved: true, unresolvedReason: "depth-limit"`.99100### Step 4: Classify Completeness101102- `"full"`: `unresolvedImports` array is empty103- `"shallow"`: all unresolved entries have reason `"depth-limit"`104- `"partial"`: any unresolved entry has a non-depth reason (`"dynamic-computed"`, `"barrel-depth-exceeded"`, `"not-found"`)105106### Step 5: Persist and Present1071081. Collect all resolved file paths into `cachedFiles` array.1092. Write the ComponentMap to `.claude/qa-cache/component-maps/{route-slug}.json`.1103. Present a summary to the user:111112```113Component Map: /dashboard/settings (depth 3, shallow)114Entry: app/dashboard/settings/page.tsx [server]115 Layout: app/dashboard/layout.tsx [server]11611723 components mapped | 4 unresolved (depth-limit) | 2 external (shadcn)118119Server (8): SettingsPage, SettingsLayout, AccountSection, ...120Client (12): ThemeToggle, NotificationForm, ProfileEditor, ...121Shared (3): Button, Card, Input122123Data flow: 2 server actions, 1 React Query hook, 3 prop chains124Hooks: useState(5), useEffect(3), useForm(2), useQuery(1)125```1261274. Wait for user confirmation before the coordinator proceeds to diagnosis.128129## Output Format130131The ComponentMap JSON follows the schema in the design document. Key fields:132133```json134{135 "version": "1.0",136 "route": "/dashboard/settings",137 "depth": 3,138 "completeness": "shallow",139 "framework": { "name": "next", "routerType": "app", "version": "14.1.0" },140 "entryComponent": {141 "filePath": "/abs/path/app/dashboard/settings/page.tsx",142 "displayName": "SettingsPage",143 "boundary": "server",144 "imports": [{ "targetPath": "...", "importType": "component" }]145 },146 "unresolvedImports": [{ "sourcePath": "...", "importSpecifier": "./DeepChild", "reason": "depth-limit" }],147 "cachedFiles": ["..."]148}149```150151## Handoff152153Pass 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`.154155## References156157| Path | Load Condition | Content Summary |158|------|---------------|-----------------|159| `references/caching-protocol.md` | Step 0, when cache file exists | Cache validity rules, TTL, invalidation triggers |160| `references/import-tracing-protocol.md` | Step 3, always | Path alias resolution, barrel handling, dynamic imports, boundary rules |