@paramour-js/next 0.3.1 → 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 +48 -21
  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 +59 -0
  27. package/dist/navigation-adapter.js +9 -0
  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 +25 -9
  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 +68 -0
  43. package/dist/testing.js +113 -0
  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 +6 -2
@@ -1,6 +1,6 @@
1
1
  import type { AnyRoute, ParamsSource, RouterKind, SafeResult } from "paramour";
2
2
  /**
3
- * The devtools observation seam (design-12 DT5): a dependency-free global
3
+ * The devtools observation seam: a dependency-free global
4
4
  * slot the hooks push decode observations into and the devtools panel
5
5
  * (`@paramour-js/devtools`) reads out of. This module's JSDoc is the
6
6
  * CONTRACT OF RECORD for the slot — the panel never imports runtime code
@@ -10,7 +10,7 @@ import type { AnyRoute, ParamsSource, RouterKind, SafeResult } from "paramour";
10
10
  *
11
11
  * - Slot key: `Symbol.for("paramour.devtools.seam")` — the realm-global
12
12
  * symbol registry, the same cross-copy identity idiom as core's error
13
- * brands (RL6): a second physical copy of this module (dual-package
13
+ * brands: a second physical copy of this module (dual-package
14
14
  * hazard, bundler duplication) mints the SAME symbol and lands on the
15
15
  * same slot. Either side — hooks or panel — may create the slot; both
16
16
  * sides are create-if-absent.
@@ -24,14 +24,14 @@ import type { AnyRoute, ParamsSource, RouterKind, SafeResult } from "paramour";
24
24
  * `add` — same JS thread, so nothing can be emitted between the read and
25
25
  * the add. Emitters push to `buffer` (FIFO-capped at
26
26
  * {@link OBSERVATION_BUFFER_CAP}) and then invoke every listener.
27
- * - Production (DT6): every emit call site sits behind
27
+ * - Production: every emit call site sits behind
28
28
  * `process.env.NODE_ENV !== "production"`, which Next's compilers
29
29
  * constant-fold; with the package's `sideEffects: false` the then-dead
30
30
  * import of this module is dropped entirely. The emitted JS here imports
31
31
  * NOTHING (the one `paramour` import is type-only) — load-bearing for
32
32
  * that erasure.
33
33
  */
