@timber-js/app 0.2.0-alpha.146 → 0.2.0-alpha.148

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 (297) hide show
  1. package/dist/_chunks/{actions-OIZ8siQu.js → actions-a0RczDnB.js} +8 -34
  2. package/dist/_chunks/{actions-OIZ8siQu.js.map → actions-a0RczDnB.js.map} +1 -1
  3. package/dist/_chunks/cache-api-Co-yI-gP.js +424 -0
  4. package/dist/_chunks/cache-api-Co-yI-gP.js.map +1 -0
  5. package/dist/_chunks/{metadata-routes-BjepjefT.js → canonicalize-P41GR6tY.js} +65 -2
  6. package/dist/_chunks/canonicalize-P41GR6tY.js.map +1 -0
  7. package/dist/_chunks/{cli-schema-sync-D2IpXicg.js → cli-schema-sync-DJDdyEae.js} +157 -313
  8. package/dist/_chunks/cli-schema-sync-DJDdyEae.js.map +1 -0
  9. package/dist/_chunks/cli-schema-sync-D_IhHEUW.js +2 -0
  10. package/dist/_chunks/{define-DMVhsipD.js → define-Bssfp6ot.js} +3 -33
  11. package/dist/_chunks/define-Bssfp6ot.js.map +1 -0
  12. package/dist/_chunks/{define-C3RvHQPK.js → define-CFmvb4Bt.js} +15 -7
  13. package/dist/_chunks/define-CFmvb4Bt.js.map +1 -0
  14. package/dist/_chunks/{href-validation-DyZIzfMJ.js → href-validation-FiA_rToW.js} +2 -1
  15. package/dist/_chunks/{href-validation-DyZIzfMJ.js.map → href-validation-FiA_rToW.js.map} +1 -1
  16. package/dist/_chunks/{tracing-M_zKubhk.js → logger-CM0clugc.js} +394 -107
  17. package/dist/_chunks/logger-CM0clugc.js.map +1 -0
  18. package/dist/_chunks/navigation-root-bUDZLw74.js.map +1 -1
  19. package/dist/_chunks/plugin-context-DurqFBuY.js +2 -0
  20. package/dist/_chunks/plugin-context-GfDWsdd2.js +268 -0
  21. package/dist/_chunks/plugin-context-GfDWsdd2.js.map +1 -0
  22. package/dist/_chunks/{resolve-schema-Dj8tQloi.js → resolve-schema-3iUvBV5T.js} +3 -30
  23. package/dist/_chunks/resolve-schema-3iUvBV5T.js.map +1 -0
  24. package/dist/_chunks/{schema-bridge-DK_89h8O.js → schema-bridge-BY3QLBL7.js} +35 -2
  25. package/dist/_chunks/schema-bridge-BY3QLBL7.js.map +1 -0
  26. package/dist/_chunks/tree-match-D2l830j2.js +102 -0
  27. package/dist/_chunks/tree-match-D2l830j2.js.map +1 -0
  28. package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +1 -1
  29. package/dist/_chunks/{walkers-7FBPM-hX.js → walkers-DvWDj453.js} +154 -88
  30. package/dist/_chunks/walkers-DvWDj453.js.map +1 -0
  31. package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -1
  32. package/dist/adapters/cloudflare-kv-cache.js +36 -14
  33. package/dist/adapters/cloudflare-kv-cache.js.map +1 -1
  34. package/dist/adapters/compress-module.d.ts +5 -4
  35. package/dist/adapters/compress-module.d.ts.map +1 -1
  36. package/dist/adapters/nitro.d.ts +1 -3
  37. package/dist/adapters/nitro.d.ts.map +1 -1
  38. package/dist/adapters/nitro.js +14 -134
  39. package/dist/adapters/nitro.js.map +1 -1
  40. package/dist/cache/cache-api.d.ts.map +1 -1
  41. package/dist/cache/index.d.ts +3 -1
  42. package/dist/cache/index.d.ts.map +1 -1
  43. package/dist/cache/index.js +20 -235
  44. package/dist/cache/index.js.map +1 -1
  45. package/dist/cache/invalidation-epoch.d.ts +43 -0
  46. package/dist/cache/invalidation-epoch.d.ts.map +1 -0
  47. package/dist/cache/redis-handler.d.ts +13 -0
  48. package/dist/cache/redis-handler.d.ts.map +1 -1
  49. package/dist/cache/timber-cache.d.ts +17 -9
  50. package/dist/cache/timber-cache.d.ts.map +1 -1
  51. package/dist/cli-schema-sync.d.ts +21 -1
  52. package/dist/cli-schema-sync.d.ts.map +1 -1
  53. package/dist/cli.d.ts +12 -0
  54. package/dist/cli.d.ts.map +1 -1
  55. package/dist/cli.js +26 -31
  56. package/dist/cli.js.map +1 -1
  57. package/dist/client/browser-entry/hmr.d.ts.map +1 -1
  58. package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
  59. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
  60. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  61. package/dist/client/error-boundary.d.ts.map +1 -1
  62. package/dist/client/error-boundary.js +13 -4
  63. package/dist/client/error-boundary.js.map +1 -1
  64. package/dist/client/error-reconstituter.d.ts +8 -1
  65. package/dist/client/error-reconstituter.d.ts.map +1 -1
  66. package/dist/client/history.d.ts +10 -17
  67. package/dist/client/history.d.ts.map +1 -1
  68. package/dist/client/index.js +1 -1
  69. package/dist/client/index.js.map +1 -1
  70. package/dist/client/internal.js +30 -33
  71. package/dist/client/internal.js.map +1 -1
  72. package/dist/client/navigation-api.d.ts +9 -2
  73. package/dist/client/navigation-api.d.ts.map +1 -1
  74. package/dist/client/navigation-context.d.ts +0 -11
  75. package/dist/client/navigation-context.d.ts.map +1 -1
  76. package/dist/client/navigation-root.d.ts +0 -5
  77. package/dist/client/navigation-root.d.ts.map +1 -1
  78. package/dist/client/router.d.ts.map +1 -1
  79. package/dist/client/rsc-fetch.d.ts.map +1 -1
  80. package/dist/client/use-search-params.d.ts +10 -5
  81. package/dist/client/use-search-params.d.ts.map +1 -1
  82. package/dist/codec.js +1 -1
  83. package/dist/config-validation.d.ts.map +1 -1
  84. package/dist/cookies/define-cookie.d.ts.map +1 -1
  85. package/dist/cookies/index.js +2 -4
  86. package/dist/cookies/index.js.map +1 -1
  87. package/dist/dev-tools/dev-page-shell.d.ts +1 -0
  88. package/dist/dev-tools/dev-page-shell.d.ts.map +1 -1
  89. package/dist/dev-tools/hydration-overlay.d.ts +11 -0
  90. package/dist/dev-tools/hydration-overlay.d.ts.map +1 -0
  91. package/dist/dev-tools/index.d.ts +1 -2
  92. package/dist/dev-tools/index.d.ts.map +1 -1
  93. package/dist/fonts/ast-walk.d.ts +68 -0
  94. package/dist/fonts/ast-walk.d.ts.map +1 -0
  95. package/dist/fonts/ast.d.ts +23 -11
  96. package/dist/fonts/ast.d.ts.map +1 -1
  97. package/dist/fonts/bundle.d.ts +3 -1
  98. package/dist/fonts/bundle.d.ts.map +1 -1
  99. package/dist/fonts/dev-middleware.d.ts +1 -1
  100. package/dist/fonts/dev-middleware.d.ts.map +1 -1
  101. package/dist/fonts/fallbacks.d.ts +0 -7
  102. package/dist/fonts/fallbacks.d.ts.map +1 -1
  103. package/dist/fonts/google.d.ts +4 -2
  104. package/dist/fonts/google.d.ts.map +1 -1
  105. package/dist/fonts/local.d.ts +3 -3
  106. package/dist/fonts/local.d.ts.map +1 -1
  107. package/dist/fonts/paths.d.ts +43 -0
  108. package/dist/fonts/paths.d.ts.map +1 -0
  109. package/dist/fonts/pipeline.d.ts +7 -21
  110. package/dist/fonts/pipeline.d.ts.map +1 -1
  111. package/dist/fonts/transform.d.ts.map +1 -1
  112. package/dist/fonts/virtual-modules.d.ts +1 -18
  113. package/dist/fonts/virtual-modules.d.ts.map +1 -1
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +609 -406
  116. package/dist/index.js.map +1 -1
  117. package/dist/params/index.js +1 -1
  118. package/dist/params/resolve-schema.d.ts.map +1 -1
  119. package/dist/plugin-context.d.ts +28 -5
  120. package/dist/plugin-context.d.ts.map +1 -1
  121. package/dist/plugins/cache.d.ts +13 -5
  122. package/dist/plugins/cache.d.ts.map +1 -1
  123. package/dist/plugins/dev-server.d.ts.map +1 -1
  124. package/dist/plugins/fonts.d.ts +1 -2
  125. package/dist/plugins/fonts.d.ts.map +1 -1
  126. package/dist/plugins/routing.d.ts.map +1 -1
  127. package/dist/routing/codegen-types.d.ts +4 -0
  128. package/dist/routing/codegen-types.d.ts.map +1 -1
  129. package/dist/routing/codegen.d.ts.map +1 -1
  130. package/dist/routing/convention-lint.d.ts.map +1 -1
  131. package/dist/routing/file-cache.d.ts +8 -0
  132. package/dist/routing/file-cache.d.ts.map +1 -0
  133. package/dist/routing/index.js +2 -2
  134. package/dist/routing/interception.d.ts +0 -5
  135. package/dist/routing/interception.d.ts.map +1 -1
  136. package/dist/routing/manifest-codegen.d.ts +32 -0
  137. package/dist/routing/manifest-codegen.d.ts.map +1 -0
  138. package/dist/routing/scanner.d.ts.map +1 -1
  139. package/dist/schema-bridge.d.ts +15 -0
  140. package/dist/schema-bridge.d.ts.map +1 -1
  141. package/dist/search-params/define.d.ts.map +1 -1
  142. package/dist/search-params/index.js +7 -3
  143. package/dist/search-params/index.js.map +1 -1
  144. package/dist/search-params/wrappers.d.ts.map +1 -1
  145. package/dist/segment-params/define.d.ts.map +1 -1
  146. package/dist/segment-params/index.js +1 -1
  147. package/dist/server/access-gate.d.ts.map +1 -1
  148. package/dist/server/actions.d.ts +0 -14
  149. package/dist/server/actions.d.ts.map +1 -1
  150. package/dist/server/build-manifest.d.ts +0 -29
  151. package/dist/server/build-manifest.d.ts.map +1 -1
  152. package/dist/server/canonicalize.d.ts +10 -0
  153. package/dist/server/canonicalize.d.ts.map +1 -1
  154. package/dist/server/deny-boundary.d.ts +27 -2
  155. package/dist/server/deny-boundary.d.ts.map +1 -1
  156. package/dist/server/deny-renderer.d.ts +1 -1
  157. package/dist/server/deny-renderer.d.ts.map +1 -1
  158. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  159. package/dist/server/html-injector-core.d.ts +8 -0
  160. package/dist/server/html-injector-core.d.ts.map +1 -1
  161. package/dist/server/html-injectors.d.ts.map +1 -1
  162. package/dist/server/index.js +2 -2
  163. package/dist/server/internal.d.ts +1 -5
  164. package/dist/server/internal.d.ts.map +1 -1
  165. package/dist/server/internal.js +238 -349
  166. package/dist/server/internal.js.map +1 -1
  167. package/dist/server/node-stream-transforms.d.ts +0 -11
  168. package/dist/server/node-stream-transforms.d.ts.map +1 -1
  169. package/dist/server/render-utils.d.ts +25 -0
  170. package/dist/server/render-utils.d.ts.map +1 -0
  171. package/dist/server/route-element-builder.d.ts +11 -25
  172. package/dist/server/route-element-builder.d.ts.map +1 -1
  173. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  174. package/dist/server/rsc-entry/helpers.d.ts +2 -18
  175. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  176. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  177. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  178. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  179. package/dist/server/ssr-render.d.ts.map +1 -1
  180. package/dist/server/status-code-resolver.d.ts +6 -0
  181. package/dist/server/status-code-resolver.d.ts.map +1 -1
  182. package/dist/server/utils/mdx-file.d.ts +15 -0
  183. package/dist/server/utils/mdx-file.d.ts.map +1 -0
  184. package/dist/shared/href-validation.d.ts.map +1 -1
  185. package/dist/shims/image.d.ts +15 -15
  186. package/package.json +3 -3
  187. package/src/adapters/cloudflare-kv-cache.ts +56 -19
  188. package/src/adapters/compress-module.ts +27 -118
  189. package/src/adapters/nitro.ts +3 -41
  190. package/src/adapters/raw-imports.d.ts +5 -0
  191. package/src/cache/cache-api.ts +9 -1
  192. package/src/cache/index.ts +5 -2
  193. package/src/cache/invalidation-epoch.ts +93 -0
  194. package/src/cache/redis-handler.ts +33 -2
  195. package/src/cache/timber-cache.ts +109 -53
  196. package/src/cli-schema-sync.ts +123 -30
  197. package/src/cli.ts +33 -37
  198. package/src/client/browser-entry/hmr.ts +0 -11
  199. package/src/client/browser-entry/hydrate.ts +39 -5
  200. package/src/client/browser-entry/post-hydration.ts +6 -15
  201. package/src/client/browser-entry/router-init.ts +98 -3
  202. package/src/client/error-boundary.tsx +14 -0
  203. package/src/client/error-reconstituter.tsx +15 -1
  204. package/src/client/history.ts +10 -24
  205. package/src/client/navigation-api.ts +14 -9
  206. package/src/client/navigation-context.ts +1 -21
  207. package/src/client/navigation-root.tsx +0 -8
  208. package/src/client/router.ts +18 -8
  209. package/src/client/rsc-fetch.ts +7 -2
  210. package/src/client/use-search-params.ts +10 -5
  211. package/src/config-validation.ts +33 -25
  212. package/src/cookies/define-cookie.ts +2 -18
  213. package/src/dev-tools/dev-page-shell.ts +6 -2
  214. package/src/dev-tools/hydration-overlay.ts +158 -0
  215. package/src/dev-tools/index.ts +3 -16
  216. package/src/fonts/ast-walk.ts +235 -0
  217. package/src/fonts/ast.ts +91 -249
  218. package/src/fonts/bundle.ts +16 -9
  219. package/src/fonts/dev-middleware.ts +16 -5
  220. package/src/fonts/fallbacks.ts +0 -23
  221. package/src/fonts/google.ts +7 -3
  222. package/src/fonts/local.ts +7 -6
  223. package/src/fonts/paths.ts +58 -0
  224. package/src/fonts/pipeline.ts +27 -48
  225. package/src/fonts/transform.ts +27 -0
  226. package/src/fonts/virtual-modules.ts +1 -57
  227. package/src/index.ts +5 -11
  228. package/src/params/resolve-schema.ts +2 -34
  229. package/src/plugin-context.ts +95 -17
  230. package/src/plugins/cache.ts +64 -17
  231. package/src/plugins/dev-server.ts +61 -9
  232. package/src/plugins/fonts.ts +21 -6
  233. package/src/plugins/routing.ts +5 -348
  234. package/src/routing/codegen-types.ts +4 -0
  235. package/src/routing/codegen.ts +66 -8
  236. package/src/routing/convention-lint.ts +4 -3
  237. package/src/routing/file-cache.ts +23 -0
  238. package/src/routing/interception.ts +2 -21
  239. package/src/routing/manifest-codegen.ts +290 -0
  240. package/src/routing/scanner.ts +85 -80
  241. package/src/routing/schema-validation.ts +2 -2
  242. package/src/schema-bridge.ts +63 -2
  243. package/src/search-params/define.ts +34 -2
  244. package/src/search-params/wrappers.ts +21 -3
  245. package/src/segment-params/define.ts +2 -62
  246. package/src/server/access-gate.tsx +8 -0
  247. package/src/server/actions.ts +8 -31
  248. package/src/server/build-manifest.ts +0 -56
  249. package/src/server/canonicalize.ts +6 -3
  250. package/src/server/deny-boundary.ts +106 -13
  251. package/src/server/deny-renderer.ts +46 -10
  252. package/src/server/error-boundary-wrapper.ts +1 -14
  253. package/src/server/html-injector-core.ts +19 -0
  254. package/src/server/html-injectors.ts +14 -4
  255. package/src/server/internal.ts +1 -18
  256. package/src/server/node-stream-transforms.ts +5 -72
  257. package/src/server/render-utils.ts +31 -0
  258. package/src/server/route-element-builder.ts +77 -47
  259. package/src/server/route-matcher.ts +22 -6
  260. package/src/server/rsc-entry/error-renderer.ts +2 -12
  261. package/src/server/rsc-entry/helpers.ts +6 -34
  262. package/src/server/rsc-entry/index.ts +11 -0
  263. package/src/server/rsc-entry/render-route.ts +7 -9
  264. package/src/server/rsc-entry/rsc-payload.ts +2 -1
  265. package/src/server/rsc-entry/ssr-renderer.ts +2 -2
  266. package/src/server/ssr-render.ts +1 -11
  267. package/src/server/status-code-resolver.ts +3 -2
  268. package/src/server/tree-match.ts +15 -5
  269. package/src/server/utils/mdx-file.ts +22 -0
  270. package/src/shared/href-validation.ts +8 -0
  271. package/dist/_chunks/cli-schema-sync-D2IpXicg.js.map +0 -1
  272. package/dist/_chunks/cli-schema-sync-DH-M8Poy.js +0 -2
  273. package/dist/_chunks/define-C3RvHQPK.js.map +0 -1
  274. package/dist/_chunks/define-DMVhsipD.js.map +0 -1
  275. package/dist/_chunks/handler-store-BVDgyTh7.js +0 -78
  276. package/dist/_chunks/handler-store-BVDgyTh7.js.map +0 -1
  277. package/dist/_chunks/logger-Dt-jQaBO.js +0 -291
  278. package/dist/_chunks/logger-Dt-jQaBO.js.map +0 -1
  279. package/dist/_chunks/metadata-routes-BjepjefT.js.map +0 -1
  280. package/dist/_chunks/resolve-schema-Dj8tQloi.js.map +0 -1
  281. package/dist/_chunks/schema-bridge-DK_89h8O.js.map +0 -1
  282. package/dist/_chunks/tracing-M_zKubhk.js.map +0 -1
  283. package/dist/_chunks/walkers-7FBPM-hX.js.map +0 -1
  284. package/dist/_chunks/warnings-CNPXaypM.js +0 -125
  285. package/dist/_chunks/warnings-CNPXaypM.js.map +0 -1
  286. package/dist/client/link-pending-store.d.ts +0 -65
  287. package/dist/client/link-pending-store.d.ts.map +0 -1
  288. package/dist/client/nav-link-store.d.ts +0 -27
  289. package/dist/client/nav-link-store.d.ts.map +0 -1
  290. package/dist/dev-tools/warnings.d.ts +0 -109
  291. package/dist/dev-tools/warnings.d.ts.map +0 -1
  292. package/dist/server/flush.d.ts +0 -74
  293. package/dist/server/flush.d.ts.map +0 -1
  294. package/src/client/link-pending-store.ts +0 -116
  295. package/src/client/nav-link-store.ts +0 -37
  296. package/src/dev-tools/warnings.ts +0 -238
  297. package/src/server/flush.ts +0 -183
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Hydration mismatch diff overlay — dev-only.
3
+ *
4
+ * Parses React 19's structured diff from hydration error messages and
5
+ * renders a color-coded + Client / - Server diff in a dismissible overlay.
6
+ *
7
+ * See design/21-dev-server.md §"Error Overlay"
8
+ */
9
+
10
+ const REACT_HYDRATION_LINK = 'https://react.dev/link/hydration-mismatch';
11
+
12
+ const HYDRATION_PATTERNS = [
13
+ /^In HTML, (.+?) cannot be a child of <(.+?)>\./,
14
+ /^In HTML, (.+?) cannot be a descendant of <(.+?)>\./,
15
+ /^In HTML, text nodes cannot be a child of <(.+?)>\./,
16
+ /^In HTML, whitespace text nodes cannot be a child of <(.+?)>\./,
17
+ /Hydration failed because the server rendered (text|HTML) didn't match the client\./,
18
+ /A tree hydrated but some attributes of the server rendered HTML didn't match the client properties/,
19
+ ];
20
+
21
+ export function isHydrationError(error: unknown): boolean {
22
+ if (!error || typeof error !== 'object') return false;
23
+ const msg = (error as { message?: string }).message;
24
+ if (typeof msg !== 'string') return false;
25
+ return HYDRATION_PATTERNS.some((re) => re.test(msg));
26
+ }
27
+
28
+ interface ParsedHydrationError {
29
+ message: string;
30
+ diff: string | null;
31
+ }
32
+
33
+ function parseHydrationError(msg: string): ParsedHydrationError {
34
+ const hasComponentStackDiff = [
35
+ /^In HTML, (.+?) cannot be a child of <(.+?)>\.(.*)\nThis will cause a hydration error\.(.*)/,
36
+ /^In HTML, (.+?) cannot be a descendant of <(.+?)>\.\nThis will cause a hydration error\.(.*)/,
37
+ /^In HTML, text nodes cannot be a child of <(.+?)>\.\nThis will cause a hydration error\./,
38
+ /^In HTML, whitespace text nodes cannot be a child of <(.+?)>\./,
39
+ ].some((re) => re.test(msg));
40
+
41
+ if (hasComponentStackDiff) {
42
+ const [message, diffLog = ''] = msg.split('\n\n');
43
+ const diff = diffLog.trim();
44
+ return { message: message.trim(), diff: diff || null };
45
+ }
46
+
47
+ const [before, after] = msg.split(REACT_HYDRATION_LINK);
48
+ if (after !== undefined && after.length > 1) {
49
+ const lines: string[] = [];
50
+ for (const line of after.split('\n')) {
51
+ const trimmed = line.trim();
52
+ if (trimmed === '' || trimmed.startsWith('at ')) continue;
53
+ lines.push(line);
54
+ }
55
+ return { message: before.trim(), diff: lines.join('\n') || null };
56
+ }
57
+
58
+ return { message: msg.trim(), diff: null };
59
+ }
60
+
61
+ let overlayEl: HTMLDivElement | null = null;
62
+ let errors: ParsedHydrationError[] = [];
63
+
64
+ function escapeHtml(s: string): string {
65
+ return s
66
+ .replace(/&/g, '&amp;')
67
+ .replace(/</g, '&lt;')
68
+ .replace(/>/g, '&gt;')
69
+ .replace(/"/g, '&quot;');
70
+ }
71
+
72
+ function renderDiffHtml(diff: string): string {
73
+ return diff
74
+ .split('\n')
75
+ .map((line) => {
76
+ const trimmed = line.trimStart();
77
+ if (trimmed.startsWith('+')) {
78
+ return `<span style="color:#4ade80;display:block">${escapeHtml(line)}</span>`;
79
+ }
80
+ if (trimmed.startsWith('-')) {
81
+ return `<span style="color:#f87171;display:block">${escapeHtml(line)}</span>`;
82
+ }
83
+ return `<span style="color:#a1a1aa;display:block">${escapeHtml(line)}</span>`;
84
+ })
85
+ .join('');
86
+ }
87
+
88
+ function render(): void {
89
+ if (errors.length === 0) {
90
+ if (overlayEl) {
91
+ overlayEl.remove();
92
+ overlayEl = null;
93
+ }
94
+ return;
95
+ }
96
+
97
+ if (!overlayEl) {
98
+ overlayEl = document.createElement('div');
99
+ overlayEl.setAttribute('data-timber-hydration-overlay', '');
100
+ document.body.appendChild(overlayEl);
101
+ }
102
+
103
+ const count = errors.length;
104
+ const errorsHtml = errors
105
+ .map(
106
+ (err, i) => `
107
+ <div style="margin-bottom:${i < count - 1 ? '16px' : '0'};padding-bottom:${i < count - 1 ? '16px' : '0'};border-bottom:${i < count - 1 ? '1px solid #333' : 'none'}">
108
+ <div style="color:#fbbf24;font-weight:600;margin-bottom:8px">${escapeHtml(err.message)}</div>
109
+ ${
110
+ err.diff
111
+ ? `<div style="margin-top:8px">
112
+ <div style="display:flex;gap:16px;margin-bottom:6px;font-size:11px">
113
+ <span><span style="color:#4ade80">+</span> Client</span>
114
+ <span><span style="color:#f87171">-</span> Server</span>
115
+ </div>
116
+ <pre style="margin:0;padding:10px;background:#1a1a1a;border-radius:4px;overflow-x:auto;font-size:12px;line-height:1.5">${renderDiffHtml(err.diff)}</pre>
117
+ </div>`
118
+ : ''
119
+ }
120
+ </div>`
121
+ )
122
+ .join('');
123
+
124
+ overlayEl.innerHTML = `
125
+ <div style="position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:2147483646;display:flex;align-items:center;justify-content:center;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif" data-timber-hydration-backdrop>
126
+ <div style="background:#18181b;color:#e4e4e7;border:1px solid #333;border-radius:8px;max-width:720px;width:90vw;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 8px 32px rgba(0,0,0,0.5)">
127
+ <div style="display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #333">
128
+ <div style="display:flex;align-items:center;gap:8px">
129
+ <span style="background:#dc2626;color:#fff;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:700;text-transform:uppercase">Hydration Mismatch</span>
130
+ ${count > 1 ? `<span style="color:#71717a;font-size:12px">${count} errors</span>` : ''}
131
+ </div>
132
+ <button data-timber-hydration-close style="background:none;border:1px solid #333;color:#a1a1aa;cursor:pointer;padding:4px 10px;border-radius:4px;font-size:12px;line-height:1">Dismiss</button>
133
+ </div>
134
+ <div style="padding:16px;overflow-y:auto;font-size:13px;line-height:1.6">
135
+ ${errorsHtml}
136
+ </div>
137
+ </div>
138
+ </div>`;
139
+
140
+ overlayEl.querySelector('[data-timber-hydration-close]')?.addEventListener('click', dismiss);
141
+ overlayEl.querySelector('[data-timber-hydration-backdrop]')?.addEventListener('click', (e) => {
142
+ if ((e.target as HTMLElement).hasAttribute('data-timber-hydration-backdrop')) dismiss();
143
+ });
144
+ }
145
+
146
+ function dismiss(): void {
147
+ errors = [];
148
+ render();
149
+ }
150
+
151
+ export function showHydrationError(error: unknown): void {
152
+ if (!error || typeof error !== 'object') return;
153
+ const msg = (error as { message?: string }).message;
154
+ if (typeof msg !== 'string') return;
155
+ const parsed = parseHydrationError(msg);
156
+ errors.push(parsed);
157
+ render();
158
+ }
@@ -8,7 +8,6 @@
8
8
  * - 404-page: self-contained HTML 404 page
9
9
  * - terminal: boxed terminal error output
10
10
  * - logger: structured request tree logger
11
- * - warnings: dev-mode footgun warnings
12
11
  * - browser-logs: forward browser console to server
13
12
  * - logs: forward server console to browser
14
13
  * - holding-server: startup holding server
@@ -64,21 +63,6 @@ export {
64
63
  type DevLoggerConfig,
65
64
  } from './logger.js';
66
65
 
67
- // Dev-mode warnings
68
- export {
69
- warnSuspenseWrappingChildren,
70
- warnDenyInSuspense,
71
- warnRedirectInSuspense,
72
- warnRedirectInAccess,
73
- warnStaticRequestApi,
74
- warnSlowSlotWithoutSuspense,
75
- setViteServer,
76
- WarningId,
77
- type DevWarningConfig,
78
- _resetWarnings,
79
- _getEmitted,
80
- } from './warnings.js';
81
-
82
66
  // Browser log forwarding
83
67
  export {
84
68
  timberDevBrowserLogs,
@@ -94,3 +78,6 @@ export { createHoldingServer, type HoldingServer } from './holding-server.js';
94
78
 
95
79
  // OTEL instrumentation
96
80
  export { instrumentDevFetch, DevSpanProcessor, type DevFetchCleanup } from './instrumentation.js';
81
+
82
+ // Hydration mismatch overlay (client-side, dev-only)
83
+ export { isHydrationError, showHydrationError } from './hydration-overlay.js';
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Generic AST node types and recursive walkers for font static analysis.
3
+ *
4
+ * `ast.ts` owns parsing, config extraction, and call-site discovery; this
5
+ * module owns the recursive walks that police where font bindings may be
6
+ * used (dynamic configs, nested scopes, unconsumed references). Split out
7
+ * to keep `ast.ts` under the 500-code-line limit (design/18-build-system.md).
8
+ *
9
+ * Design doc: 24-fonts.md §"Step 1: Static Analysis"
10
+ */
11
+
12
+ // ── AST node types (minimal subset of estree) ────────────────────────────────
13
+
14
+ export interface AstNode {
15
+ type: string;
16
+ [key: string]: unknown;
17
+ }
18
+
19
+ export interface IdentifierNode extends AstNode {
20
+ type: 'Identifier';
21
+ name: string;
22
+ }
23
+
24
+ export interface PositionedNode extends AstNode {
25
+ start: number;
26
+ end: number;
27
+ }
28
+
29
+ /** A flagged reference/usage for build-error reporting. */
30
+ export interface FlaggedUsage {
31
+ name: string;
32
+ line: number;
33
+ column: number;
34
+ }
35
+
36
+ // ── Offset → line/column helper ──────────────────────────────────────────────
37
+
38
+ /** Convert a 0-based byte offset into a 1-based line and 0-based column. */
39
+ export function offsetToLineColumn(
40
+ source: string,
41
+ offset: number
42
+ ): { line: number; column: number } {
43
+ let line = 1;
44
+ let lastNewline = -1;
45
+ for (let i = 0; i < offset; i++) {
46
+ if (source[i] === '\n') {
47
+ line++;
48
+ lastNewline = i;
49
+ }
50
+ }
51
+ return { line, column: offset - lastNewline - 1 };
52
+ }
53
+
54
+ // ── Shared recursion plumbing ────────────────────────────────────────────────
55
+
56
+ /**
57
+ * Invoke `visit` on every direct child AST node of `node` (array elements
58
+ * and object-valued properties with a `type` field).
59
+ */
60
+ export function walkChildNodes(node: AstNode, visit: (child: AstNode) => void): void {
61
+ for (const key of Object.keys(node)) {
62
+ if (key === 'type') continue;
63
+ const child = node[key];
64
+ if (Array.isArray(child)) {
65
+ for (const item of child) {
66
+ if (item && typeof item === 'object' && (item as AstNode).type) {
67
+ visit(item as AstNode);
68
+ }
69
+ }
70
+ } else if (child && typeof child === 'object' && (child as AstNode).type) {
71
+ visit(child as AstNode);
72
+ }
73
+ }
74
+ }
75
+
76
+ // ── Walkers ──────────────────────────────────────────────────────────────────
77
+
78
+ /**
79
+ * Recursively walk the AST looking for CallExpression nodes where
80
+ * the callee is one of the imported font names and the first argument
81
+ * is not an ObjectExpression (i.e. it's dynamic).
82
+ */
83
+ export function walkForDynamicCalls(
84
+ node: AstNode,
85
+ names: ReadonlySet<string>,
86
+ source: string
87
+ ): string | null {
88
+ if (!node || typeof node !== 'object') return null;
89
+
90
+ if (node.type === 'CallExpression') {
91
+ const callee = node.callee as AstNode;
92
+ if (callee.type === 'Identifier' && names.has((callee as IdentifierNode).name)) {
93
+ const args = node.arguments as AstNode[];
94
+ if (args.length > 0 && args[0].type !== 'ObjectExpression') {
95
+ // Extract the argument source text
96
+ const argStart = (args[0] as PositionedNode).start;
97
+ const argEnd = (args[0] as PositionedNode).end;
98
+ const argText = source.slice(argStart, argEnd);
99
+ const calleeName = (callee as IdentifierNode).name;
100
+ return `${calleeName}(${argText})`;
101
+ }
102
+ }
103
+ }
104
+
105
+ let result: string | null = null;
106
+ walkChildNodes(node, (child) => {
107
+ if (!result) result = walkForDynamicCalls(child, names, source);
108
+ });
109
+ return result;
110
+ }
111
+
112
+ /**
113
+ * Node types that introduce a nested scope — any font call inside is an
114
+ * error (font calls must be module-level, same constraint as Next.js).
115
+ * Module scope so it isn't rebuilt on every recursive walk call.
116
+ */
117
+ const NESTED_SCOPE_TYPES = new Set([
118
+ 'FunctionDeclaration',
119
+ 'FunctionExpression',
120
+ 'ArrowFunctionExpression',
121
+ 'IfStatement',
122
+ 'ForStatement',
123
+ 'ForInStatement',
124
+ 'ForOfStatement',
125
+ 'WhileStatement',
126
+ 'DoWhileStatement',
127
+ 'SwitchStatement',
128
+ 'TryStatement',
129
+ 'BlockStatement',
130
+ 'ClassDeclaration',
131
+ 'ClassExpression',
132
+ ]);
133
+
134
+ /**
135
+ * Recursively walk an AST node looking for font CallExpressions in nested
136
+ * scopes (function bodies, if-blocks, loops, etc.). Font calls must be
137
+ * module-level; nested calls are collected as errors.
138
+ *
139
+ * `isNested` tracks whether we've descended past the top-level statement.
140
+ * Top-level VariableDeclarations and ExpressionStatements are already
141
+ * handled by the main loop in `findFontCallSites`, so we only flag calls
142
+ * found deeper.
143
+ */
144
+ export function walkForNestedFontCalls(
145
+ node: AstNode,
146
+ bindingNames: ReadonlyMap<string, unknown>,
147
+ source: string,
148
+ out: FlaggedUsage[],
149
+ isNested: boolean
150
+ ): void {
151
+ if (!node || typeof node !== 'object') return;
152
+
153
+ if (node.type === 'CallExpression') {
154
+ const callee = node.callee as AstNode;
155
+ if (
156
+ isNested &&
157
+ callee.type === 'Identifier' &&
158
+ bindingNames.has((callee as IdentifierNode).name)
159
+ ) {
160
+ const pos = offsetToLineColumn(source, (node as PositionedNode).start);
161
+ out.push({ name: (callee as IdentifierNode).name, line: pos.line, column: pos.column });
162
+ return; // Don't recurse into this call's children
163
+ }
164
+ }
165
+
166
+ const enterNested = isNested || NESTED_SCOPE_TYPES.has(node.type);
167
+ walkChildNodes(node, (child) =>
168
+ walkForNestedFontCalls(child, bindingNames, source, out, enterNested)
169
+ );
170
+ }
171
+
172
+ /**
173
+ * Recursively walk an AST node looking for references to font bindings
174
+ * that were not consumed by a recognized call site.
175
+ *
176
+ * Skips:
177
+ * - recognized CallExpressions (the transform rewrites them wholesale)
178
+ * - TS type-position nodes (erased at runtime)
179
+ * - non-computed member properties (`obj.Inter` doesn't reference the binding)
180
+ * - non-computed object keys (`{ Inter: 1 }` doesn't reference the binding)
181
+ * - export aliases (`export { inter as Inter }` — only `local` is a reference)
182
+ */
183
+ export function walkForUnconsumedReferences(
184
+ node: AstNode,
185
+ bindingNames: ReadonlyMap<string, unknown>,
186
+ consumedCallStarts: ReadonlySet<number>,
187
+ source: string,
188
+ out: FlaggedUsage[]
189
+ ): void {
190
+ if (!node || typeof node !== 'object') return;
191
+ if (node.type === 'ImportDeclaration' || node.type.startsWith('TS')) return;
192
+
193
+ if (node.type === 'Identifier') {
194
+ const name = (node as IdentifierNode).name;
195
+ if (bindingNames.has(name)) {
196
+ const pos = offsetToLineColumn(source, (node as PositionedNode).start);
197
+ out.push({ name, line: pos.line, column: pos.column });
198
+ }
199
+ return;
200
+ }
201
+
202
+ if (node.type === 'CallExpression' && consumedCallStarts.has((node as PositionedNode).start)) {
203
+ return; // The whole call (callee + config object) is rewritten.
204
+ }
205
+
206
+ if (node.type === 'MemberExpression' && !(node as { computed?: boolean }).computed) {
207
+ const object = (node as { object?: AstNode }).object;
208
+ if (object) {
209
+ walkForUnconsumedReferences(object, bindingNames, consumedCallStarts, source, out);
210
+ }
211
+ return;
212
+ }
213
+
214
+ if (node.type === 'Property' && !(node as { computed?: boolean }).computed) {
215
+ const value = (node as { value?: AstNode }).value;
216
+ if (value) {
217
+ walkForUnconsumedReferences(value, bindingNames, consumedCallStarts, source, out);
218
+ }
219
+ return;
220
+ }
221
+
222
+ if (node.type === 'ExportSpecifier') {
223
+ // Only `local` references a module binding. `exported` is just the
224
+ // public name — `export { inter as Inter }` must not flag `Inter`.
225
+ const local = (node as { local?: AstNode }).local;
226
+ if (local) {
227
+ walkForUnconsumedReferences(local, bindingNames, consumedCallStarts, source, out);
228
+ }
229
+ return;
230
+ }
231
+
232
+ walkChildNodes(node, (child) =>
233
+ walkForUnconsumedReferences(child, bindingNames, consumedCallStarts, source, out)
234
+ );
235
+ }