@timber-js/app 0.2.0-alpha.207 → 0.2.0-alpha.208

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 (31) hide show
  1. package/dist/_chunks/{error-boundary-BndF-3Td.js → error-boundary-BrHmG-2X.js} +2 -2
  2. package/dist/_chunks/{error-boundary-BndF-3Td.js.map → error-boundary-BrHmG-2X.js.map} +1 -1
  3. package/dist/_chunks/{status-page-marker-DwQBrLBz.js → status-page-marker-CNbpUOPB.js} +88 -9
  4. package/dist/_chunks/status-page-marker-CNbpUOPB.js.map +1 -0
  5. package/dist/client/error-boundary.js +1 -1
  6. package/dist/client/history.d.ts +8 -0
  7. package/dist/client/history.d.ts.map +1 -1
  8. package/dist/client/internal.js +30 -7
  9. package/dist/client/internal.js.map +1 -1
  10. package/dist/client/navigation-commit.d.ts +4 -0
  11. package/dist/client/navigation-commit.d.ts.map +1 -1
  12. package/dist/client/router-pipeline.d.ts +2 -2
  13. package/dist/client/router-pipeline.d.ts.map +1 -1
  14. package/dist/client/router.d.ts.map +1 -1
  15. package/dist/client/segment-cache.d.ts +44 -4
  16. package/dist/client/segment-cache.d.ts.map +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +101 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/plugins/react-resolve.d.ts +49 -0
  21. package/dist/plugins/react-resolve.d.ts.map +1 -0
  22. package/dist/server/internal.js +1 -1
  23. package/package.json +3 -3
  24. package/src/client/history.ts +17 -0
  25. package/src/client/navigation-commit.ts +6 -0
  26. package/src/client/router-pipeline.ts +31 -4
  27. package/src/client/router.ts +22 -2
  28. package/src/client/segment-cache.ts +110 -11
  29. package/src/index.ts +2 -0
  30. package/src/plugins/react-resolve.ts +149 -0
  31. package/dist/_chunks/status-page-marker-DwQBrLBz.js.map +0 -1