34
- /** The `Symbol.for("paramour.devtools.seam")` slot shape — the DT5 contract. */
34
+ /** The `Symbol.for("paramour.devtools.seam")` slot shape — the seam contract. */
35
35
  export interface ParamourDevtoolsSeam {
36
36
  /** Capped FIFO; oldest dropped past the cap. Replay = read it. */
37
37
  readonly buffer: ParamourObservation[];
@@ -43,12 +43,12 @@ export interface ParamourDevtoolsSeam {
43
43
  */
44
44
  readonly version: 1;
45
45
  }
46
- /** Discriminant naming which hook reported (design-12 DT4). */
46
+ /** Discriminant naming which hook reported. */
47
47
  export type ParamourHookId = "app.useRouteParams" | "app.useRouteParamsOrThrow" | "app.useSearch" | "app.useSearchOrThrow" | "pages.useRouteParams" | "pages.useSearch";
48
48
  /**
49
- * Navigation capability captured from the EMITTING hook's router (design-12
50
- * DT8): the panel commits URL edits through this, so it never guesses which
51
- * router is live and never imports Next. The panel passes ONLY the
49
+ * Navigation capability captured from the EMITTING hook's router: the panel
50
+ * commits URL edits through this, so it never guesses which router is live
51
+ * and never imports Next. The panel passes ONLY the
52
52
  * serialized search string (`""` or `"?…"`); the hook resolves it against
53
53
  * its OWN current pathname — `usePathname()` (App) / `asPath`'s path part
54
54
  * (Pages), both basePath-/locale-relative, which is what `replace()`
@@ -60,18 +60,18 @@ export type ParamourHookId = "app.useRouteParams" | "app.useRouteParamsOrThrow"
60
60
  */
61
61
  export type ParamourNavigate = (search: string) => void;
62
62
  /**
63
- * One hook decode, reported on decode CHANGE (design-12 DT4) — and
63
+ * One hook decode, reported on decode CHANGE — and
64
64
  * re-reported when the hook's resolution base moves under an unchanged
65
65
  * decode (a layout surviving `/product/1?q=a` → `/product/2?q=a`), so the
66
66
  * captured `navigate`/`pathname` never go stale while the hook is mounted.
67
67
  */
68
68
  export type ParamourObservation = ParamourParamsObservation | ParamourSearchObservation;
69
69
  /**
70
- * Pre-`select` decode result (design-12 DT12): the hook's full `SafeResult`
71
- * — the error arm carries the LIVE `ParamsDecodeError`/`SearchDecodeError`
72
- * with its `issues` — never the user's `select` projection. `pending` is
73
- * the Pages-only third state (DT11). Generic-erased on purpose: the panel
74
- * treats `data` structurally.
70
+ * Pre-`select` decode result: the hook's full `SafeResult` — the error arm
71
+ * carries the LIVE `ParamsDecodeError`/`SearchDecodeError` with its
72
+ * `issues` — never the user's `select` projection. `pending` is the
73
+ * Pages-only third state. Generic-erased on purpose: the panel treats
74
+ * `data` structurally.
75
75
  */
76
76
  export type ParamourObservationResult = SafeResult<unknown> | {
77
77
  readonly status: "pending";
@@ -84,7 +84,7 @@ export interface ParamourParamsObservation extends ParamourObservationBase {
84
84
  /**
85
85
  * Search decode: wire is decode-time `[key, value]` pairs in wire order —
86
86
  * order is load-bearing for repeated keys (P5/S5), and pairs round-trip
87
- * losslessly into the panel's raw-wire editing (DT8).
87
+ * losslessly into the panel's raw-wire editing.
88
88
  */
89
89
  export interface ParamourSearchObservation extends ParamourObservationBase {
90
90
  readonly kind: "search";
@@ -106,7 +106,7 @@ interface ParamourObservationBase {
106
106
  readonly pathname: string;
107
107
  readonly result: ParamourObservationResult;
108
108
  /**
109
- * The LIVE route object (DT5: same JS context, no serialization) — the
109
+ * The LIVE route object (same JS context, no serialization) — the
110
110
  * panel calls `describeRoute`, the route's own codecs, and
111
111
  * `buildSearchString` on it directly.
112
112
  */
@@ -115,7 +115,7 @@ interface ParamourObservationBase {
115
115
  }
116
116
  /**
117
117
  * 128: replay only needs the pre-panel-mount window. One observation per
118
- * decode CHANGE per hook (DT4) means even a long pre-open session is dozens
118
+ * decode CHANGE per hook means even a long pre-open session is dozens
119
119
  * of entries, not thousands; the panel keys on route, so depth beyond
120
120
  * "every route seen recently" adds nothing — the cap mostly bounds how many
121
121
  * live route/result references the buffer retains.
@@ -123,8 +123,8 @@ interface ParamourObservationBase {
123
123
  export declare const OBSERVATION_BUFFER_CAP = 128;
124
124
  /**
125
125
  * Pushes one observation and notifies listeners. The internal production
126
- * early-return is belt-and-suspenders under DT6 (every call site is ALSO
127
- * guarded, which is what the bundler erases); it makes the guard directly
126
+ * early-return is belt-and-suspenders (every call site is ALSO guarded,
127
+ * which is what the bundler erases); it makes the guard directly
128
128
  * unit-testable and keeps a future unguarded call site failing safe.
129
129
  */
130
130
  export declare function emitObservation(observation: ParamourObservation): void;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * 128: replay only needs the pre-panel-mount window. One observation per
3
- * decode CHANGE per hook (DT4) means even a long pre-open session is dozens
3
+ * decode CHANGE per hook means even a long pre-open session is dozens
4
4
  * of entries, not thousands; the panel keys on route, so depth beyond
5
5
  * "every route seen recently" adds nothing — the cap mostly bounds how many
6
6
  * live route/result references the buffer retains.
@@ -10,8 +10,8 @@ const SEAM_KEY = Symbol.for("paramour.devtools.seam");
10
10
  const globalSlots = globalThis;
11
11
  /**
12
12
  * Pushes one observation and notifies listeners. The internal production
13
- * early-return is belt-and-suspenders under DT6 (every call site is ALSO
14
- * guarded, which is what the bundler erases); it makes the guard directly
13
+ * early-return is belt-and-suspenders (every call site is ALSO guarded,
14
+ * which is what the bundler erases); it makes the guard directly
15
15
  * unit-testable and keeps a future unguarded call site failing safe.
16
16
  */
17
17
  export function emitObservation(observation) {
@@ -34,7 +34,7 @@ export async function runDoctorChecks(projectRoot) {
34
34
  status: "fail",
35
35
  });
36
36
  }
37
- // 2. Route directories resolve (PR8 discovery, config dirs honored).
37
+ // 2. Route directories resolve (joint discovery, config dirs honored).
38
38
  let inputs;
39
39
  try {
40
40
  inputs = await resolveInputs({}, projectRoot, config);
package/dist/emit.d.ts CHANGED
@@ -2,34 +2,34 @@
2
2
  export interface WriteIfChangedResult {
3
3
  /**
4
4
  * Prior file content, or `null` when the file did not exist — the input
5
- * for TR4's drift warning (which paths appeared/disappeared).
5
+ * for the drift warning (which paths appeared/disappeared).
6
6
  */
7
7
  previousContent: null | string;
8
8
  /**
9
- * `false` on a byte-identical no-op — the signal `--check` (TR7) and the
10
- * watch loop (TR5) hang off.
9
+ * `false` on a byte-identical no-op — the signal `--check` and the watch
10
+ * loop hang off.
11
11
  */
12
12
  written: boolean;
13
13
  }
14
- /** Input of {@link emitArtifact} — one union per router (PR9). */
14
+ /** Input of {@link emitArtifact} — one union per router. */
15
15
  export interface EmitRoutes {
16
16
  appRoutes: readonly string[];
17
17
  pagesRoutes: readonly string[];
18
18
  }
19
19
  /**
20
- * Artifact text for the per-router route unions (TR3/PR9): deterministic —
21
- * sorted, deduped, LF-only, trailing newline, no timestamps. Sorting/deduping
20
+ * Artifact text for the per-router route unions: deterministic — sorted,
21
+ * deduped, LF-only, trailing newline, no timestamps. Sorting/deduping
22
22
  * happens here as well as in the scanner so byte-identity never depends on
23
23
  * who the caller was. Always the leading-pipe multiline union form, even for
24
24
  * one path — one shape, no count-dependent formatting. An empty union omits
25
- * its member entirely (TR3's absent-not-`never` rule, applied per router in
26
- * PR9): a pages-only project keeps world-A's permissive `string` fallback
27
- * for `defineAppRoute`, and vice versa.
25
+ * its member entirely (absent, not `never`, applied per router): a
26
+ * pages-only project keeps world-A's permissive `string` fallback for
27
+ * `defineAppRoute`, and vice versa.
28
28
  */
29
29
  export declare function emitArtifact(routes: EmitRoutes): string;
30
30
  /**
31
- * Compare-before-write (TR3): a no-op regeneration must not touch the file —
32
- * that property is what prevents TS-server churn, watch-loop feedback, and
33
- * concurrent-generator races (TR6).
31
+ * Compare-before-write: a no-op regeneration must not touch the file — that
32
+ * property is what prevents TS-server churn, watch-loop feedback, and
33
+ * concurrent-generator races.
34
34
  */
35
35
  export declare function writeIfChanged(filePath: string, content: string): WriteIfChangedResult;
package/dist/emit.js CHANGED
@@ -2,14 +2,14 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  const HEADER = "// Generated by @paramour-js/next. Do not edit — regenerate with `paramour generate`.";
4
4
  /**
5
- * Artifact text for the per-router route unions (TR3/PR9): deterministic —
6
- * sorted, deduped, LF-only, trailing newline, no timestamps. Sorting/deduping
5
+ * Artifact text for the per-router route unions: deterministic — sorted,
6
+ * deduped, LF-only, trailing newline, no timestamps. Sorting/deduping
7
7
  * happens here as well as in the scanner so byte-identity never depends on
8
8
  * who the caller was. Always the leading-pipe multiline union form, even for
9
9
  * one path — one shape, no count-dependent formatting. An empty union omits
10
- * its member entirely (TR3's absent-not-`never` rule, applied per router in
11
- * PR9): a pages-only project keeps world-A's permissive `string` fallback
12
- * for `defineAppRoute`, and vice versa.
10
+ * its member entirely (absent, not `never`, applied per router): a
11
+ * pages-only project keeps world-A's permissive `string` fallback for
12
+ * `defineAppRoute`, and vice versa.
13
13
  */
14
14
  export function emitArtifact(routes) {
15
15
  const members = ["appRoutes", "pagesRoutes"].flatMap((member) => {
@@ -21,22 +21,22 @@ export function emitArtifact(routes) {
21
21
  // contain a quote or backslash (Linux/macOS), which unescaped would emit
22
22
  // invalid TS or silently declare the wrong route. JSON string escaping is
23
23
  // a subset of TS string-literal escaping (output stays valid TS) and is
24
- // deterministic, so TR3's byte-identity guarantee holds.
24
+ // deterministic, so the byte-identity guarantee holds.
25
25
  return [
26
26
  ` ${member}:`,
27
27
  `${sorted.map((path) => ` | ${JSON.stringify(path)}`).join("\n")};`,
28
28
  ];
29
29
  });
30
30
  if (members.length === 0) {
31
- // TR3: no routes yet → NO members (explicitly not `never`, which would
31
+ // No routes yet → NO members (explicitly not `never`, which would
32
32
  // make every route-constructor call an error). The empty merge keeps
33
- // the documented world-A `string` fallback (RL8).
33
+ // the documented world-A `string` fallback.
34
34
  return [
35
35
  HEADER,
36
36
  'import "paramour";',
37
37
  "",
38
38
  'declare module "paramour" {',
39
- " // TR3: empty merge — no routes discovered yet; the pre-generation",
39
+ " // Empty merge — no routes discovered yet; the pre-generation",
40
40
  " // `string` fallback stays in effect.",
41
41
  " // eslint-disable-next-line @typescript-eslint/no-empty-object-type",
42
42
  " interface ParamourRegister {}",
@@ -57,9 +57,9 @@ export function emitArtifact(routes) {
57
57
  ].join("\n");
58
58
  }
59
59
  /**
60
- * Compare-before-write (TR3): a no-op regeneration must not touch the file —
61
- * that property is what prevents TS-server churn, watch-loop feedback, and
62
- * concurrent-generator races (TR6).
60
+ * Compare-before-write: a no-op regeneration must not touch the file — that
61
+ * property is what prevents TS-server churn, watch-loop feedback, and
62
+ * concurrent-generator races.
63
63
  */
64
64
  export function writeIfChanged(filePath, content) {
65
65
  const previousContent = existsSync(filePath)
@@ -67,7 +67,7 @@ export function writeIfChanged(filePath, content) {
67
67
  : null;
68
68
  if (previousContent === content)
69
69
  return { previousContent, written: false };
70
- // TR3's outFile escape hatch may point into a not-yet-existing directory.
70
+ // The outFile escape hatch may point into a not-yet-existing directory.
71
71
  mkdirSync(dirname(filePath), { recursive: true });
72
72
  writeFileSync(filePath, content);
73
73
  return { previousContent, written: true };
@@ -1,11 +1,11 @@
1
1
  /**
2
- * The shared generation engine (TR9): `withTypedRoutes` and the CLI drive
3
- * these same functions, so wrapper and `paramour generate` cannot drift on
4
- * what a pass produces. Everything here is @internal — not barrel API.
2
+ * The shared generation engine: `withTypedRoutes` and the CLI drive these
3
+ * same functions, so wrapper and `paramour generate` cannot drift on what a
4
+ * pass produces. Everything here is @internal — not barrel API.
5
5
  */
6
6
  /** Result of {@link checkArtifact}. */
7
7
  export interface CheckResult {
8
- /** App-router drift — split per router so the report names it (PR9). */
8
+ /** App-router drift — split per router so the report names it. */
9
9
  app: RouterDrift;
10
10
  /** `true` when the artifact file does not exist at all. */
11
11
  missingFile: boolean;
@@ -14,7 +14,7 @@ export interface CheckResult {
14
14
  /** `true` on a byte-identical artifact — the only non-drift state. */
15
15
  upToDate: boolean;
16
16
  }
17
- /** Inputs to one generation/check pass; either dir may be absent (PR1). */
17
+ /** Inputs to one generation/check pass; either dir may be absent. */
18
18
  export interface GenerateInputs {
19
19
  appDir?: string | undefined;
20
20
  artifactPath: string;
@@ -29,10 +29,10 @@ export interface GenerateResult {
29
29
  pagesRoutes: string[];
30
30
  /** Prior artifact content, `null` when the file did not exist. */
31
31
  previousContent: null | string;
32
- /** `false` on a byte-identical no-op (TR3 write-if-changed). */
32
+ /** `false` on a byte-identical no-op (write-if-changed). */
33
33
  written: boolean;
34
34
  }
35
- /** One router's appeared/disappeared route paths (TR4/TR7 drift). */
35
+ /** One router's appeared/disappeared route paths. */
36
36
  export interface RouterDrift {
37
37
  /** Routes on disk (scan) that the artifact lacks. */
38
38
  appeared: string[];
@@ -40,25 +40,25 @@ export interface RouterDrift {
40
40
  disappeared: string[];
41
41
  }
42
42
  /**
43
- * `--check` (TR7): scan to memory and byte-compare against disk — never
44
- * writes. A missing artifact is drift, not an error: that is exactly the
45
- * CI-degrades-to-world-A case the committed file exists to prevent (TR3).
43
+ * `--check`: scan to memory and byte-compare against disk — never writes. A
44
+ * missing artifact is drift, not an error: that is exactly the
45
+ * CI-degrades-to-world-A case the committed file exists to prevent.
46
46
  */
47
47
  export declare function checkArtifact(inputs: GenerateInputs): CheckResult;
48
48
  /**
49
49
  * Per-router drift of a completed {@link generate} pass against the artifact
50
- * it replaced — the wrapper's build-phase drift report (TR4).
50
+ * it replaced — the wrapper's build-phase drift report.
51
51
  */
52
52
  export declare function diffGenerated(result: GenerateResult): {
53
53
  app: RouterDrift;
54
54
  pages: RouterDrift;
55
55
  };
56
56
  /**
57
- * The ` + /new (app)` / ` - /old (pages)` lines of a drift report
58
- * (TR4/TR7) — each line names the router its path moved in (PR9).
57
+ * The ` + /new (app)` / ` - /old (pages)` lines of a drift report — each
58
+ * line names the router its path moved in.
59
59
  */
60
60
  export declare function formatRouteDiff(app: RouterDrift, pages: RouterDrift): string[];
61
- /** One generation pass: scan → emit → write-if-changed (TR3). */
61
+ /** One generation pass: scan → emit → write-if-changed. */
62
62
  export declare function generate(inputs: GenerateInputs): GenerateResult;
63
63
  /**
64
64
  * Per-router route paths in a previously emitted artifact; both empty for a
package/dist/generate.js CHANGED
@@ -3,17 +3,17 @@ import { emitArtifact, writeIfChanged } from "./emit.js";
3
3
  import { scanRoutes } from "./scan.js";
4
4
  /**
5
5
  * Reads the per-router unions back out of a previously emitted artifact for
6
- * drift diffs (TR4/TR7). Only ever applied to text this package generated
7
- * (TR3 deterministic form), so line-anchored matches on the member headers
8
- * and union members are exact, not heuristic. `\s*$` on both tolerates a
6
+ * drift diffs. Only ever applied to text this package generated (its
7
+ * deterministic form), so line-anchored matches on the member headers and
8
+ * union members are exact, not heuristic. `\s*$` on both tolerates a
9
9
  * CRLF-resaved artifact.
10
10
  */
11
11
  const MEMBER_HEADER = /^\s*(appRoutes|pagesRoutes):\s*$/;
12
12
  const UNION_MEMBER = /^\s*\| "(.*)";?\s*$/;
13
13
  /**
14
- * `--check` (TR7): scan to memory and byte-compare against disk — never
15
- * writes. A missing artifact is drift, not an error: that is exactly the
16
- * CI-degrades-to-world-A case the committed file exists to prevent (TR3).
14
+ * `--check`: scan to memory and byte-compare against disk — never writes. A
15
+ * missing artifact is drift, not an error: that is exactly the
16
+ * CI-degrades-to-world-A case the committed file exists to prevent.
17
17
  */
18
18
  export function checkArtifact(inputs) {
19
19
  const routes = scanRoutes(inputs, inputs.pageExtensions);
@@ -39,7 +39,7 @@ export function checkArtifact(inputs) {
39
39
  }
40
40
  /**
41
41
  * Per-router drift of a completed {@link generate} pass against the artifact
42
- * it replaced — the wrapper's build-phase drift report (TR4).
42
+ * it replaced — the wrapper's build-phase drift report.
43
43
  */
44
44
  export function diffGenerated(result) {
45
45
  const previous = parseArtifactRoutes(result.previousContent);
@@ -49,8 +49,8 @@ export function diffGenerated(result) {
49
49
  };
50
50
  }
51
51
  /**
52
- * The ` + /new (app)` / ` - /old (pages)` lines of a drift report
53
- * (TR4/TR7) — each line names the router its path moved in (PR9).
52
+ * The ` + /new (app)` / ` - /old (pages)` lines of a drift report — each
53
+ * line names the router its path moved in.
54
54
  */
55
55
  export function formatRouteDiff(app, pages) {
56
56
  return [
@@ -60,7 +60,7 @@ export function formatRouteDiff(app, pages) {
60
60
  ...pages.disappeared.map((path) => ` - ${path} (pages)`),
61
61
  ];
62
62
  }
63
- /** One generation pass: scan → emit → write-if-changed (TR3). */
63
+ /** One generation pass: scan → emit → write-if-changed. */
64
64
  export function generate(inputs) {
65
65
  const routes = scanRoutes(inputs, inputs.pageExtensions);
66
66
  return {
@@ -92,7 +92,7 @@ export function parseArtifactRoutes(previousContent) {
92
92
  continue;
93
93
  }
94
94
  // Any other line ends the member block — union members are contiguous
95
- // in the TR3 deterministic form.
95
+ // in the deterministic emitted form.
96
96
  current = undefined;
97
97
  }
98
98
  return { appRoutes, pagesRoutes };
@@ -37,10 +37,10 @@ export interface RouteDefinition {
37
37
  * its text mentions a define constructor (grep-then-load). `routeFiles`
38
38
  * config globs replace the default patterns when the heuristic misfires.
39
39
  *
40
- * jiti evaluates matched files (the §7.2 loader carry-over). Known limits,
41
- * both handled by the per-module degrade: tsconfig `paths` aliases are not
42
- * resolved (a future improvement could feed them into jiti's `alias`
43
- * option), and `server-only`-style imports throw outside Next.
40
+ * jiti evaluates matched files (the loader carry-over from config loading).
41
+ * Known limits, both handled by the per-module degrade: tsconfig `paths`
42
+ * aliases are not resolved (a future improvement could feed them into jiti's
43
+ * `alias` option), and `server-only`-style imports throw outside Next.
44
44
  */
45
45
  export declare function discoverRouteDefinitions(projectRoot: string, options?: {
46
46
  routeFiles?: readonly string[] | undefined;
@@ -23,10 +23,10 @@ const MAX_PREFILTER_BYTES = 512 * 1024;
23
23
  * its text mentions a define constructor (grep-then-load). `routeFiles`
24
24
  * config globs replace the default patterns when the heuristic misfires.
25
25
  *
26
- * jiti evaluates matched files (the §7.2 loader carry-over). Known limits,
27
- * both handled by the per-module degrade: tsconfig `paths` aliases are not
28
- * resolved (a future improvement could feed them into jiti's `alias`
29
- * option), and `server-only`-style imports throw outside Next.
26
+ * jiti evaluates matched files (the loader carry-over from config loading).
27
+ * Known limits, both handled by the per-module degrade: tsconfig `paths`
28
+ * aliases are not resolved (a future improvement could feed them into jiti's
29
+ * `alias` option), and `server-only`-style imports throw outside Next.
30
30
  */
31
31
  export async function discoverRouteDefinitions(projectRoot, options = {}) {
32
32
  // Dynamic imports, same stance as config.ts: only commands that actually
package/dist/lock.d.ts CHANGED
@@ -12,18 +12,17 @@ export interface AcquireLockResult {
12
12
  release?: () => void;
13
13
  }
14
14
  /**
15
- * Cross-process single-writer guard (TR6): a best-effort pidfile lock. On
16
- * startup: read lock → liveness-probe the owner → decline if alive,
17
- * (over)write and acquire if dead or absent. Deliberately best-effort, not
18
- * correct — TR3's deterministic write-if-changed output means two live
19
- * watchers produce identical bytes; imperfect locking costs a log line, not
20
- * corruption. Hence no flock semantics, atomic-rename dances, or PID-reuse
21
- * paranoia. The in-process singleton (TR6 guard 1) lives at the composition
22
- * points, not here.
15
+ * Cross-process single-writer guard: a best-effort pidfile lock. On startup:
16
+ * read lock → liveness-probe the owner → decline if alive, (over)write and
17
+ * acquire if dead or absent. Deliberately best-effort, not correct — the
18
+ * deterministic write-if-changed output means two live watchers produce
19
+ * identical bytes; imperfect locking costs a log line, not corruption. Hence
20
+ * no flock semantics, atomic-rename dances, or PID-reuse paranoia. The
21
+ * in-process singleton guard lives at the composition points, not here.
23
22
  */
24
23
  export declare function acquireWatcherLock(lockPath: string): AcquireLockResult;
25
24
  /**
26
- * The one canonical pidfile location (TR6): CLI-vs-wrapper dedupe only works
25
+ * The one canonical pidfile location: CLI-vs-wrapper dedupe only works
27
26
  * because both paths compute the lock from the same project root.
28
27
  */
29
28
  export declare function watcherLockPath(projectRoot: string): string;
package/dist/lock.js CHANGED
@@ -3,14 +3,13 @@ import { dirname, join } from "node:path";
3
3
  /** Strict anchored PID parse — anything else is a stale/corrupt lock. */
4
4
  const PID_RE = /^\d+$/;
5
5
  /**
6
- * Cross-process single-writer guard (TR6): a best-effort pidfile lock. On
7
- * startup: read lock → liveness-probe the owner → decline if alive,
8
- * (over)write and acquire if dead or absent. Deliberately best-effort, not
9
- * correct — TR3's deterministic write-if-changed output means two live
10
- * watchers produce identical bytes; imperfect locking costs a log line, not
11
- * corruption. Hence no flock semantics, atomic-rename dances, or PID-reuse
12
- * paranoia. The in-process singleton (TR6 guard 1) lives at the composition
13
- * points, not here.
6
+ * Cross-process single-writer guard: a best-effort pidfile lock. On startup:
7
+ * read lock → liveness-probe the owner → decline if alive, (over)write and
8
+ * acquire if dead or absent. Deliberately best-effort, not correct — the
9
+ * deterministic write-if-changed output means two live watchers produce
10
+ * identical bytes; imperfect locking costs a log line, not corruption. Hence
11
+ * no flock semantics, atomic-rename dances, or PID-reuse paranoia. The
12
+ * in-process singleton guard lives at the composition points, not here.
14
13
  */
15
14
  export function acquireWatcherLock(lockPath) {
16
15
  const ownerPid = readOwnerPid(lockPath);
@@ -35,8 +34,8 @@ export function acquireWatcherLock(lockPath) {
35
34
  }
36
35
  }
37
36
  catch {
38
- // Best-effort (TR6): a leftover lock self-heals via the liveness
39
- // probe on the next startup.
37
+ // Best-effort: a leftover lock self-heals via the liveness probe on
38
+ // the next startup.
40
39
  }
41
40
  };
42
41
  const reraise = (signal) => {
@@ -57,13 +56,13 @@ export function acquireWatcherLock(lockPath) {
57
56
  return { acquired: true, release };
58
57
  }
59
58
  /**
60
- * The one canonical pidfile location (TR6): CLI-vs-wrapper dedupe only works
59
+ * The one canonical pidfile location: CLI-vs-wrapper dedupe only works
61
60
  * because both paths compute the lock from the same project root.
62
61
  */
63
62
  export function watcherLockPath(projectRoot) {
64
63
  return join(projectRoot, "node_modules", ".cache", "paramour", "watcher.lock");
65
64
  }
66
- /** `true` when `pid` is a live process (TR6 liveness probe). */
65
+ /** `true` when `pid` is a live process — the liveness probe. */
67
66
  function isAlive(pid) {
68
67
  try {
69
68
  process.kill(pid, 0);
@@ -0,0 +1,59 @@
1
+ import type { ParamsSource } from "paramour";
2
+ /**
3
+ * Adapter seam for the client hooks' framework reads: each flavor's hooks
4
+ * resolve Next through a React context so the /testing entry can override
5
+ * the reads without runner-specific module mocking. Deliberately NO
6
+ * `"use client"` directive — app.ts (which carries one) and pages.ts (which
7
+ * must not) both import from here, like observe.ts/select.ts; the directive
8
+ * belongs on the entry modules, not a shared leaf.
9
+ *
10
+ * This module is Next-free on purpose: the contexts default to `null`
11
+ * and each flavor ENTRY supplies its own real-Next fallback
12
+ * (`useContext(ctx) ?? realAdapter`), so neither this module nor the
13
+ * /testing entry ever drags a `next/*` specifier into its graph. The
14
+ * dist.test.ts bundle-hygiene invariants (/app reaches only
15
+ * `next/navigation`, /pages only `next/router.js`, /testing neither) depend
16
+ * on exactly this split — a context whose DEFAULT VALUE were the real
17
+ * adapter would break all three.
18
+ */
19
+ /**
20
+ * App-flavor adapter: EXACTLY the ambient view of `next/navigation` declared
21
+ * in `src/types/next-navigation.d.ts` — that ambient is the contract of
22
+ * record, and this interface must stay in lockstep with it (the
23
+ * `examples/next-compat` pins guard the real-Next side). `useParams()`'s
24
+ * `null` arm is the outside-App-Router-tree state (Next #48058 family) the
25
+ * hooks deliberately tolerate; `useRouter().replace`/`usePathname` are the
26
+ * devtools `navigate` capability's write path and resolution base.
27
+ */
28
+ export interface AppNavigationAdapter {
29
+ useParams(): null | ParamsSource;
30
+ usePathname(): string;
31
+ useRouter(): {
32
+ replace(href: string): void;
33
+ };
34
+ useSearchParams(): URLSearchParams;
35
+ }
36
+ /**
37
+ * Pages-flavor adapter: EXACTLY the ambient view of `next/router.js`
38
+ * declared in `src/types/next-router.d.ts` — that ambient is the contract of
39
+ * record, and this interface must stay in lockstep with it. The ambient's
40
+ * throw-on-unmounted behavior under `app/` is part of the contract: adapter
41
+ * implementations reproduce it by THROWING from `useRouter()`, which
42
+ * pages.ts translates.
43
+ */
44
+ export interface PagesNavigationAdapter {
45
+ useRouter(): {
46
+ asPath: string;
47
+ isReady: boolean;
48
+ query: ParamsSource;
49
+ replace(url: string): Promise<boolean>;
50
+ };
51
+ }
52
+ /**
53
+ * The `null` default is load-bearing: with no provider mounted, app.ts falls
54
+ * back to its real `next/navigation` adapter — zero markup and zero behavior
55
+ * change in production.
56
+ */
57
+ export declare const AppNavigationContext: import("react").Context<AppNavigationAdapter | null>;
58
+ /** The pages twin; its `null` default is load-bearing for the same reasons. */
59
+ export declare const PagesNavigationContext: import("react").Context<PagesNavigationAdapter | null>;
@@ -0,0 +1,9 @@
1
+ import { createContext } from "react";
2
+ /**
3
+ * The `null` default is load-bearing: with no provider mounted, app.ts falls
4
+ * back to its real `next/navigation` adapter — zero markup and zero behavior
5
+ * change in production.
6
+ */
7
+ export const AppNavigationContext = createContext(null);
8
+ /** The pages twin; its `null` default is load-bearing for the same reasons. */
9
+ export const PagesNavigationContext = createContext(null);
package/dist/observe.d.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  import type { AnyRoute, ParamsSource, RouterKind } from "paramour";
2
2
  import type { ParamourHookId, ParamourNavigate, ParamourObservationResult, ParamourSearchWire } from "./devtools-seam.js";
3
3
  /**
4
- * Shared devtools seam wiring for the six read hooks (design-12 DT4/DT8):
5
- * the navigate builders (one per router flavor) and the per-hook emitter
6
- * that owns WHEN an observation goes out. Deliberately NO `"use client"`
7
- * directive — app.ts (which carries one) and pages.ts (which must not,
8
- * PR2) both import from here, like select.ts.
4
+ * Shared devtools seam wiring for the six read hooks: the navigate builders
5
+ * (one per router flavor) and the per-hook emitter that owns WHEN an
6
+ * observation goes out. Deliberately NO `"use client"` directive — app.ts
7
+ * (which carries one) and pages.ts (which must not) both import from here,
8
+ * like select.ts.
9
9
  *
10
10
  * Emission policy: the hooks call {@link DevtoolsEmitter.observe} from
11
- * inside the `useStableResult` compute — the SEL4 fingerprint cache miss IS
12
- * the decode-change dedup (DT4), and only render-phase can report the
11
+ * inside the `useStableResult` compute — the fingerprint cache miss IS the
12
+ * decode-change dedup, and only render-phase can report the
13
13
  * OrThrow hooks' error observation before the rethrow. `observe` alone
14
14
  * would leave one staleness hole: a component that survives a navigation
15
15
  * whose decode is unchanged (`/product/1?q=a` → `/product/2?q=a` in a
@@ -18,10 +18,10 @@ import type { ParamourHookId, ParamourNavigate, ParamourObservationResult, Param
18
18
  * navigate back to the old resource. {@link DevtoolsEmitter.refresh},
19
19
  * called render-phase after the stable result returns, closes it: when the
20
20
  * resolution base (or a HMR-reminted route) moved under a cached decode, it
21
- * re-emits the CACHED result with the fresh spec — decode stability (SEL4)
22
- * is untouched, only the seam payload is renewed.
21
+ * re-emits the CACHED result with the fresh spec — decode stability is
22
+ * untouched, only the seam payload is renewed.
23
23
  *
24
- * Production erasure (DT6): every call site keeps a literal
24
+ * Production erasure: every call site keeps a literal
25
25
  * `process.env.NODE_ENV` guard the bundler constant-folds, and both emitter
26
26
  * methods early-return behind the same literal guard, so the
27
27
  * `emitObservation` import above is dead in a production bundle and drops
@@ -51,15 +51,15 @@ export interface ObservationSpec {
51
51
  readonly wire: () => ParamourSearchWire | Readonly<ParamsSource>;
52
52
  }
53
53
  /**
54
- * App-flavor navigate capability (DT8): `next/navigation`'s `replace`
55
- * returns void and resolves the basePath-/locale-relative join itself.
54
+ * App-flavor navigate capability: `next/navigation`'s `replace` returns void
55
+ * and resolves the basePath-/locale-relative join itself.
56
56
  */
57
57
  export declare function makeAppNavigate(router: {
58
58
  replace: (href: string) => void;
59
59
  }, pathname: string): ParamourNavigate;
60
60
  /**
61
- * Pages-flavor navigate capability (DT8): `next/router`'s `replace` returns
62
- * a promise that REJECTS on routine navigation aborts (rapid re-commits
61
+ * Pages-flavor navigate capability: `next/router`'s `replace` returns a
62
+ * promise that REJECTS on routine navigation aborts (rapid re-commits
63
63
  * from the panel), marked with next's `cancelled` discriminant — those must
64
64
  * not surface as unhandled rejections. Anything else is a real failure
65
65
  * (render error, route-info error) silently discarding the user's edit, so