@paramour-js/next 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/app.d.ts +48 -49
  2. package/dist/app.js +11 -12
  3. package/dist/cli-args.d.ts +4 -4
  4. package/dist/cli-args.js +4 -4
  5. package/dist/cli-inputs.d.ts +6 -7
  6. package/dist/cli-inputs.js +6 -7
  7. package/dist/cli.js +1 -1
  8. package/dist/collisions.d.ts +8 -8
  9. package/dist/collisions.js +9 -9
  10. package/dist/commands/generate.d.ts +7 -7
  11. package/dist/commands/generate.js +16 -16
  12. package/dist/commands/init.js +1 -1
  13. package/dist/config.d.ts +10 -10
  14. package/dist/config.js +4 -4
  15. package/dist/devtools-seam.d.ts +19 -19
  16. package/dist/devtools-seam.js +3 -3
  17. package/dist/doctor/checks.js +1 -1
  18. package/dist/emit.d.ts +12 -12
  19. package/dist/emit.js +13 -13
  20. package/dist/generate.d.ts +14 -14
  21. package/dist/generate.js +11 -11
  22. package/dist/list/discover-route-defs.d.ts +4 -4
  23. package/dist/list/discover-route-defs.js +4 -4
  24. package/dist/lock.d.ts +8 -9
  25. package/dist/lock.js +11 -12
  26. package/dist/navigation-adapter.d.ts +17 -18
  27. package/dist/navigation-adapter.js +4 -4
  28. package/dist/observe.d.ts +14 -14
  29. package/dist/observe.js +4 -4
  30. package/dist/pages.d.ts +30 -31
  31. package/dist/pages.js +10 -10
  32. package/dist/run-cli.d.ts +1 -1
  33. package/dist/run-cli.js +1 -1
  34. package/dist/scan-app.d.ts +10 -10
  35. package/dist/scan-app.js +30 -30
  36. package/dist/scan-pages.d.ts +6 -6
  37. package/dist/scan-pages.js +28 -26
  38. package/dist/scan.d.ts +13 -10
  39. package/dist/scan.js +7 -7
  40. package/dist/select.d.ts +40 -40
  41. package/dist/select.js +30 -30
  42. package/dist/testing.d.ts +28 -32
  43. package/dist/testing.js +15 -15
  44. package/dist/watch.d.ts +12 -12
  45. package/dist/watch.js +13 -13
  46. package/dist/with-typed-routes.d.ts +11 -10
  47. package/dist/with-typed-routes.js +42 -39
  48. package/package.json +2 -2
