toiljs 0.0.15 → 0.0.16

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 (217) hide show
  1. package/.babelrc +13 -13
  2. package/.gitattributes +2 -2
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
  4. package/.github/ISSUE_TEMPLATE/bug_report.yml +90 -90
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -8
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  7. package/.github/PULL_REQUEST_TEMPLATE.md +43 -43
  8. package/.github/changelog-config.json +45 -45
  9. package/.github/dependabot.yml +27 -27
  10. package/.github/workflows/ci.yml +191 -191
  11. package/.prettierrc.json +11 -11
  12. package/.vscode/settings.json +9 -9
  13. package/CHANGELOG.md +5 -5
  14. package/LICENSE +187 -187
  15. package/README.md +339 -315
  16. package/as-pect.asconfig.json +34 -34
  17. package/as-pect.config.js +65 -65
  18. package/assets/logo.svg +36 -36
  19. package/build/backend/.tsbuildinfo +1 -1
  20. package/build/cli/.tsbuildinfo +1 -1
  21. package/build/cli/index.js +0 -0
  22. package/build/client/.tsbuildinfo +1 -1
  23. package/build/client/dev/devtools.d.ts +6 -0
  24. package/build/client/dev/devtools.js +442 -0
  25. package/build/client/dev/error-overlay.d.ts +9 -0
  26. package/build/client/dev/error-overlay.js +19 -4
  27. package/build/client/navigation/prefetch.d.ts +1 -0
  28. package/build/client/navigation/prefetch.js +35 -0
  29. package/build/client/routing/Router.js +1 -1
  30. package/build/client/routing/hooks.js +6 -2
  31. package/build/client/routing/loader.d.ts +23 -0
  32. package/build/client/routing/loader.js +53 -7
  33. package/build/client/routing/mount.js +4 -3
  34. package/build/compiler/.tsbuildinfo +1 -1
  35. package/build/compiler/config.d.ts +16 -0
  36. package/build/compiler/config.js +7 -0
  37. package/build/compiler/docs.js +16 -16
  38. package/build/compiler/index.d.ts +2 -2
  39. package/build/compiler/index.js +1 -1
  40. package/build/compiler/plugin.js +156 -0
  41. package/build/compiler/prerender.d.ts +1 -0
  42. package/build/compiler/prerender.js +1 -1
  43. package/build/compiler/seo.d.ts +1 -1
  44. package/build/compiler/seo.js +5 -4
  45. package/build/compiler/ssg.js +32 -1
  46. package/build/io/.tsbuildinfo +1 -1
  47. package/build/logger/.tsbuildinfo +1 -1
  48. package/build/shared/.tsbuildinfo +1 -1
  49. package/eslint.config.js +48 -48
  50. package/examples/basic/client/404.tsx +11 -11
  51. package/examples/basic/client/components/.gitkeep +1 -1
  52. package/examples/basic/client/global-error.tsx +13 -13
  53. package/examples/basic/client/layout.tsx +25 -25
  54. package/examples/basic/client/public/images/.gitkeep +1 -1
  55. package/examples/basic/client/public/images/logo.svg +36 -36
  56. package/examples/basic/client/public/robots.txt +2 -2
  57. package/examples/basic/client/routes/docs/[...slug].tsx +12 -12
  58. package/examples/basic/client/routes/features/error/error.tsx +16 -16
  59. package/examples/basic/client/routes/features/template/b.tsx +14 -14
  60. package/examples/basic/client/routes/files/[[...slug]].tsx +21 -21
  61. package/examples/basic/client/routes/gallery/layout.tsx +13 -13
  62. package/examples/basic/client/routes/io.tsx +24 -24
  63. package/examples/basic/client/routes/loader-demo/loading.tsx +13 -13
  64. package/examples/basic/client/routes/search.tsx +61 -61
  65. package/examples/basic/client/toil.tsx +5 -5
  66. package/package.json +155 -148
  67. package/presets/eslint.js +88 -88
  68. package/presets/no-uint8array-tostring.js +200 -200
  69. package/presets/prettier.json +18 -18
  70. package/presets/tsconfig.json +37 -37
  71. package/src/backend/index.ts +160 -160
  72. package/src/cli/proc.ts +50 -50
  73. package/src/cli/updates.ts +69 -69
  74. package/src/cli/validate.ts +31 -31
  75. package/src/client/channel/channel.ts +146 -146
  76. package/src/client/components/Form.tsx +65 -65
  77. package/src/client/components/Script.tsx +113 -113
  78. package/src/client/components/Slot.tsx +21 -21
  79. package/src/client/dev/devtools.tsx +973 -0
  80. package/src/client/dev/error-overlay.tsx +30 -4
  81. package/src/client/head/head.ts +167 -167
  82. package/src/client/head/metadata.ts +112 -112
  83. package/src/client/index.ts +89 -89
  84. package/src/client/navigation/NavLink.tsx +86 -86
  85. package/src/client/navigation/navigation.ts +235 -235
  86. package/src/client/navigation/prefetch.ts +169 -130
  87. package/src/client/navigation/scroll.ts +53 -53
  88. package/src/client/routing/Router.tsx +8 -2
  89. package/src/client/routing/action.ts +122 -122
  90. package/src/client/routing/error-boundary.tsx +43 -43
  91. package/src/client/routing/hooks.ts +21 -6
  92. package/src/client/routing/loader.ts +325 -235
  93. package/src/client/routing/match.ts +47 -47
  94. package/src/client/routing/mount.tsx +54 -52
  95. package/src/client/routing/params-context.ts +10 -10
  96. package/src/client/routing/slot-context.ts +7 -7
  97. package/src/client/search/search.ts +189 -189
  98. package/src/client/search/use-page-search.ts +73 -73
  99. package/src/client/types.ts +73 -73
  100. package/src/compiler/config.ts +219 -182
  101. package/src/compiler/docs.ts +228 -228
  102. package/src/compiler/generate.ts +394 -394
  103. package/src/compiler/index.ts +64 -57
  104. package/src/compiler/pages.ts +70 -70
  105. package/src/compiler/plugin.ts +170 -2
  106. package/src/compiler/prerender.ts +156 -156
  107. package/src/compiler/seo.ts +397 -390
  108. package/src/compiler/ssg.ts +162 -126
  109. package/src/io/BinaryReader.ts +340 -340
  110. package/src/io/BinaryWriter.ts +385 -385
  111. package/src/io/FastMap.ts +127 -127
  112. package/src/io/index.ts +11 -11
  113. package/src/io/lengths.ts +14 -14
  114. package/src/io/types.ts +18 -18
  115. package/src/logger/index.ts +22 -22
  116. package/src/server/index.ts +10 -10
  117. package/src/server/main.ts +13 -13
  118. package/src/server/tsconfig.json +4 -4
  119. package/src/shared/index.ts +10 -10
  120. package/std/client/index.d.ts +15 -15
  121. package/std/client/package.json +3 -3
  122. package/test/assembly/example.spec.ts +7 -7
  123. package/test/channel.test.ts +21 -21
  124. package/test/dom/Link.test.tsx +47 -47
  125. package/test/dom/NavLink.test.tsx +37 -37
  126. package/test/dom/error-overlay.test.tsx +44 -44
  127. package/test/dom/loader.test.tsx +121 -121
  128. package/test/dom/navigation.test.ts +59 -59
  129. package/test/dom/revalidate.test.tsx +38 -38
  130. package/test/dom/route-head.test.tsx +78 -78
  131. package/test/dom/router-loading.test.tsx +44 -44
  132. package/test/dom/scroll.test.ts +56 -56
  133. package/test/dom/use-metadata.test.tsx +58 -58
  134. package/test/io.test.ts +93 -93
  135. package/test/navlink.test.ts +28 -28
  136. package/test/placeholder.test.ts +9 -9
  137. package/test/routes.test.ts +76 -76
  138. package/test/seo.test.ts +175 -164
  139. package/test/slot-layouts.test.ts +69 -69
  140. package/test/ssg.test.ts +36 -36
  141. package/test/update.test.ts +44 -44
  142. package/test/validate.test.ts +42 -42
  143. package/toil-routes.d.ts +7 -0
  144. package/toilconfig.json +30 -30
  145. package/tsconfig.backend.json +13 -13
  146. package/tsconfig.base.json +35 -35
  147. package/tsconfig.cli.json +13 -13
  148. package/tsconfig.client.json +14 -14
  149. package/tsconfig.compiler.json +13 -13
  150. package/tsconfig.io.json +12 -12
  151. package/tsconfig.json +22 -22
  152. package/tsconfig.logger.json +12 -12
  153. package/tsconfig.server.json +10 -10
  154. package/tsconfig.shared.json +12 -12
  155. package/vitest.config.ts +26 -26
  156. package/.idea/codeStyles/Project.xml +0 -54
  157. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  158. package/.idea/inspectionProfiles/Project_Default.xml +0 -6
  159. package/.idea/modules.xml +0 -8
  160. package/.idea/prettier.xml +0 -7
  161. package/.idea/toiljs.iml +0 -8
  162. package/.idea/vcs.xml +0 -6
  163. package/.toil/entry.tsx +0 -9
  164. package/.toil/index.html +0 -12
  165. package/.toil/routes.ts +0 -9
  166. package/build/cli/configure.d.ts +0 -16
  167. package/build/cli/configure.js +0 -272
  168. package/build/cli/create.d.ts +0 -16
  169. package/build/cli/create.js +0 -420
  170. package/build/cli/diagnostics.d.ts +0 -55
  171. package/build/cli/diagnostics.js +0 -333
  172. package/build/cli/doctor.d.ts +0 -6
  173. package/build/cli/doctor.js +0 -249
  174. package/build/cli/features.d.ts +0 -25
  175. package/build/cli/features.js +0 -107
  176. package/build/cli/index.d.ts +0 -2
  177. package/build/cli/proc.d.ts +0 -6
  178. package/build/cli/proc.js +0 -31
  179. package/build/cli/ui.d.ts +0 -9
  180. package/build/cli/ui.js +0 -75
  181. package/build/cli/update.d.ts +0 -7
  182. package/build/cli/update.js +0 -117
  183. package/build/cli/updates.d.ts +0 -10
  184. package/build/cli/updates.js +0 -45
  185. package/build/cli/validate.d.ts +0 -4
  186. package/build/cli/validate.js +0 -19
  187. package/build/client/Link.d.ts +0 -8
  188. package/build/client/Link.js +0 -44
  189. package/build/client/NavLink.d.ts +0 -14
  190. package/build/client/NavLink.js +0 -37
  191. package/build/client/Router.d.ts +0 -7
  192. package/build/client/Router.js +0 -55
  193. package/build/client/channel.d.ts +0 -23
  194. package/build/client/channel.js +0 -94
  195. package/build/client/error-boundary.d.ts +0 -16
  196. package/build/client/error-boundary.js +0 -19
  197. package/build/client/head.d.ts +0 -26
  198. package/build/client/head.js +0 -87
  199. package/build/client/hooks.d.ts +0 -17
  200. package/build/client/hooks.js +0 -48
  201. package/build/client/lazy.d.ts +0 -16
  202. package/build/client/lazy.js +0 -53
  203. package/build/client/match.d.ts +0 -2
  204. package/build/client/match.js +0 -32
  205. package/build/client/mount.d.ts +0 -2
  206. package/build/client/mount.js +0 -13
  207. package/build/client/navigation.d.ts +0 -13
  208. package/build/client/navigation.js +0 -97
  209. package/build/client/params-context.d.ts +0 -2
  210. package/build/client/params-context.js +0 -2
  211. package/build/client/prefetch.d.ts +0 -11
  212. package/build/client/prefetch.js +0 -100
  213. package/build/client/runtime.d.ts +0 -31
  214. package/build/client/runtime.js +0 -112
  215. package/build/client/scroll.d.ts +0 -8
  216. package/build/client/scroll.js +0 -36
  217. package/toil-env.d.ts +0 -16
