|
1 | 1 | --- |
2 | 2 | name: router-design |
3 | | -description: 'Stage 2: Analyze reference implementations and produce design decisions document from router concepts. Reads 01-router-concepts.md and reference code.' |
| 3 | +description: 'Stage 2: Analyze reference implementations and produce design decisions from the stage 1 JSON. Reads 01-router-concepts.json and reference code, emits JSON conforming to output.schema.json.' |
4 | 4 | --- |
5 | 5 |
|
6 | 6 | # Stage 2: Design Decisions |
7 | 7 |
|
8 | 8 | ## Context |
9 | 9 |
|
10 | | -You are Stage 2 of the router integration pipeline. Your job is to read the router concepts extracted in Stage 1, analyze the existing reference implementations, and produce a concrete design document that will guide code generation. |
| 10 | +You are Stage 2 of the router integration pipeline. Your job is to read the structured router concepts from Stage 1, analyze the existing reference implementations, and produce explicit design decisions that will guide code generation in Stage 3. |
11 | 11 |
|
12 | | -## Input |
13 | | - |
14 | | -1. Read `docs/integrations/<framework>/01-router-concepts.md` |
15 | | -2. Read the reference implementations to understand the SDK contract: |
16 | | - - Plugin interface: `packages/rum-core/src/domain/plugins.ts` |
17 | | - - Public API: `packages/rum-core/src/boot/rumPublicApi.ts` |
18 | | - - Angular router: `packages/rum-angular/src/domain/angularRouter/` (all files) |
19 | | - - React router: `packages/rum-react/src/domain/reactRouter/` (all files) |
20 | | - - Vue router: `packages/rum-vue/src/domain/router/` (all files) |
21 | | -3. Read reference entry points and package configs: |
22 | | - - Angular entry point: `packages/rum-angular/src/entries/main.ts` |
23 | | - - Vue entry point: `packages/rum-vue/src/entries/main.ts` |
24 | | - - React entry point: `packages/rum-react/src/entries/main.ts` |
25 | | - - Vue package.json: `packages/rum-vue/package.json` |
26 | | - - Angular package.json: `packages/rum-angular/package.json` |
27 | | - |
28 | | -Find the `<framework>` directory by listing `docs/integrations/`. |
29 | | - |
30 | | -## Process |
| 12 | +The pipeline invokes you with `claude -p --output-format json --json-schema .claude/skills/router-design/output.schema.json`. Your final message must be a single JSON object conforming to that schema; the harness validates it and writes the CLI wrapper to `docs/integrations/<framework>/02-design-decisions.json` with the payload on `.structured_output`. |
31 | 13 |
|
32 | | -For each concept in `01-router-concepts.md`, find the closest equivalent in the reference implementations. Every mapping MUST include inline links to both: |
33 | | - |
34 | | -- The framework doc source (from 01-router-concepts.md links) |
35 | | -- The specific file and line range in the reference implementation |
36 | | - |
37 | | -### Required Sections |
| 14 | +## Input |
38 | 15 |
|
39 | | -**Architecture Overview** |
40 | | -2-3 sentences describing the overall approach. Which reference implementation is closest and why. |
| 16 | +You receive a **framework identifier** as skill param (e.g. `angular`, `vue`, `tanstack-react-router`). |
41 | 17 |
|
42 | | -**Public API** |
43 | | -Exactly what the user imports and calls. Show the complete setup code example: |
| 18 | +Read: |
44 | 19 |
|
45 | | -```typescript |
46 | | -// What the user writes in their app |
47 | | -import { ... } from '@datadog/browser-rum-<framework>' |
48 | | -``` |
| 20 | +1. `docs/integrations/<framework>/01-router-concepts.json` — stage 1 CLI wrapper. Extract the router-concepts payload with `jq '.structured_output' <file>`. |
| 21 | +2. Reference implementations (to understand SDK patterns): |
| 22 | + - Plugin files: `packages/rum-vue/src/domain/vuePlugin.ts`, `packages/rum-react/src/domain/reactPlugin.ts`, `packages/rum-nextjs/src/domain/nextjsPlugin.ts` |
| 23 | + - Vue router: `packages/rum-vue/src/domain/router/` (all `.ts` files) |
| 24 | + - React router: `packages/rum-react/src/domain/reactRouter/` (all `.ts` files) |
| 25 | + - Next.js router: `packages/rum-nextjs/src/domain/nextJSRouter/` (all `.ts` files) |
| 26 | + - Entry points: `packages/rum-vue/src/entries/main.ts`, `packages/rum-react/src/entries/main.ts`, `packages/rum-nextjs/src/entries/main.ts` |
| 27 | + - Package configs: `packages/rum-vue/package.json`, `packages/rum-react/package.json`, `packages/rum-nextjs/package.json` |
| 28 | + - Plugin interface: `packages/rum-core/src/domain/plugins.ts` |
49 | 29 |
|
50 | | -**File Structure** |
51 | | -The exact file tree for `packages/rum-<framework>/` with one-line descriptions per file. Follow the convention from reference packages: |
| 30 | +## Process |
52 | 31 |
|
53 | | -- `src/entries/main.ts` — public exports |
54 | | -- `src/domain/<framework>Plugin.ts` — plugin + subscriber pattern |
55 | | -- `src/domain/<framework>Router/` — router integration files |
56 | | -- `src/test/` — test helpers |
57 | | -- `package.json`, `tsconfig.json`, `README.md` |
| 32 | +### 1. Hook Selection (Deterministic) |
58 | 33 |
|
59 | | -**Navigation Hook Decision** |
60 | | -Which framework hook/event to subscribe to, and which reference implementation it's most similar to. Justify the choice based on the lifecycle timing analysis from Stage 1 (after redirects, before data fetches, before render). |
| 34 | +Apply these priority rules to the `hooks` array from `01-router-concepts.json`. |
61 | 35 |
|
62 | | -IMPORTANT: The navigation hook choice directly affects what data is available to `computeViewName()`. Different hooks may expose different route objects, matched route arrays, or URL representations. If the framework has multiple candidate hooks, show how the view name computation differs for each option: |
| 36 | +**The integration must be client-side only.** Only consider hooks that fire on the client. Use the `access` field and `ssr` section from `01-router-concepts.json` to determine this. |
63 | 37 |
|
64 | | -- What route data each hook provides (e.g. matched route records vs. raw URL vs. route config) |
65 | | -- How that changes the `computeViewName()` implementation |
66 | | -- Whether one hook gives better data for view name computation (e.g. access to parameterized route patterns vs. only resolved URLs) |
| 38 | +**Priority rules (in order):** |
67 | 39 |
|
68 | | -This analysis should reinforce or challenge the hook choice — if a hook that fires later provides significantly better route data, that trade-off must be documented. |
| 40 | +1. `afterCancellation: true` — **required**. Never start a RUM view for a navigation that didn't occur. |
| 41 | +2. `afterRedirects: true` — **prefer**. Report the final destination, not intermediate routes. |
| 42 | +3. `afterFetch: false` AND `afterRender: false` — **prefer**. Start the view before data loading and DOM mutation so RUM events (fetch resources, long tasks, interactions) are attributed to the new view, not the previous one. |
69 | 43 |
|
70 | | -**View Name Algorithm** |
71 | | -Pseudocode or step-by-step description of how `computeViewName()` will work for this framework. Cover: |
| 44 | +Apply in order: |
72 | 45 |
|
73 | | -- How to access the matched route records after navigation |
74 | | -- How dynamic segments appear in the route definition (and whether they need normalization) |
75 | | -- How catch-all/wildcard routes should be substituted with actual path segments |
76 | | -- Normal routes, dynamic segments, nested routes, catch-all/wildcard routes |
77 | | -- Edge cases specific to this framework |
78 | | -- Link to the most similar existing `computeViewName` implementation and note differences |
| 46 | +- Filter to `afterCancellation: true`. If no hooks pass, flag as critical issue and stop. |
| 47 | +- Among those, prefer `afterRedirects: true`. |
| 48 | +- Among those, prefer `afterFetch: false` AND `afterRender: false`. |
| 49 | +- If rules conflict (no hook satisfies all), higher-priority rule wins. |
| 50 | +- If multiple hooks still tie, prefer the one that fires earliest in the lifecycle. |
79 | 51 |
|
80 | | -**Wrapping Strategy** |
81 | | -How the integration hooks into the framework. Reference implementations: |
| 52 | +Document which hooks were considered, which rules each passed/failed, and why the selected hook won. |
82 | 53 |
|
83 | | -- Angular: [`ENVIRONMENT_INITIALIZER` provider](packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.ts) |
84 | | -- React: [wrapper around `createRouter`](packages/rum-react/src/domain/reactRouter/createRouter.ts) and [`useRoutes` hook](packages/rum-react/src/domain/reactRouter/useRoutes.ts) |
85 | | -- Vue: [wrapper around `createRouter`](packages/rum-vue/src/domain/router/vueRouter.ts) |
| 54 | +### 2. Wrapping Strategy (LLM Judgment) |
86 | 55 |
|
87 | | -Determine which pattern fits this framework and why. |
| 56 | +Read the selected hook's `access` field from `01-router-concepts.json`. Determine the most idiomatic way for users to integrate the plugin in this framework. |
88 | 57 |
|
89 | | -**Type Strategy** |
90 | | -Whether to define minimal local types (like Angular's [`RouteSnapshot`](packages/rum-angular/src/domain/angularRouter/types.ts)) to avoid runtime framework imports, or import types directly from the framework package. |
| 58 | +Consider: |
91 | 59 |
|
92 | | -**Plugin Configuration** |
93 | | -How the plugin will be configured. All reference implementations use the same pattern: |
| 60 | +- How existing plugins/libraries are typically added in this framework's ecosystem |
| 61 | +- Whether the hook needs a router instance (→ wrap the factory that creates it) |
| 62 | +- Whether the hook needs component context (→ renderless component or hook) |
| 63 | +- Whether the hook needs DI (→ provider registration) |
94 | 64 |
|
95 | | -- [`VuePluginConfiguration`](packages/rum-vue/src/domain/vuePlugin.ts) with `router?: boolean` |
96 | | -- `onInit` sets `trackViewsManually = true` when `router: true` |
| 65 | +Reference patterns from existing implementations: |
97 | 66 |
|
98 | | -**Peer Dependencies** |
99 | | -Which framework packages are required as peer dependencies, with version ranges. |
| 67 | +- Vue: wraps `createRouter()` factory to get router instance for `afterEach` |
| 68 | +- React: wraps `createBrowserRouter()` factory OR wraps `useRoutes()` hook |
| 69 | +- Angular: provider with `inject(Router)` for `router.events` observable |
100 | 70 |
|
101 | | -**Navigation Filtering** |
102 | | -How to handle: |
| 71 | +### 3. View Name Algorithm (LLM Classification) |
103 | 72 |
|
104 | | -- Failed navigations (guards blocking, cancellations) |
105 | | -- Duplicate navigations (same path) |
106 | | -- Query-only changes |
107 | | -- Initial navigation |
| 73 | +Read the selected hook's `availableApi` from `01-router-concepts.json`. Classify into one of three families (in preference order): |
108 | 74 |
|
109 | | -Reference the filtering logic in existing implementations: |
| 75 | +- **`route-id`** — Framework provides the parameterized route pattern as a string. Minimal post-processing needed (e.g. strip route groups). Example: SvelteKit `route.id`. |
| 76 | +- **`matched-records`** — Framework provides matched route records (array or tree). Iterate and concatenate path segments. Handle catch-all substitution. Example: Vue `to.matched[]`, React `state.matches[]`. |
| 77 | +- **`param-substitution`** — Framework provides only the evaluated pathname + params object. Must reconstruct the route template by substituting values back with placeholders. Least preferred — heuristic and fragile. Example: Next.js `useParams()` + `usePathname()`. |
110 | 78 |
|
111 | | -- Vue: [lines 15-22 of vueRouter.ts](packages/rum-vue/src/domain/router/vueRouter.ts) |
112 | | -- React: subscribe callback in [createRouter.ts](packages/rum-react/src/domain/reactRouter/createRouter.ts) |
| 79 | +### 4. Target Package (LLM Judgment) |
113 | 80 |
|
114 | | -**Test Strategy** |
115 | | -List every test case to implement, grouped by file: |
| 81 | +Determine whether this router needs a new package or extends an existing one. |
116 | 82 |
|
117 | | -- `<framework>Plugin.spec.ts`: plugin structure, subscriber callbacks, telemetry, trackViewsManually |
118 | | -- `start<Framework>View.spec.ts`: all view name computation cases (static, dynamic, nested, catch-all, edge cases) |
119 | | -- Router integration spec: navigation event handling, filtering, deduplication |
| 83 | +- **`new-package`** — The router belongs to a framework with no existing SDK package (e.g., SvelteKit, Angular). Create `packages/rum-<framework>/`. |
| 84 | +- **`extend-existing`** — The router is an alternative router for a framework that already has an SDK package (e.g., TanStack Router is a React router → extends `rum-react`). Add files under a subdirectory within the existing package. |
120 | 85 |
|
121 | | -**Trade-offs and Alternatives** |
122 | | -Document any decisions where multiple valid approaches existed. For each, state what was chosen, what was rejected, and why. |
| 86 | +To decide: check if `packages/rum-*` already has a package for the same UI framework (React, Vue, etc.). If yes, extend it. If no, create new. |
123 | 87 |
|
124 | | -### Unmapped Concepts |
| 88 | +For extend-existing, also determine the subdirectory path for the new router files (e.g., `src/domain/tanstackRouter/`). |
125 | 89 |
|
126 | | -For any framework concept that has no SDK equivalent, create a section: |
| 90 | +### 5. Reference Implementation |
127 | 91 |
|
128 | | -```markdown |
129 | | -### Unmapped: <concept name> |
| 92 | +Select the `packages/rum-*` implementation that is closest across: |
130 | 93 |
|
131 | | -**Severity:** critical | minor |
132 | | -**Reason:** <why there's no equivalent> |
133 | | -**Impact:** <what this means for the integration> |
134 | | -``` |
| 94 | +- Hook subscription pattern |
| 95 | +- Wrapping strategy |
| 96 | +- Algorithm family |
135 | 97 |
|
136 | | -- `critical`: the integration cannot work without this (e.g. no way to get route matches) — **stop the pipeline** |
137 | | -- `minor`: the integration works but this feature isn't supported (e.g. named outlets not tracked) |
| 98 | +Stage 3 reads this implementation as its primary model for code generation. |
138 | 99 |
|
139 | | -## Exit Criteria |
| 100 | +### 6. SSR Handling (LLM Judgment) |
140 | 101 |
|
141 | | -If any unmapped concept has severity `critical`, stop the pipeline. Write an exit note at the top of the output explaining which concepts could not be mapped and why. |
| 102 | +If `ssr.supported: true` in `01-router-concepts.json`, describe how the integration should ensure client-side-only execution. Use the `clientDetection` API from Stage 1 if available. |
142 | 103 |
|
143 | | -## Output |
| 104 | +## Output Schema |
144 | 105 |
|
145 | | -Write the result to `docs/integrations/<framework>/02-design-decisions.md`. |
| 106 | +Return the populated object as your final message. The pipeline invokes you with `--output-format json --json-schema output.schema.json`; the harness validates the object and writes the full CLI wrapper (with the object on `.structured_output`) to `docs/integrations/<framework>/02-design-decisions.json`. Do not write files yourself. |
0 commit comments