@@ -63,9 +63,36 @@ export interface SegmentNode {
63
63
  */
64
64
  export declare class SegmentCache {
65
65
  private root;
66
+ /**
67
+ * Monotonic counter. Bumped by `invalidateReuse()` (TIM-1466).
68
+ * `serializeStateTree` suppresses `segments` and `slots` when
69
+ * `reuseInvalidatedAt > reuseRestoredAt`. Only `restoreReuse()`
70
+ * advances `reuseRestoredAt` — `set()` does not, because a
71
+ * pre-invalidation navigation's commit thunk would lift suppression
72
+ * before a post-invalidation request has been sent (codex on #1167).
73
+ */
74
+ private reuseInvalidatedAt;
75
+ private reuseRestoredAt;
66
76
  get(segment: string): SegmentNode | undefined;
67
77
  set(segment: string, node: SegmentNode): void;
68
78
  clear(): void;
79
+ /**
80
+ * Suppress segment and slot reuse without destroying the tree
81
+ * structure (TIM-1466). `serializeStateTree` sends empty
82
+ * `segments`/`slots` (forcing a full render) while `treePaths`
83
+ * still reflects the mounted tree for interception scoping.
84
+ * Call `restoreReuse()` once post-invalidation data has been
85
+ * fetched and committed.
86
+ */
87
+ invalidateReuse(): void;
88
+ /**
89
+ * Lift reuse suppression. Called after the segment cache has been
90
+ * rebuilt from a response that was fetched with suppression active
91
+ * (i.e. the server sent a full render). Safe to call idempotently.
92
+ */
93
+ restoreReuse(): void;
94
+ /** Whether segment/slot reuse is currently suppressed (TIM-1466). */
95
+ isReuseSuppressed(): boolean;
69
96
  /**
70
97
  * Serialize the mounted segment tree for the X-Timber-State-Tree header.
71
98
  * Only includes sync segments — async segments are excluded because the
@@ -140,12 +167,16 @@ export interface PrefetchKey {
140
167
  */
141
168
  export declare function prefetchScopeOf(stateTree: StateTree | undefined): string;
142
169
  /** Outcome of a prefetch flight — discriminated so NonRscResponse is a
143
- * success (stores a negative entry) rather than a rejection. */
170
+ * success (stores a negative entry) rather than a rejection.
171
+ * `startGen` is the eviction generation at flight start — joiners that
172
+ * started after a gen bump must reject pre-invalidation results (TIM-1464). */
144
173
  export type FlightOutcome = {
145
174
  kind: 'ready';
146
175
  result: PrefetchResult;
176
+ startGen: number;
147
177
  } | {
148
178
  kind: 'non-route';
179
+ startGen: number;
149
180
  };
150
181
  /**
151
182
  * Short-lived cache for hover-triggered prefetches. Entries expire after
@@ -206,9 +237,18 @@ export declare class PrefetchCache {
206
237
  /** Check if the entry is a negative cache entry (URL is not a route). */
207
238
  isNegative(key: PrefetchKey): boolean;
208
239
  /**
209
- * Invalidate all entries whose destination URL matches (TIM-1454).
210
- * Used by `revalidatePath` on a non-current route so the next navigation
211
- * fetches fresh data instead of replaying a stale prefetch.
240
+ * Invalidate entries whose destination URL matches (TIM-1454).
241
+ *
242
+ * When `url` has no search string, evicts all entries sharing that
243
+ * pathname — `/products` evicts `/products?page=1` too, because
244
+ * `revalidatePath('/products')` invalidates the route regardless of
245
+ * query (TIM-1465).
246
+ *
247
+ * When `url` has a search string, only the exact URL is evicted.
248
+ *
249
+ * Also bumps the eviction generation so in-flight singleflight
250
+ * flights for the invalidated URL(s) discard their results on
251
+ * completion (TIM-1464).
212
252
  */
213
253
  invalidateUrl(url: string): void;
214
254
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"segment-cache.d.ts","sourceRoot":"","sources":["../../src/client/segment-cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"segment-cache.d.ts","sourceRoot":"","sources":["../../src/client/segment-cache.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAKxE,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACrC,uFAAuF;IACvF,WAAW,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IACnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACpD,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;;OAMG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAC5B,2CAA2C;IAC3C,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACnC,kEAAkE;IAClE,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACjC,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAA0B;IACtC;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,eAAe,CAAK;IAE5B,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAK5C;IAED,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAI5C;IAED,KAAK,IAAI,IAAI,CAGZ;IAED;;;;;;;OAOG;IACH,eAAe,IAAI,IAAI,CAEtB;IAED;;;;OAIG;IACH,YAAY,IAAI,IAAI,CAEnB;IAED,qEAAqE;IACrE,iBAAiB,IAAI,OAAO,CAE3B;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBAAkB,CAAC,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAoB3D;CACF;AAsDD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,WAAW,GAAG,SAAS,CAmEjF;AAmBD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;CACf;AAgCD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAExE;AAED;;;gFAGgF;AAChF,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5C;;;;;;;;;;;;;GAaG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAU;IACxC,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,OAAO,CAAuE;IACtF,OAAO,CAAC,WAAW,CAAK;IAExB,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAKlD;IAED,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,GAAG,SAAS,CAShD;IAED,6DAA6D;IAC7D,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAE7B;IAED,0EAA0E;IAC1E,OAAO,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,GAAG,SAAS,CAMpD;IAED;;;;;;;;;;;;OAYG;IACH,eAAe,CACb,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,cAAc,CAAC,EACzD,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,GACtC,OAAO,CAAC,aAAa,CAAC,CAqBxB;IAED;;;;;;OAMG;IACH,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,GAAG,SAAS,CAEjE;IAED;;;;;;;OAOG;IACH,WAAW,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAElC;IAED,yEAAyE;IACzE,UAAU,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAEpC;IAED;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CA4B/B;IAED;;;;;;;;;;;;;OAaG;IACH,UAAU,IAAI,IAAI,CAGjB;IAED,cAAc,IAAI,MAAM,CAEvB;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAC;AAyCjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAa1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAQ1D,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAElG;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,MAAM;CAAG;AAE1B;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAEvE;AAyGD,wBAAgB,MAAM,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,YAAY,EAAE,CAiXhE;eAEc,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAC;AA0CjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAa1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAQ1D,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAElG;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,MAAM;CAAG;AAE1B;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAEvE;AAyGD,wBAAgB,MAAM,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,YAAY,EAAE,CAkXhE;eAEc,MAAM"}
package/dist/index.js CHANGED
@@ -5989,6 +5989,106 @@ function timberReactProd() {
5989
5989
  };
5990
5990
  }
5991
5991
  //#endregion
5992
+ //#region src/plugins/react-resolve.ts
5993
+ /**
5994
+ * timber-react-resolve — Deduplicate React across timber and user project.
5995
+ *
5996
+ * Without this plugin, pnpm's strict isolation can install two physical
5997
+ * copies of React when the user's pinned version differs from timber's
5998
+ * peer dep. Timber's 47 bare `from 'react'` imports resolve to one copy
5999
+ * while react-dom uses the other — causing "Invalid hook call" hydration
6000
+ * crashes with no indication of the real cause.
6001
+ *
6002
+ * The fix: inject `resolve.dedupe` entries so Vite always resolves React
6003
+ * packages from the project root. Unlike `resolve.alias`, `dedupe`
6004
+ * preserves Vite's per-environment export conditions — the RSC
6005
+ * environment still gets the `react-server` build, SSR gets the SSR
6006
+ * build, and the browser gets the browser build. The deduplication
6007
+ * happens at the package level, not the file level.
6008
+ *
6009
+ * A `configResolved` check detects version mismatches between timber's
6010
+ * resolved React and the project's React, and logs an actionable error.
6011
+ *
6012
+ * Design doc: 18-build-system.md §"Module Resolution"
6013
+ * Task: TIM-1520
6014
+ */
6015
+ /**
6016
+ * React packages to deduplicate. Vite's `resolve.dedupe` extracts the
6017
+ * base package name (via `deepImportRE`) before checking, so subpath
6018
+ * specifiers like `react/jsx-runtime` are covered by the base `react`
6019
+ * entry — listing them separately has no effect.
6020
+ */
6021
+ var REACT_DEDUPE_PACKAGES = ["react", "react-dom"];
6022
+ var RED = "\x1B[31m";
6023
+ var BOLD = "\x1B[1m";
6024
+ var DIM = "\x1B[2m";
6025
+ var RESET = "\x1B[0m";
6026
+ /**
6027
+ * Read the version from a package's package.json given a resolve root.
6028
+ */
6029
+ function readPackageVersion(packageName, resolveRoot) {
6030
+ try {
6031
+ const req = createRequire(`${resolveRoot}/package.json`);
6032
+ return req(req.resolve(`${packageName}/package.json`)).version ?? null;
6033
+ } catch {
6034
+ return null;
6035
+ }
6036
+ }
6037
+ /**
6038
+ * Check whether timber's React and the user project's React resolve to
6039
+ * the same version. Returns mismatches if any.
6040
+ */
6041
+ function detectReactMismatch(projectRoot) {
6042
+ const mismatches = [];
6043
+ const timberRoot = dirname(dirname(fileURLToPath(import.meta.url)));
6044
+ for (const pkg of ["react", "react-dom"]) {
6045
+ const timberVersion = readPackageVersion(pkg, timberRoot);
6046
+ const projectVersion = readPackageVersion(pkg, projectRoot);
6047
+ if (timberVersion && projectVersion && timberVersion !== projectVersion) mismatches.push({
6048
+ specifier: pkg,
6049
+ timberVersion,
6050
+ projectVersion
6051
+ });
6052
+ }
6053
+ return mismatches;
6054
+ }
6055
+ /**
6056
+ * Format React version mismatches as an actionable warning.
6057
+ */
6058
+ function formatReactMismatch(mismatches) {
6059
+ if (mismatches.length === 0) return "";
6060
+ const lines = [];
6061
+ lines.push(`${RED}${BOLD}[timber]${RESET} ${RED}React version mismatch detected — this causes silent hydration crashes${RESET}`);
6062
+ lines.push("");
6063
+ for (const m of mismatches) lines.push(` ${RED}✗${RESET} ${m.specifier}: timber has ${BOLD}${m.timberVersion}${RESET}, project has ${BOLD}${m.projectVersion}${RESET}`);
6064
+ lines.push("");
6065
+ lines.push(` ${DIM}This typically causes "Invalid hook call" errors because two copies of${RESET}`);
6066
+ lines.push(` ${DIM}React are loaded. timber's resolve.dedupe should handle this, but${RESET}`);
6067
+ lines.push(` ${DIM}if you see hook errors, ensure your React version satisfies timber's peer dep.${RESET}`);
6068
+ lines.push("");
6069
+ lines.push(` ${DIM}Fix: update React in your project to match —${RESET}`);
6070
+ lines.push(` ${BOLD}pnpm add react@${mismatches[0].timberVersion} react-dom@${mismatches[0].timberVersion}${RESET}`);
6071
+ return lines.join("\n");
6072
+ }
6073
+ /**
6074
+ * Create the timber-react-resolve Vite plugin.
6075
+ */
6076
+ function timberReactResolve() {
6077
+ return {
6078
+ name: "timber-react-resolve",
6079
+ enforce: "pre",
6080
+ config(userConfig) {
6081
+ const merged = [...userConfig.resolve?.dedupe ?? []];
6082
+ for (const pkg of REACT_DEDUPE_PACKAGES) if (!merged.includes(pkg)) merged.push(pkg);
6083
+ return { resolve: { dedupe: merged } };
6084
+ },
6085
+ configResolved(resolved) {
6086
+ const mismatches = detectReactMismatch(resolved.root);
6087
+ if (mismatches.length > 0) process.stderr.write(`${formatReactMismatch(mismatches)}\n\n`);
6088
+ }
6089
+ };
6090
+ }
6091
+ //#endregion
5992
6092
  //#region src/plugins/chunks.ts
5993
6093
  /**
5994
6094
  * Build a map from absolute module IDs in non-facade RSC chunks to the
@@ -9387,6 +9487,7 @@ function timber(config) {
9387
9487
  // @vitejs/plugin-rsc handles:
9388
9488
  return [
9389
9489
  rootSync,
9490
+ timberReactResolve(),
9390
9491
  timberReactProd(),
9391
9492
  ...patchReactCompilerForProdJsx(react({ compiler: effectiveReactCompiler || false })),
9392
9493
  vitePluginRsc(createRscOptions(ctx, encryptionKeyExpr)),