@@ -0,0 +1,442 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState, useSyncExternalStore } from 'react';
3
+ import { getErrorLog, subscribeErrors } from './error-overlay.js';
4
+ import { isNavigationPending, navigate, setTransitions, setViewTransitions, subscribeLocation, subscribePending, } from '../navigation/navigation.js';
5
+ import { clearLoaderData, inspectLoaderCache, loaderKey, revalidate, subscribeLoaderCache, } from '../routing/loader.js';
6
+ import { matchRoute } from '../routing/match.js';
7
+ import { getPages } from '../search/search.js';
8
+ const DOCS_BASE = 'https://toil.org/docs';
9
+ function ToilLogo({ size = 16 }) {
10
+ return (_jsxs("svg", { width: size, height: size, viewBox: "0 0 500 500", "aria-hidden": "true", style: { display: 'block', flex: '0 0 auto' }, children: [_jsxs("defs", { children: [_jsxs("linearGradient", { id: "toilDtA", x1: "43.27", y1: "43.27", x2: "467.12", y2: "467.12", gradientUnits: "userSpaceOnUse", children: [_jsx("stop", { offset: "0", stopColor: "#6990ff" }), _jsx("stop", { offset: ".28", stopColor: "#521be0" }), _jsx("stop", { offset: ".66", stopColor: "#6900f4" }), _jsx("stop", { offset: "1", stopColor: "#7f00f6" })] }), _jsxs("linearGradient", { id: "toilDtB", x1: "149.99", y1: "355.49", x2: "149.99", y2: "0", gradientUnits: "userSpaceOnUse", children: [_jsx("stop", { offset: ".15", stopColor: "#6990ff", stopOpacity: ".6" }), _jsx("stop", { offset: ".55", stopColor: "#531ae1" })] })] }), _jsx("rect", { width: "500", height: "500", rx: "130", ry: "130", fill: "url(#toilDtA)" }), _jsx("path", { d: "M299.98,0L0,355.49v-225.49C0,58.2,58.2,0,130,0h169.98Z", fill: "url(#toilDtB)" }), _jsx("path", { d: "M106.17,111.11h285.24c9.9,0,16.7,9.96,13.09,19.18l-17.98,45.96c-2.11,5.39-7.31,8.94-13.09,8.94h-74.65c-7.76,0-14.06,6.29-14.06,14.06v214.94c0,7.76-6.29,14.06-14.06,14.06h-45.96c-7.76,0-14.06-6.29-14.06-14.06v-217.25c0-7.76-6.29-14.06-14.06-14.06h-73.66c-5.82,0-11.04-3.59-13.12-9.02l-16.76-43.64c-3.54-9.21,3.26-19.1,13.12-19.1Z", fill: "#fff" })] }));
11
+ }
12
+ function ClaudeLogo({ size = 16 }) {
13
+ return (_jsx("svg", { width: size, height: size, viewBox: "0 0 92 65", "aria-hidden": "true", style: { display: 'block', flex: '0 0 auto' }, children: _jsx("path", { fill: "#d97757", d: "M66.5 0H52.4L78 65h14.1L66.5 0zM25.6 0L0 65h14.4l5.2-13.6h26.8L51.6 65H66L40.4 0H25.6zm-1.2 39.3l8.8-22.8 8.8 22.8H24.4z" }) }));
14
+ }
15
+ function ChatGptLogo({ size = 16 }) {
16
+ return (_jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true", style: { display: 'block', flex: '0 0 auto' }, children: _jsx("path", { fill: "#10a37f", d: "M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997z" }) }));
17
+ }
18
+ const PREFS_KEY = 'toil.devtools';
19
+ const defaultPrefs = { open: false, tab: 'route', side: 'left' };
20
+ function loadPrefs() {
21
+ try {
22
+ const raw = localStorage.getItem(PREFS_KEY);
23
+ return raw ? { ...defaultPrefs, ...JSON.parse(raw) } : defaultPrefs;
24
+ }
25
+ catch {
26
+ return defaultPrefs;
27
+ }
28
+ }
29
+ let prefs = typeof localStorage !== 'undefined' ? loadPrefs() : defaultPrefs;
30
+ const prefListeners = new Set();
31
+ function setPrefs(next) {
32
+ prefs = { ...prefs, ...next };
33
+ try {
34
+ localStorage.setItem(PREFS_KEY, JSON.stringify(prefs));
35
+ }
36
+ catch {
37
+ }
38
+ for (const l of prefListeners)
39
+ l();
40
+ }
41
+ function usePrefs() {
42
+ return useSyncExternalStore((l) => {
43
+ prefListeners.add(l);
44
+ return () => prefListeners.delete(l);
45
+ }, () => prefs, () => defaultPrefs);
46
+ }
47
+ function useCurrentUrl() {
48
+ return useSyncExternalStore(subscribeLocation, () => window.location.pathname + window.location.search, () => '/');
49
+ }
50
+ function usePending() {
51
+ return useSyncExternalStore(subscribePending, isNavigationPending, () => false);
52
+ }
53
+ function useErrors() {
54
+ return useSyncExternalStore(subscribeErrors, getErrorLog, () => getErrorLog());
55
+ }
56
+ function useLoaderCache() {
57
+ return useSyncExternalStore(subscribeLoaderCache, inspectLoaderCache, inspectLoaderCache);
58
+ }
59
+ function safeJson(value) {
60
+ try {
61
+ return JSON.stringify(value, null, 2) ?? String(value);
62
+ }
63
+ catch {
64
+ return String(value);
65
+ }
66
+ }
67
+ function readHead() {
68
+ const metas = [];
69
+ const links = [];
70
+ if (typeof document === 'undefined')
71
+ return { metas, links };
72
+ document.head.querySelectorAll('meta').forEach((m) => {
73
+ const name = m.getAttribute('name') ?? m.getAttribute('property');
74
+ const content = m.getAttribute('content');
75
+ if (name && content)
76
+ metas.push({ name, content });
77
+ });
78
+ document.head.querySelectorAll('link[rel]').forEach((l) => {
79
+ links.push({ rel: l.getAttribute('rel') ?? '', href: l.getAttribute('href') ?? '' });
80
+ });
81
+ return { metas, links };
82
+ }
83
+ const STYLE_ID = 'toil-devtools-style';
84
+ const CSS = `
85
+ .toil-dt{position:fixed;bottom:12px;z-index:2147483646;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#e7e9f0}
86
+ .toil-dt.left{left:12px}.toil-dt.right{right:12px}
87
+ .toil-dt-badge{display:flex;align-items:center;gap:7px;background:#15151c;border:1px solid #2c2c38;border-radius:999px;padding:5px 11px 5px 8px;cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,.35);user-select:none}
88
+ .toil-dt-badge:hover{border-color:#3a3a48}
89
+ .toil-dt-dot{width:8px;height:8px;border-radius:50%;background:#22e3ab;box-shadow:0 0 6px #22e3ab}
90
+ .toil-dt-dot.pending{background:#f7b93e;box-shadow:0 0 6px #f7b93e;animation:toil-dt-pulse 1s infinite}
91
+ .toil-dt-dot.error{background:#ef4444;box-shadow:0 0 6px #ef4444}
92
+ @keyframes toil-dt-pulse{0%,100%{opacity:1}50%{opacity:.4}}
93
+ .toil-dt-logo{font-weight:700;background:linear-gradient(90deg,#2563ff,#7c3aed,#22e3ab);-webkit-background-clip:text;background-clip:text;color:transparent}
94
+ .toil-dt-panel{width:380px;max-width:calc(100vw - 24px);max-height:min(70vh,560px);background:#101016;border:1px solid #2c2c38;border-radius:12px;box-shadow:0 16px 56px rgba(0,0,0,.55);display:flex;flex-direction:column;overflow:hidden}
95
+ .toil-dt-tabs{display:flex;border-bottom:1px solid #23232e;flex:0 0 auto}
96
+ .toil-dt-tab{flex:1;padding:8px 4px;background:none;border:0;color:#8b90a4;font:inherit;cursor:pointer;border-bottom:2px solid transparent}
97
+ .toil-dt-tab.active{color:#e7e9f0;border-bottom-color:#2563ff}
98
+ .toil-dt-tab:hover{color:#c8cee0}
99
+ .toil-dt-body{padding:12px 14px;overflow:auto}
100
+ .toil-dt-head{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid #23232e;flex:0 0 auto}
101
+ .toil-dt-x{background:none;border:0;color:#8b90a4;cursor:pointer;font:inherit;font-size:14px}
102
+ .toil-dt-row{display:flex;justify-content:space-between;gap:10px;padding:3px 0;border-bottom:1px solid #1b1b24}
103
+ .toil-dt-k{color:#8b90a4}
104
+ .toil-dt-v{color:#e7e9f0;text-align:right;word-break:break-all}
105
+ .toil-dt-tag{display:inline-block;padding:1px 6px;border-radius:5px;background:#23232e;color:#a8b0c8;margin:1px 3px 1px 0;font-size:11px}
106
+ .toil-dt-rt{display:flex;align-items:center;gap:6px;padding:3px 0}
107
+ .toil-dt-rt a{color:#7aa2ff;text-decoration:none;cursor:pointer}.toil-dt-rt a:hover{text-decoration:underline}
108
+ .toil-dt-rt .dyn{color:#c8cee0;cursor:default}
109
+ .toil-dt-edit{margin-left:auto;background:none;border:0;color:#5b6178;cursor:pointer;font:inherit}.toil-dt-edit:hover{color:#7aa2ff}
110
+ .toil-dt-sec{margin:0 0 6px;color:#6b7088;text-transform:uppercase;letter-spacing:.05em;font-size:10px}
111
+ .toil-dt-sw{display:flex;align-items:center;justify-content:space-between;padding:5px 0}
112
+ .toil-dt-btn{font:inherit;color:#e7e9f0;background:#23232e;border:1px solid #33333f;border-radius:6px;padding:3px 9px;cursor:pointer}
113
+ .toil-dt-btn:hover{border-color:#454556}
114
+ .toil-dt-err{padding:6px 0;border-bottom:1px solid #1b1b24}
115
+ .toil-dt-err .msg{color:#ff8a8a;word-break:break-word}
116
+ .toil-dt-empty{color:#6b7088;padding:8px 0}
117
+ .toil-dt-pre{background:#0a0a0e;border:1px solid #1b1b24;border-radius:6px;padding:8px;max-height:200px;overflow:auto;white-space:pre-wrap;word-break:break-word;color:#c8cee0;margin:6px 0 0;font-size:11px}
118
+ .toil-dt-chk{display:flex;gap:8px;align-items:center;padding:3px 0}
119
+ .toil-dt-ok{color:#22e3ab}.toil-dt-bad{color:#ef4444}
120
+ .toil-dt-og{display:flex;gap:8px;border:1px solid #23232e;border-radius:8px;overflow:hidden;background:#0d0d13}
121
+ .toil-dt-og-img{width:72px;height:72px;object-fit:cover;flex:0 0 auto}
122
+ .toil-dt-og-body{padding:6px 8px;min-width:0}
123
+ .toil-dt-og-title{color:#e7e9f0;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
124
+ .toil-dt-og-desc{color:#8b90a4;font-size:11px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
125
+ .toil-dt-ta{width:100%;box-sizing:border-box;background:#0a0a0e;border:1px solid #23232e;border-radius:6px;color:#e7e9f0;font:inherit;padding:7px 8px;resize:vertical;min-height:54px}
126
+ .toil-dt-ta:focus{outline:none;border-color:#2563ff}
127
+ .toil-dt-ai-btns{display:flex;gap:6px;flex-wrap:wrap;margin:8px 0}
128
+ .toil-dt-ai-btn{display:flex;align-items:center;gap:6px;font:inherit;color:#e7e9f0;background:#23232e;border:1px solid #33333f;border-radius:6px;padding:5px 10px;cursor:pointer}
129
+ .toil-dt-ai-btn:hover{border-color:#454556}
130
+ .toil-dt-doc{display:block;color:#7aa2ff;text-decoration:none;padding:3px 0}.toil-dt-doc:hover{text-decoration:underline}
131
+ .toil-dt-pal-wrap{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:flex-start;justify-content:center;background:rgba(0,0,0,.45);padding-top:14vh}
132
+ .toil-dt-pal{width:440px;max-width:calc(100vw - 24px);background:#101016;border:1px solid #2c2c38;border-radius:12px;box-shadow:0 16px 56px rgba(0,0,0,.6);overflow:hidden;font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#e7e9f0}
133
+ .toil-dt-pal input{width:100%;box-sizing:border-box;background:none;border:0;border-bottom:1px solid #23232e;color:#e7e9f0;font:inherit;padding:11px 14px}
134
+ .toil-dt-pal input:focus{outline:none}
135
+ .toil-dt-pal-list{max-height:340px;overflow:auto;padding:4px}
136
+ .toil-dt-pal-item{display:flex;gap:8px;align-items:center;padding:7px 10px;border-radius:6px;cursor:pointer;color:#c8cee0}
137
+ .toil-dt-pal-item.sel{background:#1c1c26;color:#e7e9f0}
138
+ .toil-dt-pal-kind{color:#6b7088;font-size:11px;margin-left:auto}
139
+ `;
140
+ function injectStyles() {
141
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID))
142
+ return;
143
+ const el = document.createElement('style');
144
+ el.id = STYLE_ID;
145
+ el.textContent = CSS;
146
+ document.head.appendChild(el);
147
+ }
148
+ function isDynamic(pattern) {
149
+ return /[:*]/.test(pattern);
150
+ }
151
+ function openInEditor(file) {
152
+ void fetch(`/__toil/open?file=${encodeURIComponent(file)}`).catch(() => undefined);
153
+ }
154
+ function Row({ k, children }) {
155
+ return (_jsxs("div", { className: "toil-dt-row", children: [_jsx("span", { className: "toil-dt-k", children: k }), _jsx("span", { className: "toil-dt-v", children: children })] }));
156
+ }
157
+ function RouteTab({ routes, slots, info, }) {
158
+ const url = useCurrentUrl();
159
+ const pending = usePending();
160
+ const pathname = url.split('?')[0];
161
+ const search = url.slice(pathname.length);
162
+ let matched = null;
163
+ for (const r of routes) {
164
+ const params = matchRoute(r.pattern, pathname);
165
+ if (params) {
166
+ matched = { route: r, params };
167
+ break;
168
+ }
169
+ }
170
+ const activeSlots = [];
171
+ for (const [name, defs] of Object.entries(slots)) {
172
+ if (defs.some((d) => matchRoute(d.pattern, pathname)))
173
+ activeSlots.push(name);
174
+ }
175
+ const has = (r) => [
176
+ r.loading ? 'loading' : '',
177
+ r.errorComponent ? 'error' : '',
178
+ r.templates?.length ? 'template' : '',
179
+ r.layouts?.length ? `${String(r.layouts.length)} layout` : '',
180
+ ]
181
+ .filter(Boolean)
182
+ .join(', ') || 'none';
183
+ return (_jsxs("div", { className: "toil-dt-body", children: [_jsx(Row, { k: "path", children: pathname || '/' }), _jsx(Row, { k: "match", children: matched ? matched.route.pattern : 'no match (404)' }), search && _jsx(Row, { k: "query", children: search }), matched && Object.keys(matched.params).length > 0 && (_jsx(Row, { k: "params", children: JSON.stringify(matched.params) })), matched && _jsx(Row, { k: "boundaries", children: has(matched.route) }), _jsx(Row, { k: "slots", children: activeSlots.length ? activeSlots.join(', ') : 'none' }), _jsx(Row, { k: "navigating", children: pending ? 'yes' : 'no' }), _jsxs("p", { className: "toil-dt-sec", style: { marginTop: 12 }, children: ["Routes (", routes.length, ")"] }), routes.map((r) => {
184
+ const file = info?.routes[r.pattern];
185
+ return (_jsxs("div", { className: "toil-dt-rt", children: [isDynamic(r.pattern) ? (_jsx("span", { className: "dyn", children: r.pattern })) : (_jsx("a", { onClick: () => {
186
+ navigate(r.pattern);
187
+ }, children: r.pattern })), file && (_jsx("button", { className: "toil-dt-edit", title: `open ${file}`, onClick: () => {
188
+ openInEditor(file);
189
+ }, children: "edit" }))] }, r.pattern));
190
+ })] }));
191
+ }
192
+ function DataTab() {
193
+ const url = useCurrentUrl();
194
+ const entries = useLoaderCache();
195
+ const pathname = url.split('?')[0];
196
+ const key = loaderKey(pathname, url.slice(pathname.length));
197
+ const entry = entries.find((e) => e.key === key);
198
+ return (_jsxs("div", { className: "toil-dt-body", children: [!entry && _jsx("p", { className: "toil-dt-empty", children: "No cached loader data for this route." }), entry && (_jsxs(_Fragment, { children: [_jsx(Row, { k: "status", children: entry.status }), _jsx(Row, { k: "has loader", children: entry.hasLoader ? 'yes' : 'no' }), _jsx(Row, { k: "revalidate", children: entry.revalidate === false ? 'never' : `${String(entry.revalidate)}s` }), _jsx(Row, { k: "loaded", children: entry.loadedAt ? new Date(entry.loadedAt).toLocaleTimeString() : '-' }), entry.hasLoader ? (_jsxs(_Fragment, { children: [_jsxs("div", { style: { margin: '8px 0' }, children: [_jsx("button", { className: "toil-dt-btn", onClick: () => {
199
+ revalidate();
200
+ }, children: "Revalidate" }), ' ', _jsx("button", { className: "toil-dt-btn", onClick: () => {
201
+ clearLoaderData();
202
+ }, children: "Clear cache" })] }), entry.data === undefined ? (_jsx("p", { className: "toil-dt-empty", children: "Loader returned no data." })) : (_jsx("pre", { className: "toil-dt-pre", children: safeJson(entry.data) }))] })) : (_jsx("p", { className: "toil-dt-empty", children: "This route has no loader, so there is no data to inspect." }))] })), _jsxs("p", { className: "toil-dt-sec", style: { marginTop: 12 }, children: ["Cache (", entries.length, ")"] }), entries.map((e) => (_jsx(Row, { k: e.key, children: e.status }, e.key)))] }));
203
+ }
204
+ function Check({ ok, label }) {
205
+ return (_jsxs("div", { className: "toil-dt-chk", children: [_jsx("span", { className: ok ? 'toil-dt-ok' : 'toil-dt-bad', children: ok ? '✓' : '✗' }), _jsx("span", { children: label })] }));
206
+ }
207
+ function HeadTab() {
208
+ useCurrentUrl();
209
+ const title = typeof document !== 'undefined' ? document.title : '';
210
+ const { metas, links } = readHead();
211
+ const meta = (n) => metas.find((m) => m.name === n)?.content;
212
+ const og = {
213
+ title: meta('og:title') ?? title,
214
+ description: meta('og:description') ?? meta('description'),
215
+ image: meta('og:image'),
216
+ };
217
+ const pages = getPages();
218
+ const described = pages.filter((p) => p.metadata.description !== undefined).length;
219
+ return (_jsxs("div", { className: "toil-dt-body", children: [_jsx(Row, { k: "title", children: title || '(none)' }), _jsx("p", { className: "toil-dt-sec", style: { marginTop: 10 }, children: "OpenGraph preview" }), _jsxs("div", { className: "toil-dt-og", children: [og.image && (_jsx("img", { src: og.image, alt: "", className: "toil-dt-og-img" })), _jsxs("div", { className: "toil-dt-og-body", children: [_jsx("div", { className: "toil-dt-og-title", children: og.title || '(no title)' }), _jsx("div", { className: "toil-dt-og-desc", children: og.description ?? '(no description)' })] })] }), _jsx("p", { className: "toil-dt-sec", style: { marginTop: 10 }, children: "SEO checklist" }), _jsx(Check, { ok: Boolean(title), label: "Has a title" }), _jsx(Check, { ok: meta('description') !== undefined, label: "Has a meta description" }), _jsx(Check, { ok: og.image !== undefined, label: "Has an og:image" }), _jsx(Check, { ok: links.some((l) => l.rel === 'canonical'), label: "Has a canonical link" }), _jsx(Check, { ok: pages.length === 0 || described === pages.length, label: `Pages with a description: ${String(described)}/${String(pages.length)}` }), _jsxs("p", { className: "toil-dt-sec", style: { marginTop: 10 }, children: ["Meta (", metas.length, ")"] }), metas.map((m, i) => (_jsx(Row, { k: m.name, children: m.content }, `${m.name}:${String(i)}`)))] }));
220
+ }
221
+ function BuildTab({ info }) {
222
+ return (_jsxs("div", { className: "toil-dt-body", children: [!info && _jsx("p", { className: "toil-dt-empty", children: "Loading dev info..." }), info && (_jsxs(_Fragment, { children: [_jsx(Row, { k: "toiljs", children: info.toiljs }), _jsx(Row, { k: "vite", children: info.vite }), _jsx(Row, { k: "react", children: info.react }), _jsx(Row, { k: "dev server", children: `localhost:${String(info.port)}` }), _jsx("p", { className: "toil-dt-sec", style: { marginTop: 12 }, children: "Config" }), Object.entries(info.flags).map(([k, v]) => (_jsx(Row, { k: k, children: v ? 'on' : 'off' }, k))), _jsx(Row, { k: "ai", children: info.ai ? 'configured' : 'hand-off only' })] }))] }));
223
+ }
224
+ function ErrorsTab() {
225
+ const errors = useErrors();
226
+ if (errors.length === 0)
227
+ return _jsx("p", { className: "toil-dt-empty toil-dt-body", children: "No errors captured." });
228
+ return (_jsx("div", { className: "toil-dt-body", children: [...errors].reverse().map((e, i) => (_jsxs("div", { className: "toil-dt-err", children: [_jsxs("div", { className: "msg", children: [e.error.name, ": ", e.error.message] }), _jsxs("div", { className: "toil-dt-k", children: [e.source, ", ", new Date(e.time).toLocaleTimeString()] })] }, `${String(e.time)}:${String(i)}`))) }));
229
+ }
230
+ function buildAiContext() {
231
+ if (typeof window === 'undefined')
232
+ return '';
233
+ const where = window.location.pathname + window.location.search;
234
+ const title = document.title;
235
+ const desc = readHead().metas.find((m) => m.name === 'description')?.content;
236
+ const lines = [
237
+ 'I am working on a toiljs app (React with file-based routing, backend in toilscript/WASM).',
238
+ `Current page: ${where}`,
239
+ ];
240
+ if (title)
241
+ lines.push(`Page title: ${title}`);
242
+ if (desc)
243
+ lines.push(`Meta description: ${desc}`);
244
+ return lines.join('\n');
245
+ }
246
+ const DOC_LINKS = [
247
+ { label: 'Routing and file conventions', slug: 'routing' },
248
+ { label: 'Loaders and data', slug: 'loaders' },
249
+ { label: 'Metadata and SEO', slug: 'metadata' },
250
+ { label: 'Parallel routes and slots', slug: 'slots' },
251
+ ];
252
+ function AiTab({ info }) {
253
+ useCurrentUrl();
254
+ const [question, setQuestion] = useState('');
255
+ const [answer, setAnswer] = useState(null);
256
+ const [busy, setBusy] = useState(false);
257
+ const configured = info?.ai === true;
258
+ const prompt = () => {
259
+ const q = question.trim() || 'Explain this page and suggest improvements.';
260
+ return `${buildAiContext()}\n\nQuestion: ${q}`;
261
+ };
262
+ const handOff = (base) => {
263
+ window.open(`${base}${encodeURIComponent(prompt())}`, '_blank', 'noopener');
264
+ };
265
+ const copy = () => {
266
+ void navigator.clipboard.writeText(prompt()).catch(() => undefined);
267
+ };
268
+ const askInline = () => {
269
+ setBusy(true);
270
+ setAnswer(null);
271
+ void fetch('/__toil/ai', {
272
+ method: 'POST',
273
+ headers: { 'content-type': 'application/json' },
274
+ body: JSON.stringify({ prompt: prompt() }),
275
+ })
276
+ .then((r) => r.ok
277
+ ? r.json()
278
+ : Promise.reject(new Error(`HTTP ${String(r.status)}`)))
279
+ .then((d) => {
280
+ setAnswer(d.text ?? '(empty response)');
281
+ })
282
+ .catch((e) => {
283
+ setAnswer(`Error: ${e instanceof Error ? e.message : String(e)}`);
284
+ })
285
+ .finally(() => {
286
+ setBusy(false);
287
+ });
288
+ };
289
+ return (_jsxs("div", { className: "toil-dt-body", children: [_jsx("p", { className: "toil-dt-sec", children: "Ask about this page" }), _jsx("textarea", { className: "toil-dt-ta", placeholder: "Ask about the current route, or leave blank for a summary...", value: question, onChange: (e) => {
290
+ setQuestion(e.target.value);
291
+ } }), _jsxs("div", { className: "toil-dt-ai-btns", children: [_jsxs("button", { className: "toil-dt-ai-btn", onClick: () => {
292
+ handOff('https://claude.ai/new?q=');
293
+ }, children: [_jsx(ClaudeLogo, { size: 14 }), " Claude"] }), _jsxs("button", { className: "toil-dt-ai-btn", onClick: () => {
294
+ handOff('https://chatgpt.com/?q=');
295
+ }, children: [_jsx(ChatGptLogo, { size: 14 }), " ChatGPT"] }), _jsx("button", { className: "toil-dt-ai-btn", onClick: copy, children: "Copy" }), configured && (_jsx("button", { className: "toil-dt-ai-btn", disabled: busy, onClick: askInline, children: busy ? 'Asking...' : 'Ask inline' }))] }), !configured && (_jsxs("p", { className: "toil-dt-k", children: ["Inline answers are off. Set ", _jsx("span", { className: "toil-dt-tag", children: "devtools.ai" }), " in your config to proxy a provider; the API key stays server-side."] })), answer !== null && _jsx("pre", { className: "toil-dt-pre", children: answer }), _jsx("p", { className: "toil-dt-sec", style: { marginTop: 12 }, children: "Quick docs" }), DOC_LINKS.map((d) => (_jsx("a", { className: "toil-dt-doc", href: `${DOCS_BASE}/${d.slug}`, target: "_blank", rel: "noreferrer", children: d.label }, d.slug)))] }));
296
+ }
297
+ function Palette({ routes, onClose }) {
298
+ const [q, setQ] = useState('');
299
+ const [sel, setSel] = useState(0);
300
+ const items = [];
301
+ for (const r of routes) {
302
+ if (!isDynamic(r.pattern)) {
303
+ items.push({
304
+ label: r.pattern,
305
+ kind: 'route',
306
+ run: () => {
307
+ navigate(r.pattern);
308
+ onClose();
309
+ },
310
+ });
311
+ }
312
+ }
313
+ items.push({
314
+ label: 'Revalidate current route',
315
+ kind: 'action',
316
+ run: () => {
317
+ revalidate();
318
+ onClose();
319
+ },
320
+ }, {
321
+ label: 'Clear loader cache',
322
+ kind: 'action',
323
+ run: () => {
324
+ clearLoaderData();
325
+ onClose();
326
+ },
327
+ }, {
328
+ label: 'Ask AI about this page',
329
+ kind: 'action',
330
+ run: () => {
331
+ setPrefs({ open: true, tab: 'ai' });
332
+ onClose();
333
+ },
334
+ }, {
335
+ label: 'Open preferences',
336
+ kind: 'action',
337
+ run: () => {
338
+ setPrefs({ open: true, tab: 'prefs' });
339
+ onClose();
340
+ },
341
+ });
342
+ const needle = q.toLowerCase();
343
+ const filtered = items.filter((it) => it.label.toLowerCase().includes(needle));
344
+ const clamped = Math.min(sel, Math.max(0, filtered.length - 1));
345
+ return (_jsx("div", { className: "toil-dt-pal-wrap", onClick: onClose, children: _jsxs("div", { className: "toil-dt-pal", onClick: (e) => {
346
+ e.stopPropagation();
347
+ }, children: [_jsx("input", { autoFocus: true, placeholder: "Go to a route or run an action...", value: q, onChange: (e) => {
348
+ setQ(e.target.value);
349
+ setSel(0);
350
+ }, onKeyDown: (e) => {
351
+ if (e.key === 'ArrowDown') {
352
+ e.preventDefault();
353
+ setSel((s) => Math.min(s + 1, filtered.length - 1));
354
+ }
355
+ else if (e.key === 'ArrowUp') {
356
+ e.preventDefault();
357
+ setSel((s) => Math.max(s - 1, 0));
358
+ }
359
+ else if (e.key === 'Enter') {
360
+ e.preventDefault();
361
+ filtered[clamped]?.run();
362
+ }
363
+ else if (e.key === 'Escape') {
364
+ onClose();
365
+ }
366
+ } }), _jsxs("div", { className: "toil-dt-pal-list", children: [filtered.length === 0 && _jsx("div", { className: "toil-dt-pal-item", children: "No matches" }), filtered.map((it, i) => (_jsxs("div", { className: `toil-dt-pal-item ${i === clamped ? 'sel' : ''}`, onMouseEnter: () => {
367
+ setSel(i);
368
+ }, onClick: it.run, children: [_jsx("span", { children: it.label }), _jsx("span", { className: "toil-dt-pal-kind", children: it.kind })] }, `${it.kind}:${it.label}`)))] })] }) }));
369
+ }
370
+ function PrefsTab() {
371
+ const p = usePrefs();
372
+ const [flags, setFlags] = useState({ viewTransitions: false, transitions: false });
373
+ const toggle = (key) => {
374
+ const next = !flags[key];
375
+ setFlags((f) => ({ ...f, [key]: next }));
376
+ if (key === 'viewTransitions')
377
+ setViewTransitions(next);
378
+ else
379
+ setTransitions(next);
380
+ };
381
+ return (_jsxs("div", { className: "toil-dt-body", children: [_jsxs("div", { className: "toil-dt-sw", children: [_jsx("span", { children: "View transitions" }), _jsx("button", { className: "toil-dt-btn", onClick: () => {
382
+ toggle('viewTransitions');
383
+ }, children: flags.viewTransitions ? 'on' : 'off' })] }), _jsxs("div", { className: "toil-dt-sw", children: [_jsx("span", { children: "Loader transition" }), _jsx("button", { className: "toil-dt-btn", onClick: () => {
384
+ toggle('transitions');
385
+ }, children: flags.transitions ? 'on' : 'off' })] }), _jsxs("div", { className: "toil-dt-sw", children: [_jsx("span", { children: "Toolbar side" }), _jsx("button", { className: "toil-dt-btn", onClick: () => {
386
+ setPrefs({ side: p.side === 'left' ? 'right' : 'left' });
387
+ }, children: p.side })] })] }));
388
+ }
389
+ const TABS = [
390
+ { id: 'route', label: 'Route' },
391
+ { id: 'data', label: 'Data' },
392
+ { id: 'head', label: 'Head' },
393
+ { id: 'build', label: 'Build' },
394
+ { id: 'errors', label: 'Errors' },
395
+ { id: 'ai', label: 'AI' },
396
+ { id: 'prefs', label: 'Prefs' },
397
+ ];
398
+ export function DevToolbar({ routes, slots, }) {
399
+ const p = usePrefs();
400
+ const pending = usePending();
401
+ const errors = useErrors();
402
+ const [info, setInfo] = useState(null);
403
+ const [palette, setPalette] = useState(false);
404
+ useEffect(() => {
405
+ injectStyles();
406
+ void fetch('/__toil/devinfo')
407
+ .then((r) => (r.ok ? r.json() : null))
408
+ .then((data) => {
409
+ if (data)
410
+ setInfo(data);
411
+ })
412
+ .catch(() => undefined);
413
+ }, []);
414
+ useEffect(() => {
415
+ const onKey = (e) => {
416
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
417
+ e.preventDefault();
418
+ setPalette((v) => !v);
419
+ }
420
+ };
421
+ window.addEventListener('keydown', onKey);
422
+ return () => {
423
+ window.removeEventListener('keydown', onKey);
424
+ };
425
+ }, []);
426
+ if (info && !info.enabled)
427
+ return null;
428
+ const dotClass = errors.length > 0 ? 'error' : pending ? 'pending' : '';
429
+ const pal = palette ? (_jsx(Palette, { routes: routes, onClose: () => {
430
+ setPalette(false);
431
+ } })) : null;
432
+ if (!p.open) {
433
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: `toil-dt ${p.side}`, children: _jsxs("div", { className: "toil-dt-badge", onClick: () => {
434
+ setPrefs({ open: true });
435
+ }, title: "toiljs devtools (cmd+K)", children: [_jsx(ToilLogo, { size: 16 }), _jsx("span", { className: `toil-dt-dot ${dotClass}` })] }) }), pal] }));
436
+ }
437
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: `toil-dt ${p.side}`, children: _jsxs("div", { className: "toil-dt-panel", children: [_jsxs("div", { className: "toil-dt-head", children: [_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [_jsx(ToilLogo, { size: 14 }), _jsx("span", { className: "toil-dt-logo", children: "toiljs" }), " devtools", _jsx("span", { className: `toil-dt-dot ${dotClass}` })] }), _jsx("button", { className: "toil-dt-x", onClick: () => {
438
+ setPrefs({ open: false });
439
+ }, children: "\u2715" })] }), _jsx("div", { className: "toil-dt-tabs", children: TABS.map((t) => (_jsxs("button", { className: `toil-dt-tab ${p.tab === t.id ? 'active' : ''}`, onClick: () => {
440
+ setPrefs({ tab: t.id });
441
+ }, children: [t.label, t.id === 'errors' && errors.length > 0 ? ` (${String(errors.length)})` : ''] }, t.id))) }), p.tab === 'route' && _jsx(RouteTab, { routes: routes, slots: slots, info: info }), p.tab === 'data' && _jsx(DataTab, {}), p.tab === 'head' && _jsx(HeadTab, {}), p.tab === 'build' && _jsx(BuildTab, { info: info }), p.tab === 'errors' && _jsx(ErrorsTab, {}), p.tab === 'ai' && _jsx(AiTab, { info: info }), p.tab === 'prefs' && _jsx(PrefsTab, {})] }) }), pal] }));
442
+ }
@@ -1,4 +1,13 @@
1
1
  import { Component, type ErrorInfo, type ReactNode } from 'react';
