Skip to content

Commit c67019b

Browse files
committed
use more deterministic approach
1 parent 8a928aa commit c67019b

9 files changed

Lines changed: 807 additions & 302 deletions

File tree

Lines changed: 62 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,106 @@
11
---
22
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.'
44
---
55

66
# Stage 2: Design Decisions
77

88
## Context
99

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.
1111

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`.
3113

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
3815

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`).
4117

42-
**Public API**
43-
Exactly what the user imports and calls. Show the complete setup code example:
18+
Read:
4419

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`
4929

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
5231

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)
5833

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`.
6135

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.
6337

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):**
6739

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.
6943

70-
**View Name Algorithm**
71-
Pseudocode or step-by-step description of how `computeViewName()` will work for this framework. Cover:
44+
Apply in order:
7245

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.
7951

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.
8253

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)
8655

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.
8857

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:
9159

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)
9464

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:
9766

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
10070

101-
**Navigation Filtering**
102-
How to handle:
71+
### 3. View Name Algorithm (LLM Classification)
10372

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):
10874

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()`.
11078

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)
11380

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.
11682

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.
12085

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.
12387

124-
### Unmapped Concepts
88+
For extend-existing, also determine the subdirectory path for the new router files (e.g., `src/domain/tanstackRouter/`).
12589

126-
For any framework concept that has no SDK equivalent, create a section:
90+
### 5. Reference Implementation
12791

128-
```markdown
129-
### Unmapped: <concept name>
92+
Select the `packages/rum-*` implementation that is closest across:
13093

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
13597

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.
13899

139-
## Exit Criteria
100+
### 6. SSR Handling (LLM Judgment)
140101

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.
142103

143-
## Output
104+
## Output Schema
144105

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.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
{
2+
"title": "DesignDecisions",
3+
"description": "Explicit design decisions derived from Stage 1 router concepts and reference implementations, used by Stage 3 to generate code.",
4+
"type": "object",
5+
"additionalProperties": false,
6+
"required": ["selectedHook", "wrappingStrategy", "viewNameAlgorithm", "targetPackage", "referenceImplementation", "ssr"],
7+
"properties": {
8+
"selectedHook": {
9+
"type": "object",
10+
"additionalProperties": false,
11+
"required": ["name", "rationale"],
12+
"properties": {
13+
"name": {
14+
"type": "string",
15+
"description": "Hook name from 01-router-concepts.json, selected by the deterministic priority rules."
16+
},
17+
"rationale": {
18+
"type": "string",
19+
"description": "Which rules each candidate passed/failed and why the selected hook won."
20+
}
21+
}
22+
},
23+
"wrappingStrategy": {
24+
"type": "object",
25+
"additionalProperties": false,
26+
"required": ["pattern", "target", "rationale"],
27+
"properties": {
28+
"pattern": {
29+
"enum": ["wrap-factory", "renderless-component", "provider", "wrap-hook", "other"],
30+
"description": "wrap-factory: Wrap the router creation function, subscribe to hook inside. renderless-component: Component that calls the hook during lifecycle. provider: DI provider that injects the router and subscribes to events. wrap-hook: Wrap a user-facing hook to intercept route data. other: Escape hatch for unknown patterns."
31+
},
32+
"target": {
33+
"type": "string",
34+
"description": "What specifically to wrap/provide. E.g. 'createRouter from vue-router', 'ENVIRONMENT_INITIALIZER with inject(Router)'."
35+
},
36+
"rationale": {
37+
"type": "string",
38+
"description": "Why this is idiomatic for the framework."
39+
}
40+
}
41+
},
42+
"viewNameAlgorithm": {
43+
"type": "object",
44+
"additionalProperties": false,
45+
"required": ["family", "rationale"],
46+
"properties": {
47+
"family": {
48+
"enum": ["route-id", "matched-records", "param-substitution"],
49+
"description": "route-id: Framework provides parameterized route pattern as string. Minimal post-processing. matched-records: Framework provides matched route records. Iterate and concatenate path segments. param-substitution: Framework provides evaluated pathname + params. Reconstruct route template. Least preferred."
50+
},
51+
"rationale": {
52+
"type": "string",
53+
"description": "Why this family, based on the hook's availableApi."
54+
}
55+
}
56+
},
57+
"targetPackage": {
58+
"type": "object",
59+
"additionalProperties": false,
60+
"required": ["mode", "package"],
61+
"properties": {
62+
"mode": {
63+
"enum": ["new-package", "extend-existing"],
64+
"description": "new-package: Create a new packages/rum-<framework>/ from scratch. extend-existing: Add router support to an existing package (e.g. adding TanStack Router to rum-react)."
65+
},
66+
"package": {
67+
"type": "string",
68+
"description": "Target package directory name. For new-package: 'rum-<framework>'. For extend-existing: the existing package (e.g. 'rum-react')."
69+
},
70+
"subpath": {
71+
"type": "string",
72+
"description": "Only for extend-existing. The subdirectory for this router's files within the existing package. E.g. 'src/domain/tanstackRouter/' within packages/rum-react/."
73+
}
74+
}
75+
},
76+
"referenceImplementation": {
77+
"type": "object",
78+
"additionalProperties": false,
79+
"required": ["primary", "rationale"],
80+
"properties": {
81+
"primary": {
82+
"type": "string",
83+
"description": "Which packages/rum-* to model after (e.g. 'rum-vue'). Stage 3 reads this implementation as its primary source for code patterns."
84+
},
85+
"rationale": {
86+
"type": "string",
87+
"description": "Why this is the closest match."
88+
}
89+
}
90+
},
91+
"ssr": {
92+
"type": "object",
93+
"additionalProperties": false,
94+
"required": ["handling"],
95+
"properties": {
96+
"handling": {
97+
"type": "string",
98+
"description": "How to ensure client-side-only execution. 'N/A' if ssr.supported is false in Stage 1."
99+
}
100+
}
101+
},
102+
"notes": {
103+
"type": "string",
104+
"description": "Free text for additional design context, trade-offs, unmapped concepts, or anything Stage 3 needs to know."
105+
},
106+
"exitReason": {
107+
"type": "string",
108+
"description": "Only set if Stage 2 cannot proceed (e.g., no hook satisfies afterCancellation: true). When set, all other fields may be empty stubs and the pipeline will stop."
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)