package/dist/app.d.ts CHANGED
@@ -2,67 +2,66 @@ import { type AnyAppRoute, type InferRouteParams, type SafeResult, type SearchOu
2
2
  import { type SelectOptions } from "./select.js";
3
3
  export type { SelectOptions } from "./select.js";
4
4
  /**
5
- * Client hooks (DESIGN §9, design-07). Each layers over Next's
6
- * `useSearchParams()` / `useParams()` — App-Router params are synchronous on
7
- * the client, so there is no loading state, no `useEffect`/`useState`, and
8
- * the result is SSR-consistent. Two layers per hook (design-07):
5
+ * Client hooks. Each layers over Next's `useSearchParams()` / `useParams()`
6
+ * — App-Router params are synchronous on the client, so there is no loading
7
+ * state, no `useEffect`/`useState`, and the result is SSR-consistent. Two
8
+ * layers per hook:
9
9
  *
10
- * - Raw-slice stabilization (SEL4): the decode is keyed on the DECLARED
11
- * slice of the raw source, not on Next's object reference — a URL change
12
- * that only touches keys the route doesn't own (`?utm_source=` churn)
13
- * returns the previous result by identity, without re-decoding. Next still
14
- * re-renders every subscriber on any URL change (it owns the subscription
15
- * — SEL7: selectors stabilize slices, they cannot skip renders); this
16
- * layer makes that render cheap and downstream-invisible.
17
- * - Selection (SEL1–SEL3): every hook takes an optional `{ select }` that
18
- * projects the decoded value, with result-equality checking (`Object.is`,
10
+ * - Raw-slice stabilization: the decode is keyed on the DECLARED slice of
11
+ * the raw source, not on Next's object reference — a URL change that only
12
+ * touches keys the route doesn't own (`?utm_source=` churn) returns the
13
+ * previous result by identity, without re-decoding. Next still re-renders
14
+ * every subscriber on any URL change (it owns the subscription; selectors
15
+ * stabilize slices, they cannot skip renders); this layer makes that
16
+ * render cheap and downstream-invisible.
17
+ * - Selection: every hook takes an optional `{ select }` that projects the
18
+ * decoded value, with result-equality checking (`Object.is`,
19
19
  * `equality: "shallow"` opt-in) so an unchanged selection keeps its
20
20
  * previous reference when OTHER params change.
21
21
  *
22
- * Both layers are render-phase ref caches (SEL8) — the one sanctioned
23
- * departure from the pure-`useMemo` discipline these hooks previously held.
22
+ * Both layers are render-phase ref caches — the one sanctioned departure
23
+ * from the pure-`useMemo` discipline these hooks previously held.
24
24
  *
25
25
  * Two surfaces per half, mirroring core's server `parse` vs `safeParse`:
26
26
  * - `useSearch` / `useRouteParams` return the `SafeResult` union
27
- * (discriminated on `status`, PR12) — a user editing the URL never crashes
28
- * the component. The selector runs on the success arm only (SEL2).
27
+ * (discriminated on `status`) — a user editing the URL never crashes the
28
+ * component. The selector runs on the success arm only.
29
29
  * - `useSearchOrThrow` / `useRouteParamsOrThrow` throw the decode error in
30
30
  * render, to the nearest client error boundary.
31
31
  *
32
32
  * Both read the route's blessed-internal `~search` / `~params` via the core
33
- * decoders (design-03 RL6 — `@paramour/next` is a sanctioned consumer).
33
+ * decoders — `@paramour/next` is a sanctioned consumer of those internals.
34
34
  *
35
- * Every hook is gated to `AnyAppRoute` (design-06 PR3): a pages-branded route
36
- * at one of these call sites is a compile error, not a runtime surprise —
37
- * these hooks read Next's App-Router navigation hooks, whose pages twin has
38
- * different state cardinality (`@paramour-js/next/pages`).
35
+ * Every hook is gated to `AnyAppRoute`: a pages-branded route at one of
36
+ * these call sites is a compile error, not a runtime surprise — these hooks
37
+ * read Next's App-Router navigation hooks, whose pages twin has different
38
+ * state cardinality (`@paramour-js/next/pages`).
39
39
  *
40
- * Devtools instrumentation (design-12): each hook reports through the shared
41
- * emitter in observe.ts — `observe` from inside the `useStableResult`
42
- * compute callback, which runs exactly on a `(route, fingerprint)` cache
43
- * miss, so the SEL4 fingerprint layer IS the decode-change dedup (DT4;
44
- * StrictMode's dev double render reuses the ref cache and cannot
45
- * double-emit), and `refresh` after the stable result returns, re-emitting
46
- * the CACHED result when the pathname moved under an unchanged decode so
47
- * the seam's `navigate`/`pathname` never go stale (DT8). Observations carry
48
- * the full pre-`select` result (DT12), and the `OrThrow` hooks report the
49
- * error observation BEFORE rethrowing — only render-phase can, since an
50
- * effect never runs for a throwing render. Every emit sits behind
51
- * `process.env.NODE_ENV !== "production"`, which bundlers constant-fold and
52
- * erase along with the seam module (DT6); the spec each hook hands the
53
- * emitter is built behind the same literal guard, so prod allocates
54
- * nothing. `useRouter` and `usePathname` are called unconditionally in
55
- * every hook (rules of hooks — a build-constant-guarded call would make
56
- * hook order differ between dev and prod bundles); their cost is a
57
- * referentially-stable context read each, and only the dev-only spec
58
- * captures them. The `navigate` capability receives the panel's SEARCH
40
+ * Devtools instrumentation: each hook reports through the shared emitter in
41
+ * observe.ts — `observe` from inside the `useStableResult` compute callback,
42
+ * which runs exactly on a `(route, fingerprint)` cache miss, so the
43
+ * fingerprint layer IS the decode-change dedup (StrictMode's dev double
44
+ * render reuses the ref cache and cannot double-emit), and `refresh` after
45
+ * the stable result returns, re-emitting the CACHED result when the pathname
46
+ * moved under an unchanged decode so the seam's `navigate`/`pathname` never
47
+ * go stale. Observations carry the full pre-`select` result, and the
48
+ * `OrThrow` hooks report the error observation BEFORE rethrowing — only
49
+ * render-phase can, since an effect never runs for a throwing render. Every
50
+ * emit sits behind `process.env.NODE_ENV !== "production"`, which bundlers
51
+ * constant-fold and erase along with the seam module; the spec each hook
52
+ * hands the emitter is built behind the same literal guard, so prod
53
+ * allocates nothing. `useRouter` and `usePathname` are called
54
+ * unconditionally in every hook (rules of hooks — a build-constant-guarded
55
+ * call would make hook order differ between dev and prod bundles); their
56
+ * cost is a referentially-stable context read each, and only the dev-only
57
+ * spec captures them. The `navigate` capability receives the panel's SEARCH
59
58
  * STRING only and resolves it against `usePathname()` — basePath-/locale-
60
- * relative, exactly what `router.replace` expects back (DT8; the live hash
61
- * is preserved at call time).
59
+ * relative, exactly what `router.replace` expects back (the live hash is
60
+ * preserved at call time).
62
61
  */
63
62
  /**
64
- * Decoded route params as a `SafeResult` (discriminated on `status`, PR12),
65
- * optionally projected through `options.select` (design-07 SEL1/SEL2).
63
+ * Decoded route params as a `SafeResult` (discriminated on `status`),
64
+ * optionally projected through `options.select`.
66
65
  *
67
66
  * `useParams()` returns `null` outside an App-Router tree — including the
68
67
  * initial render of every pages-router page in a hybrid app — so a `null`
@@ -76,7 +75,7 @@ export declare function useRouteParams<R extends AnyAppRoute, U>(route: R, optio
76
75
  /**
77
76
  * Decoded route params, or a thrown {@link ParamsDecodeError} (→ nearest
78
77
  * client error boundary) on a malformed URL. Optionally projected through
79
- * `options.select` (design-07 SEL1/SEL2).
78
+ * `options.select`.
80
79
  *
81
80
  * A `null` `useParams()` (outside an App-Router tree, e.g. a hybrid app's
82
81
  * pages-router initial render) degrades to `{}` so required params throw the
@@ -85,15 +84,15 @@ export declare function useRouteParams<R extends AnyAppRoute, U>(route: R, optio
85
84
  export declare function useRouteParamsOrThrow<R extends AnyAppRoute>(route: R): InferRouteParams<R>;
86
85
  export declare function useRouteParamsOrThrow<R extends AnyAppRoute, U>(route: R, options: SelectOptions<InferRouteParams<R>, U>): U;
87
86
  /**
88
- * Decoded search params as a `SafeResult` (discriminated on `status`, PR12),
89
- * optionally projected through `options.select` (design-07 SEL1/SEL2).
87
+ * Decoded search params as a `SafeResult` (discriminated on `status`),
88
+ * optionally projected through `options.select`.
90
89
  */
91
90
  export declare function useSearch<R extends AnyAppRoute>(route: R): SafeResult<SearchOutputOf<R["~search"]>>;
92
91
  export declare function useSearch<R extends AnyAppRoute, U>(route: R, options: SelectOptions<SearchOutputOf<R["~search"]>, U>): SafeResult<U>;
93
92
  /**
94
93
  * Decoded search params, or a thrown {@link SearchDecodeError} (→ nearest
95
94
  * client error boundary) on a malformed URL. Optionally projected through
96
- * `options.select` (design-07 SEL1/SEL2).
95
+ * `options.select`.
97
96
  */
98
97
  export declare function useSearchOrThrow<R extends AnyAppRoute>(route: R): SearchOutputOf<R["~search"]>;
99
98
  export declare function useSearchOrThrow<R extends AnyAppRoute, U>(route: R, options: SelectOptions<SearchOutputOf<R["~search"]>, U>): U;
package/dist/app.js CHANGED
@@ -54,7 +54,7 @@ export function useRouteParamsOrThrow(route, options) {
54
54
  };
55
55
  const value = useStableResult(route, paramsFingerprint(route, params), () => {
56
56
  // The duplicated decode call across the prod/dev branches is the price
57
- // of literal-zero prod cost — the bundler keeps exactly one branch (DT6).
57
+ // of literal-zero prod cost — the bundler keeps exactly one branch.
58
58
  if (process.env.NODE_ENV === "production") {
59
59
  return decodeParams(route, params);
60
60
  }
@@ -66,7 +66,7 @@ export function useRouteParamsOrThrow(route, options) {
66
66
  return data;
67
67
  }
68
68
  catch (error) {
69
- // Report BEFORE the throw reaches the error boundary (DT4). Only the
69
+ // Report BEFORE the throw reaches the error boundary. Only the
70
70
  // decode-error class is observed — foreign errors are not URL facts
71
71
  // the panel explains, matching safeDecode*'s taxonomy.
72
72
  if (error instanceof ParamsDecodeError && spec !== undefined) {
@@ -127,7 +127,7 @@ export function useSearchOrThrow(route, options) {
127
127
  wire: () => searchWireSnapshot(searchParams),
128
128
  };
129
129
  const value = useStableResult(route, searchParamsFingerprint(route, searchParams),
130
- // decodeSearch is keyed on SearchOutputOf (design-04 SS6) — the correct
130
+ // decodeSearch is keyed on SearchOutputOf (SS6) — the correct
131
131
  // public type — but AnyAppRoute erases its SC to `any`, so for a still-
132
132
  // generic R the call's SearchOutputOf<R["~search"]> reduces to `unknown`
133
133
  // on the value side while staying deferred on the annotation side. The
@@ -135,22 +135,21 @@ export function useSearchOrThrow(route, options) {
135
135
  // rawSearch route now infers its schema output here, not a garbage
136
136
  // {~kind, ~schema} shape. The cast appears in both branches below — the
137
137
  // prod/dev split (and its duplicated decode call) is the price of
138
- // literal-zero prod cost; the bundler keeps exactly one branch (DT6).
138
+ // literal-zero prod cost; the bundler keeps exactly one branch.
139
139
  () => {
140
140
  if (process.env.NODE_ENV === "production") {
141
- return decodeSearch(route["~search"], searchParams);
141
+ return decodeSearch(route["~search"], searchParams, route.path);
142
142
  }
143
143
  try {
144
- const data = decodeSearch(route["~search"], searchParams);
144
+ const data = decodeSearch(route["~search"], searchParams, route.path);
145
145
  if (spec !== undefined) {
146
146
  emitter.observe(spec, { data, status: "success" });
147
147
  }
148
148
  return data;
149
149
  }
150
150
  catch (error) {
151
- // Report BEFORE the throw reaches the error boundary (DT4); only
152
- // the decode-error class is observed, matching safeDecode*'s
153
- // taxonomy.
151
+ // Report BEFORE the throw reaches the error boundary; only the
152
+ // decode-error class is observed, matching safeDecode*'s taxonomy.
154
153
  if (error instanceof SearchDecodeError && spec !== undefined) {
155
154
  emitter.observe(spec, { error, status: "error" });
156
155
  }
@@ -163,8 +162,8 @@ export function useSearchOrThrow(route, options) {
163
162
  return useSelectedValue(value, options);
164
163
  }
165
164
  /**
166
- * Real-Next fallback for the adapter seam (design-16 TA3): the /testing
167
- * provider overrides these reads through {@link AppNavigationContext}; with
165
+ * Real-Next fallback for the adapter seam: the /testing provider overrides
166
+ * these reads through {@link AppNavigationContext}; with
168
167
  * no provider mounted the context's `null` default resolves here, so
169
168
  * production behavior (and this module's `next/navigation`-only bundle
170
169
  * graph, per dist.test.ts) is unchanged.
@@ -179,7 +178,7 @@ const realAppAdapter = {
179
178
  * Every hook resolves the adapter ONCE at its top and calls the adapter's
180
179
  * reads unconditionally, exactly where the direct Next calls previously sat
181
180
  * — hook call order is identical across renders and across provider
182
- * presence (TA4).
181
+ * presence.
183
182
  */
184
183
  function useAppNavigation() {
185
184
  return useContext(AppNavigationContext) ?? realAppAdapter;
@@ -15,10 +15,10 @@ type ParsedValues<T extends ParseArgsOptionsConfig> = ReturnType<typeof parseArg
15
15
  options: T;
16
16
  }>>["values"];
17
17
  /**
18
- * The shared command prologue (TR7): parse flags, print usage on a parse
19
- * error (exit 2) or `--help` (exit 0), and reject positionals — no command
20
- * takes one. Callers branch on `"exit" in result`; anything past the
21
- * prologue (mode merging, flag exclusivity) stays per-command.
18
+ * The shared command prologue: parse flags, print usage on a parse error
19
+ * (exit 2) or `--help` (exit 0), and reject positionals — no command takes
20
+ * one. Callers branch on `"exit" in result`; anything past the prologue
21
+ * (mode merging, flag exclusivity) stays per-command.
22
22
  */
23
23
  export declare function parseCommandFlags<const T extends HelpOption & ParseArgsOptionsConfig>(argv: readonly string[], options: T, usage: string, { stderr, stdout }: ResolvedIo): {
24
24
  exit: 0 | 2;
package/dist/cli-args.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { parseArgs } from "node:util";
2
2
  import { message } from "./cli-io.js";
3
3
  /**
4
- * The shared command prologue (TR7): parse flags, print usage on a parse
5
- * error (exit 2) or `--help` (exit 0), and reject positionals — no command
6
- * takes one. Callers branch on `"exit" in result`; anything past the
7
- * prologue (mode merging, flag exclusivity) stays per-command.
4
+ * The shared command prologue: parse flags, print usage on a parse error
5
+ * (exit 2) or `--help` (exit 0), and reject positionals — no command takes
6
+ * one. Callers branch on `"exit" in result`; anything past the prologue
7
+ * (mode merging, flag exclusivity) stays per-command.
8
8
  */
9
9
  export function parseCommandFlags(argv, options, usage, { stderr, stdout }) {
10
10
  let parsed;
@@ -18,13 +18,12 @@ export interface InputFlags {
18
18
  export declare class NoRouteDirsError extends Error {
19
19
  }
20
20
  /**
21
- * Precedence lives in exactly this function (TR7 / §7.2): flags → config
22
- * file → joint discovery (PR8). Paths resolve against the project root
23
- * (= cwd, where `next` itself would run). Discovery only runs for dirs not
24
- * explicitly given — passing both bypasses it (and its populated-ignored-dir
25
- * config error) entirely, which is the documented escape hatch. Only when
26
- * NEITHER dir exists is that an error (PR8): app-only and pages-only
27
- * projects are both fine.
21
+ * Precedence lives in exactly this function: flags → config file → joint
22
+ * discovery. Paths resolve against the project root (= cwd, where `next`
23
+ * itself would run). Discovery only runs for dirs not explicitly given —
24
+ * passing both bypasses it (and its populated-ignored-dir config error)
25
+ * entirely, which is the documented escape hatch. Only when NEITHER dir
26
+ * exists is that an error: app-only and pages-only projects are both fine.
28
27
  *
29
28
  * Commands that already loaded the config file (for fields beyond these,
30
29
  * e.g. `list`'s routeFiles) pass it as `preloaded` so jiti runs once.
@@ -12,13 +12,12 @@ import { resolveRouteDirs } from "./scan.js";
12
12
  export class NoRouteDirsError extends Error {
13
13
  }
14
14
  /**
15
- * Precedence lives in exactly this function (TR7 / §7.2): flags → config
16
- * file → joint discovery (PR8). Paths resolve against the project root
17
- * (= cwd, where `next` itself would run). Discovery only runs for dirs not
18
- * explicitly given — passing both bypasses it (and its populated-ignored-dir
19
- * config error) entirely, which is the documented escape hatch. Only when
20
- * NEITHER dir exists is that an error (PR8): app-only and pages-only
21
- * projects are both fine.
15
+ * Precedence lives in exactly this function: flags → config file → joint
16
+ * discovery. Paths resolve against the project root (= cwd, where `next`
17
+ * itself would run). Discovery only runs for dirs not explicitly given —
18
+ * passing both bypasses it (and its populated-ignored-dir config error)
19
+ * entirely, which is the documented escape hatch. Only when NEITHER dir
20
+ * exists is that an error: app-only and pages-only projects are both fine.
22
21
  *
23
22
  * Commands that already loaded the config file (for fields beyond these,
24
23
  * e.g. `list`'s routeFiles) pass it as `preloaded` so jiti runs once.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import { runCli } from "./run-cli.js";
3
- // The bin entry (TR7): all logic lives in run-cli.ts so tests never execute
3
+ // The bin entry: all logic lives in run-cli.ts so tests never execute
4
4
  // this statement. exitCode, not exit() — pending stdio writes must flush.
5
5
  process.exitCode = await runCli(process.argv.slice(2));
@@ -1,21 +1,21 @@
1
- /** A scanned route path labeled with the router that produced it (PR9). */
1
+ /** A scanned route path labeled with the router that produced it. */
2
2
  export interface ScannedRoute {
3
3
  path: string;
4
4
  router: "app" | "pages";
5
5
  }
6
6
  /**
7
- * Route-collision failure mode (PR9): states Next itself refuses to build
8
- * have no valid artifact, so the scanners throw instead of emitting one.
9
- * Composition points map this error to their ruled exits — CLI exit 2,
10
- * `withTypedRoutes` throw during config evaluation, and a non-fatal loud
11
- * log under watch (the TR5 exception: a collision mid-`--watch` is usually
12
- * a file mid-move, so the last good artifact stays on disk).
7
+ * Route-collision failure mode: states Next itself refuses to build have no
8
+ * valid artifact, so the scanners throw instead of emitting one. Composition
9
+ * points map this error to their own exits — CLI exit 2, `withTypedRoutes`
10
+ * throw during config evaluation, and a non-fatal loud log under watch (a
11
+ * collision mid-`--watch` is usually a file mid-move, so the last good
12
+ * artifact stays on disk).
13
13
  */
14
14
  export declare class RouteCollisionError extends Error {
15
15
  name: string;
16
16
  }
17
17
  /**
18
- * PR9's structural collisions — same detection pass, non-equal strings. Two
18
+ * Structural collisions — same detection pass, non-equal strings. Two
19
19
  * states Next also refuses to build that plain string equality misses:
20
20
  *
21
21
  * - **Different slug names at one level**: `/x/[id]` + `/x/[slug]` — Next:
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Route-collision failure mode (PR9): states Next itself refuses to build
3
- * have no valid artifact, so the scanners throw instead of emitting one.
4
- * Composition points map this error to their ruled exits — CLI exit 2,
5
- * `withTypedRoutes` throw during config evaluation, and a non-fatal loud
6
- * log under watch (the TR5 exception: a collision mid-`--watch` is usually
7
- * a file mid-move, so the last good artifact stays on disk).
2
+ * Route-collision failure mode: states Next itself refuses to build have no
3
+ * valid artifact, so the scanners throw instead of emitting one. Composition
4
+ * points map this error to their own exits — CLI exit 2, `withTypedRoutes`
5
+ * throw during config evaluation, and a non-fatal loud log under watch (a
6
+ * collision mid-`--watch` is usually a file mid-move, so the last good
7
+ * artifact stays on disk).
8
8
  */
9
9
  export class RouteCollisionError extends Error {
10
10
  name = "RouteCollisionError";
@@ -22,7 +22,7 @@ const OPTIONAL_CATCH_ALL = /^\[\[\.\.\..+\]\]$/;
22
22
  */
23
23
  const DYNAMIC_SEGMENT = /^(?:\[\[\.\.\.(?<optional>.+)\]\]|\[\.\.\.(?<catchAll>.+)\]|\[(?<plain>[^[\]]+)\])$/;
24
24
  /**
25
- * PR9's structural collisions — same detection pass, non-equal strings. Two
25
+ * Structural collisions — same detection pass, non-equal strings. Two
26
26
  * states Next also refuses to build that plain string equality misses:
27
27
  *
28
28
  * - **Different slug names at one level**: `/x/[id]` + `/x/[slug]` — Next:
@@ -56,7 +56,7 @@ export function assertNoStructuralCollisions(routes) {
56
56
  const key = `${segments.slice(0, index).join("/")}#${String(index)}#${kind}`;
57
57
  const existing = dynamicAt.get(key);
58
58
  if (existing !== undefined && existing.segment !== segment) {
59
- throw new RouteCollisionError(`route collision: "${existing.path}" (${existing.router}) and "${route.path}" (${route.router}) declare conflicting dynamic segments (${existing.segment} vs ${segment}) at the same position — Next refuses different slug names for the same dynamic path (PR9)`);
59
+ throw new RouteCollisionError(`route collision: "${existing.path}" (${existing.router}) and "${route.path}" (${route.router}) declare conflicting dynamic segments (${existing.segment} vs ${segment}) at the same position — Next refuses different slug names for the same dynamic path`);
60
60
  }
61
61
  if (existing === undefined) {
62
62
  dynamicAt.set(key, { ...route, segment });
@@ -67,7 +67,7 @@ export function assertNoStructuralCollisions(routes) {
67
67
  const base = segments.length === 1 ? "/" : `/${segments.slice(0, -1).join("/")}`;
68
68
  const baseRoute = byPath.get(base);
69
69
  if (baseRoute !== undefined) {
70
- throw new RouteCollisionError(`route collision: "${route.path}" (${route.router}) also matches "${base}" (${baseRoute.router}) — an optional catch-all has the same specificity as its base path, which Next refuses to build (PR9)`);
70
+ throw new RouteCollisionError(`route collision: "${route.path}" (${route.router}) also matches "${base}" (${baseRoute.router}) — an optional catch-all has the same specificity as its base path, which Next refuses to build`);
71
71
  }
72
72
  }
73
73
  }
@@ -1,11 +1,11 @@
1
1
  import { type CliIo } from "../cli-io.js";
2
2
  /**
3
- * @internal `paramour generate` and its `check` alias (TR7), in-process
4
- * testable: returns the exit code instead of exiting. Codes are grep-style
5
- * so CI can tell drift from breakage: 0 success, 1 check-drift ONLY, 2
6
- * usage/config/operational errors — route collisions included (PR9: Next
7
- * itself fails that build, so there is no artifact to emit). Unlike the
8
- * wrapper's never-load-bearing stance (§7.3), the CLI fails loudly —
9
- * running it is explicit user intent.
3
+ * @internal `paramour generate` and its `check` alias, in-process testable:
4
+ * returns the exit code instead of exiting. Codes are grep-style so CI can
5
+ * tell drift from breakage: 0 success, 1 check-drift ONLY, 2
6
+ * usage/config/operational errors — route collisions included (Next itself
7
+ * fails that build, so there is no artifact to emit). Unlike the wrapper,
8
+ * which is never load-bearing, the CLI fails loudly — running it is explicit
9
+ * user intent.
10
10
  */
11
11
  export declare function runGenerate(argv: readonly string[], io: CliIo, mode: "check" | "generate"): Promise<number>;
@@ -38,13 +38,13 @@ const CHECK_USAGE = [
38
38
  ...SHARED_OPTION_LINES,
39
39
  ].join("\n");
40
40
  /**
41
- * @internal `paramour generate` and its `check` alias (TR7), in-process
42
- * testable: returns the exit code instead of exiting. Codes are grep-style
43
- * so CI can tell drift from breakage: 0 success, 1 check-drift ONLY, 2
44
- * usage/config/operational errors — route collisions included (PR9: Next
45
- * itself fails that build, so there is no artifact to emit). Unlike the
46
- * wrapper's never-load-bearing stance (§7.3), the CLI fails loudly —
47
- * running it is explicit user intent.
41
+ * @internal `paramour generate` and its `check` alias, in-process testable:
42
+ * returns the exit code instead of exiting. Codes are grep-style so CI can
43
+ * tell drift from breakage: 0 success, 1 check-drift ONLY, 2
44
+ * usage/config/operational errors — route collisions included (Next itself
45
+ * fails that build, so there is no artifact to emit). Unlike the wrapper,
46
+ * which is never load-bearing, the CLI fails loudly — running it is explicit
47
+ * user intent.
48
48
  */
49
49
  export async function runGenerate(argv, io, mode) {
50
50
  const { stderr, stdout } = resolveIo(io);
@@ -105,7 +105,7 @@ function describeRoutes(result) {
105
105
  ];
106
106
  return parts.length === 0 ? "0 routes" : parts.join(", ");
107
107
  }
108
- /** `--check` (TR7): exit 1 on any drift, including a missing artifact. */
108
+ /** `--check`: exit 1 on any drift, including a missing artifact. */
109
109
  function runCheck(inputs, stdout, stderr) {
110
110
  let result;
111
111
  try {
@@ -132,7 +132,7 @@ function runCheck(inputs, stdout, stderr) {
132
132
  stderr("Run `paramour generate` and commit the result.");
133
133
  return 1;
134
134
  }
135
- /** One-shot `paramour generate` (TR7). */
135
+ /** One-shot `paramour generate`. */
136
136
  function runOnce(inputs, stdout, stderr) {
137
137
  let result;
138
138
  try {
@@ -149,8 +149,8 @@ function runOnce(inputs, stdout, stderr) {
149
149
  return 0;
150
150
  }
151
151
  /**
152
- * `--watch` (TR7): TR5 watcher behind the TR6 lock, over both route dirs
153
- * (PR8). A declined lock exits 0 — another live watcher (usually `next dev`)
152
+ * `--watch`: the watcher behind the cross-process lock, over both route
153
+ * dirs. A declined lock exits 0 — another live watcher (usually `next dev`)
154
154
  * is the designed dedupe case, and the initial generation already ran.
155
155
  * Without an abort signal the returned promise stays pending; the process
156
156
  * lives via the FSWatcher refs and dies with the standard signal exits
@@ -172,7 +172,7 @@ function runWatch(inputs, projectRoot, io, stdout, stderr) {
172
172
  }
173
173
  catch (error) {
174
174
  // A corrupt lock location (e.g. a directory at the pidfile path) is an
175
- // operational error, not a crash: exit 2 like every other one (TR7).
175
+ // operational error, not a crash: exit 2 like every other one.
176
176
  stderr(`paramour: ${message(error)}`);
177
177
  return 2;
178
178
  }
@@ -196,13 +196,13 @@ function runWatch(inputs, projectRoot, io, stdout, stderr) {
196
196
  }
197
197
  catch (error) {
198
198
  if (error instanceof RouteCollisionError) {
199
- // PR9's watch exception: a collision mid-watch is usually a file
200
- // mid-move — log loudly every time, keep the last good artifact
201
- // on disk, keep running (TR5).
199
+ // The collision watch exception: a collision mid-watch is usually
200
+ // a file mid-move — log loudly every time, keep the last good
201
+ // artifact on disk, keep running.
202
202
  stderr(`paramour: ${message(error)}; keeping the last good artifact and watching for the fix`);
203
203
  return;
204
204
  }
205
- throw error; // routed to onError by the watcher (TR5 non-fatal)
205
+ throw error; // routed to onError by the watcher — non-fatal
206
206
  }
207
207
  },
208
208
  });
@@ -67,7 +67,7 @@ export async function runInit(argv, io) {
67
67
  }
68
68
  else {
69
69
  // A .mjs/.json left behind would be shadowed by the scaffold under the
70
- // ts-first discovery order (§7.2) — --force must truly replace it.
70
+ // ts-first discovery order — --force must truly replace it.
71
71
  if (existing !== undefined && existing !== "paramour.config.ts" && !dry) {
72
72
  unlinkSync(join(projectRoot, existing));
73
73
  }
package/dist/config.d.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  /**
2
- * Shape of `paramour.config.{ts,mjs,json}` (§7.2 / TR7) — the CLI's config
3
- * file. Every field is optional; the CLI's precedence is flags → this file →
4
- * inference. `.ts`/`.mjs` files default-export this object.
2
+ * Shape of `paramour.config.{ts,mjs,json}` — the CLI's config file. Every
3
+ * field is optional; the CLI's precedence is flags → this file → inference.
4
+ * `.ts`/`.mjs` files default-export this object.
5
5
  */
6
6
  export interface ParamourConfig {
7
- /** App dir, relative to the project root; default: joint discovery (PR8). */
7
+ /** App dir, relative to the project root; default: joint discovery. */
8
8
  appDir?: string;
9
- /** Artifact path, relative to the project root (TR3 escape hatch). */
9
+ /** Artifact path, relative to the project root — the escape hatch. */
10
10
  outFile?: string;
11
11
  /** Page extensions, no leading dot; default: Next's four. */
12
12
  pageExtensions?: string[];
13
- /** Pages dir, relative to the project root; default: joint discovery (PR8). */
13
+ /** Pages dir, relative to the project root; default: joint discovery. */
14
14
  pagesDir?: string;
15
15
  /**
16
16
  * Globs (relative to the project root) of modules exporting route
@@ -20,14 +20,14 @@ export interface ParamourConfig {
20
20
  */
21
21
  routeFiles?: string[];
22
22
  }
23
- /** @internal Discovery order at the project root (TR7) — first match wins. */
23
+ /** @internal Discovery order at the project root — first match wins. */
24
24
  export declare const CONFIG_FILE_NAMES: readonly ["paramour.config.ts", "paramour.config.mjs", "paramour.config.json"];
25
25
  /**
26
26
  * @internal Load and validate the project's config file, or `undefined`
27
27
  * when none exists. No upward traversal — the documented contract is three
28
- * filenames at the project root (TR7). jiti (the §7.2 loader carry-over) is
29
- * imported dynamically so only CLI runs that actually have a `.ts`/`.mjs`
30
- * config pay for it; `withTypedRoutes` users never execute it.
28
+ * filenames at the project root. jiti is imported dynamically so only CLI
29
+ * runs that actually have a `.ts`/`.mjs` config pay for it;
30
+ * `withTypedRoutes` users never execute it.
31
31
  */
32
32
  export declare function loadConfigFile(projectRoot: string): Promise<undefined | {
33
33
  config: ParamourConfig;
package/dist/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- /** @internal Discovery order at the project root (TR7) — first match wins. */
3
+ /** @internal Discovery order at the project root — first match wins. */
4
4
  export const CONFIG_FILE_NAMES = [
5
5
  "paramour.config.ts",
6
6
  "paramour.config.mjs",
@@ -9,9 +9,9 @@ export const CONFIG_FILE_NAMES = [
9
9
  /**
10
10
  * @internal Load and validate the project's config file, or `undefined`
11
11
  * when none exists. No upward traversal — the documented contract is three
12
- * filenames at the project root (TR7). jiti (the §7.2 loader carry-over) is
13
- * imported dynamically so only CLI runs that actually have a `.ts`/`.mjs`
14
- * config pay for it; `withTypedRoutes` users never execute it.
12
+ * filenames at the project root. jiti is imported dynamically so only CLI
13
+ * runs that actually have a `.ts`/`.mjs` config pay for it;
14
+ * `withTypedRoutes` users never execute it.
15
15
  */
16
16
  export async function loadConfigFile(projectRoot) {
17
17
  for (const name of CONFIG_FILE_NAMES) {