2
+ export interface DevError {
3
+ readonly error: Error;
4
+ readonly componentStack?: string;
5
+ readonly source: 'render' | 'window' | 'unhandledrejection';
6
+ readonly time: number;
7
+ }
8
+ declare function subscribe(listener: () => void): () => void;
9
+ export declare function getErrorLog(): readonly DevError[];
10
+ export declare const subscribeErrors: typeof subscribe;
2
11
  export declare function isDevMode(): boolean;
3
12
  export declare function initDevErrorOverlay(): void;
4
13
  interface BoundaryProps {
@@ -2,12 +2,17 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Component, useSyncExternalStore, } from 'react';
3
3
  let current = null;
4
4
  const listeners = new Set();
5
+ let errorLog = [];
6
+ const MAX_LOG = 50;
5
7
  function emit() {
6
8
  for (const listener of listeners)
7
9
  listener();
8
10
  }
9
11
  function setDevError(next) {
10
12
  current = next;
13
+ if (next) {
14
+ errorLog = [...errorLog, next].slice(-MAX_LOG);
15
+ }
11
16
  emit();
12
17
  }
13
18
  function subscribe(listener) {
@@ -16,6 +21,10 @@ function subscribe(listener) {
16
21
  listeners.delete(listener);
17
22
  };
18
23
  }
24
+ export function getErrorLog() {
25
+ return errorLog;
26
+ }
27
+ export const subscribeErrors = subscribe;
19
28
  export function isDevMode() {
20
29
  try {
21
30
  return Boolean(import.meta.env?.DEV);
@@ -30,13 +39,14 @@ export function initDevErrorOverlay() {
30
39
  return;
31
40
  windowBound = true;
32
41
  window.addEventListener('error', (event) => {
33
- if (event.error instanceof Error)
34
- setDevError({ error: event.error, source: 'window' });
42
+ if (event.error instanceof Error) {
43
+ setDevError({ error: event.error, source: 'window', time: Date.now() });
44
+ }
35
45
  });
36
46
  window.addEventListener('unhandledrejection', (event) => {
37
47
  const reason = event.reason;
38
48
  const error = reason instanceof Error ? reason : new Error(String(reason));
39
- setDevError({ error, source: 'unhandledrejection' });
49
+ setDevError({ error, source: 'unhandledrejection', time: Date.now() });
40
50
  });
41
51
  }
42
52
  export class DevErrorBoundary extends Component {
@@ -46,7 +56,12 @@ export class DevErrorBoundary extends Component {
46
56
  return { crashed: true };
47
57
  }
48
58
  componentDidCatch(error, info) {
49
- setDevError({ error, componentStack: info.componentStack ?? undefined, source: 'render' });
59
+ setDevError({
60
+ error,
61
+ componentStack: info.componentStack ?? undefined,
62
+ source: 'render',
63
+ time: Date.now(),
64
+ });
50
65
  }
51
66
  componentDidMount() {
52
67
  this.unsubscribe = subscribe(() => {
@@ -8,4 +8,5 @@ declare global {
8
8
  }
9
9
  }
10
10
  export declare function prefetch(href: string): void;
11
+ export declare function prefetchData(href: string): void;
11
12
  export declare function startPrefetcher(routes: RouteDef[]): void;
@@ -1,6 +1,8 @@
1
+ import { prefetchRouteData } from '../routing/loader.js';
1
2
  import { matchRoute } from '../routing/match.js';
2
3
  let routeTable = [];
3
4
  const warmed = new WeakSet();
5
+ const dataWarmed = new Set();
4
6
  let io = null;
5
7
  let mo = null;
6
8
  function routeForHref(href) {
@@ -32,6 +34,29 @@ export function prefetch(href) {
32
34
  if (route)
33
35
  warm(route);
34
36
  }
37
+ export function prefetchData(href) {
38
+ let url;
39
+ try {
40
+ url = new URL(href, window.location.href);
41
+ }
42
+ catch {
43
+ return;
44
+ }
45
+ if (url.origin !== window.location.origin)
46
+ return;
47
+ const key = url.pathname + url.search;
48
+ if (dataWarmed.has(key))
49
+ return;
50
+ for (const route of routeTable) {
51
+ const params = matchRoute(route.pattern, url.pathname);
52
+ if (params) {
53
+ dataWarmed.add(key);
54
+ warm(route);
55
+ prefetchRouteData(route, params, url.pathname, url.search);
56
+ return;
57
+ }
58
+ }
59
+ }
35
60
  function isPrefetchable(a) {
36
61
  if (a.target && a.target !== '_self')
37
62
  return false;
@@ -89,6 +114,16 @@ export function startPrefetcher(routes) {
89
114
  }
90
115
  }
91
116
  });
117
+ const onIntent = (event) => {
118
+ const target = event.target;
119
+ if (!(target instanceof Element))
120
+ return;
121
+ const a = target.closest('a[href]');
122
+ if (a instanceof HTMLAnchorElement && isPrefetchable(a) && a.href)
123
+ prefetchData(a.href);
124
+ };
125
+ document.addEventListener('pointerover', onIntent, { passive: true });
126
+ document.addEventListener('focusin', onIntent);
92
127
  const begin = () => {
93
128
  scan(document);
94
129
  mo?.observe(document.body, { childList: true, subtree: true });
@@ -21,7 +21,7 @@ function renderMatched(matched, params, pathname, epoch, keyPrefix) {
21
21
  const fallback = matched.loading
22
22
  ? createElement(Suspense, { fallback: null }, createElement(loadingComponent(matched.loading)))
23
23
  : null;
24
- let content = (_jsx(Suspense, { fallback: fallback, children: _jsx(RoutePage, { route: matched, params: params, dataKey: dataKey, epoch: epoch }) }, matched.loading ? `${dataKey}:${String(epoch)}` : undefined));
24
+ let content = (_jsx(Suspense, { fallback: fallback, children: _jsx(RoutePage, { route: matched, params: params, dataKey: dataKey, epoch: epoch }, dataKey) }, matched.loading ? `${dataKey}:${String(epoch)}` : undefined));
25
25
  const templates = matched.templates ?? [];
26
26
  for (let i = templates.length - 1; i >= 0; i--) {
27
27
  const Template = nestedLayout(templates[i]);
@@ -1,4 +1,4 @@
1
- import { useContext, useEffect, useMemo, useReducer, useSyncExternalStore } from 'react';
1
+ import { startTransition, useContext, useEffect, useMemo, useReducer, useSyncExternalStore, } from 'react';
2
2
  import { back, forward, isNavigationPending, navigate, refresh, subscribeLocation, subscribePending, } from '../navigation/navigation.js';
3
3
  import { clearLoaderData, revalidate as revalidateData } from './loader.js';
4
4
  import { ParamsContext } from './params-context.js';
@@ -32,7 +32,11 @@ export function useRouter() {
32
32
  }
33
33
  function useLocationSubscription() {
34
34
  const [, forceUpdate] = useReducer((n) => n + 1, 0);
35
- useEffect(() => subscribeLocation(forceUpdate), []);
35
+ useEffect(() => subscribeLocation(() => {
36
+ startTransition(() => {
37
+ forceUpdate();
38
+ });
39
+ }), []);
36
40
  }
37
41
  export function useLocation() {
38
42
  useLocationSubscription();
@@ -16,7 +16,30 @@ interface RouteData {
16
16
  data: unknown;
17
17
  head?: HeadSpec;
18
18
  }
19
+ interface Entry {
20
+ status: 'pending' | 'done' | 'error';
21
+ promise: Promise<void>;
22
+ value?: RouteData;
23
+ error?: unknown;
24
+ loadedAt: number;
25
+ revalidate: Revalidate;
26
+ epoch: number;
27
+ hasLoader: boolean;
28
+ prefetched: boolean;
29
+ }
30
+ export declare function subscribeLoaderCache(listener: () => void): () => void;
31
+ export interface LoaderCacheSnapshot {
32
+ readonly key: string;
33
+ readonly status: Entry['status'];
34
+ readonly hasLoader: boolean;
35
+ readonly revalidate: Revalidate;
36
+ readonly loadedAt: number;
37
+ readonly epoch: number;
38
+ readonly data: unknown;
39
+ }
40
+ export declare function inspectLoaderCache(): LoaderCacheSnapshot[];
19
41
  export declare function loaderKey(pathname: string, search: string): string;
42
+ export declare function prefetchRouteData(route: RouteDef, params: RouteParams, pathname: string, search: string): void;
20
43
  export declare function readRouteData(route: RouteDef, params: RouteParams, key: string, epoch: number): RouteData;
21
44
  export declare function clearLoaderData(): void;
22
45
  export declare function invalidateLoaderData(href?: string): void;