toiljs 0.0.15 → 0.0.19

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 (273) 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 +116 -5
  14. package/LICENSE +187 -187
  15. package/README.md +524 -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/backend/index.d.ts +1 -0
  21. package/build/backend/index.js +20 -1
  22. package/build/cli/.tsbuildinfo +1 -1
  23. package/build/cli/index.js +1320 -696
  24. package/build/client/.tsbuildinfo +1 -1
  25. package/build/client/dev/devtools.d.ts +6 -0
  26. package/build/client/dev/devtools.js +479 -0
  27. package/build/client/dev/error-overlay.d.ts +9 -0
  28. package/build/client/dev/error-overlay.js +19 -4
  29. package/build/client/errors.d.ts +1 -0
  30. package/build/client/errors.js +3 -0
  31. package/build/client/index.d.ts +2 -0
  32. package/build/client/index.js +2 -0
  33. package/build/client/navigation/prefetch.d.ts +1 -0
  34. package/build/client/navigation/prefetch.js +35 -0
  35. package/build/client/routing/Router.js +1 -1
  36. package/build/client/routing/hooks.js +6 -2
  37. package/build/client/routing/loader.d.ts +23 -0
  38. package/build/client/routing/loader.js +53 -7
  39. package/build/client/routing/mount.js +4 -3
  40. package/build/client/rpc.d.ts +1 -0
  41. package/build/client/rpc.js +37 -0
  42. package/build/compiler/.tsbuildinfo +1 -1
  43. package/build/compiler/config.d.ts +16 -0
  44. package/build/compiler/config.js +9 -0
  45. package/build/compiler/docs.js +78 -21
  46. package/build/compiler/generate.js +5 -4
  47. package/build/compiler/index.d.ts +3 -2
  48. package/build/compiler/index.js +2 -2
  49. package/build/compiler/plugin.js +228 -0
  50. package/build/compiler/prerender.d.ts +1 -0
  51. package/build/compiler/prerender.js +1 -1
  52. package/build/compiler/seo.d.ts +1 -1
  53. package/build/compiler/seo.js +20 -5
  54. package/build/compiler/ssg.js +39 -2
  55. package/build/compiler/vite.js +25 -0
  56. package/build/io/.tsbuildinfo +1 -1
  57. package/build/io/codec.d.ts +54 -0
  58. package/build/io/codec.js +143 -0
  59. package/build/io/index.d.ts +1 -2
  60. package/build/io/index.js +1 -2
  61. package/build/logger/.tsbuildinfo +1 -1
  62. package/build/shared/.tsbuildinfo +1 -1
  63. package/eslint.config.js +48 -48
  64. package/examples/basic/client/404.tsx +11 -11
  65. package/examples/basic/client/components/.gitkeep +1 -1
  66. package/examples/basic/client/global-error.tsx +13 -13
  67. package/examples/basic/client/layout.tsx +25 -25
  68. package/examples/basic/client/public/images/.gitkeep +1 -1
  69. package/examples/basic/client/public/images/logo.svg +36 -36
  70. package/examples/basic/client/public/robots.txt +2 -2
  71. package/examples/basic/client/routes/docs/[...slug].tsx +12 -12
  72. package/examples/basic/client/routes/features/error/error.tsx +16 -16
  73. package/examples/basic/client/routes/features/index.tsx +1 -1
  74. package/examples/basic/client/routes/features/template/b.tsx +14 -14
  75. package/examples/basic/client/routes/files/[[...slug]].tsx +21 -21
  76. package/examples/basic/client/routes/gallery/layout.tsx +13 -13
  77. package/examples/basic/client/routes/io.tsx +23 -24
  78. package/examples/basic/client/routes/loader-demo/loading.tsx +13 -13
  79. package/examples/basic/client/routes/rest.tsx +74 -0
  80. package/examples/basic/client/routes/rpc.tsx +43 -0
  81. package/examples/basic/client/routes/search.tsx +61 -61
  82. package/examples/basic/client/toil.tsx +5 -5
  83. package/package.json +167 -148
  84. package/presets/eslint.js +88 -88
  85. package/presets/no-uint8array-tostring.js +200 -200
  86. package/presets/prettier-plugin.js +51 -0
  87. package/presets/prettier.json +19 -18
  88. package/presets/tsconfig.json +37 -37
  89. package/server/runtime/README.md +97 -0
  90. package/server/runtime/abort/abort.ts +27 -0
  91. package/server/runtime/env/Server.ts +61 -0
  92. package/server/runtime/envelope.ts +191 -0
  93. package/server/runtime/exports/index.ts +52 -0
  94. package/server/runtime/handlers/ToilHandler.ts +34 -0
  95. package/server/runtime/index.ts +26 -0
  96. package/server/runtime/lang/Potential.ts +5 -0
  97. package/server/runtime/memory.ts +81 -0
  98. package/server/runtime/request.ts +55 -0
  99. package/server/runtime/response.ts +86 -0
  100. package/server/runtime/rest/Rest.ts +39 -0
  101. package/server/runtime/rest/RestHandler.ts +20 -0
  102. package/server/runtime/rest/RouteContext.ts +82 -0
  103. package/server/runtime/rest/match.ts +48 -0
  104. package/server/runtime/tsconfig.json +7 -0
  105. package/src/backend/index.ts +202 -160
  106. package/src/cli/create.ts +15 -5
  107. package/src/cli/diagnostics.ts +81 -0
  108. package/src/cli/doctor.ts +384 -7
  109. package/src/cli/index.ts +11 -2
  110. package/src/cli/proc.ts +50 -50
  111. package/src/cli/updates.ts +69 -69
  112. package/src/cli/validate.ts +31 -31
  113. package/src/client/channel/channel.ts +146 -146
  114. package/src/client/components/Form.tsx +65 -65
  115. package/src/client/components/Script.tsx +113 -113
  116. package/src/client/components/Slot.tsx +21 -21
  117. package/src/client/dev/devtools.tsx +1018 -0
  118. package/src/client/dev/error-overlay.tsx +30 -4
  119. package/src/client/errors.ts +11 -0
  120. package/src/client/head/head.ts +167 -167
  121. package/src/client/head/metadata.ts +112 -112
  122. package/src/client/index.ts +91 -89
  123. package/src/client/navigation/NavLink.tsx +86 -86
  124. package/src/client/navigation/navigation.ts +235 -235
  125. package/src/client/navigation/prefetch.ts +169 -130
  126. package/src/client/navigation/scroll.ts +53 -53
  127. package/src/client/routing/Router.tsx +8 -2
  128. package/src/client/routing/action.ts +122 -122
  129. package/src/client/routing/error-boundary.tsx +43 -43
  130. package/src/client/routing/hooks.ts +21 -6
  131. package/src/client/routing/loader.ts +325 -235
  132. package/src/client/routing/match.ts +47 -47
  133. package/src/client/routing/mount.tsx +54 -52
  134. package/src/client/routing/params-context.ts +10 -10
  135. package/src/client/routing/slot-context.ts +7 -7
  136. package/src/client/rpc.ts +64 -0
  137. package/src/client/search/search.ts +189 -189
  138. package/src/client/search/use-page-search.ts +73 -73
  139. package/src/client/types.ts +73 -73
  140. package/src/compiler/config.ts +221 -182
  141. package/src/compiler/docs.ts +285 -228
  142. package/src/compiler/generate.ts +395 -394
  143. package/src/compiler/index.ts +66 -57
  144. package/src/compiler/pages.ts +70 -70
  145. package/src/compiler/plugin.ts +258 -2
  146. package/src/compiler/prerender.ts +156 -156
  147. package/src/compiler/seo.ts +417 -390
  148. package/src/compiler/ssg.ts +171 -126
  149. package/src/compiler/vite.ts +34 -0
  150. package/src/io/FastMap.ts +151 -127
  151. package/src/io/FastSet.ts +15 -1
  152. package/src/io/codec.ts +217 -0
  153. package/src/io/index.ts +10 -11
  154. package/src/io/lengths.ts +14 -14
  155. package/src/io/types.ts +19 -18
  156. package/src/logger/index.ts +22 -22
  157. package/src/shared/index.ts +10 -10
  158. package/std/client/index.d.ts +15 -15
  159. package/std/client/package.json +3 -3
  160. package/test/assembly/example.spec.ts +17 -7
  161. package/test/channel.test.ts +21 -21
  162. package/test/doctor.test.ts +65 -0
  163. package/test/dom/Link.test.tsx +47 -47
  164. package/test/dom/NavLink.test.tsx +37 -37
  165. package/test/dom/error-overlay.test.tsx +44 -44
  166. package/test/dom/loader.test.tsx +121 -121
  167. package/test/dom/navigation.test.ts +59 -59
  168. package/test/dom/revalidate.test.tsx +38 -38
  169. package/test/dom/route-head.test.tsx +78 -78
  170. package/test/dom/router-loading.test.tsx +44 -44
  171. package/test/dom/scroll.test.ts +56 -56
  172. package/test/dom/use-metadata.test.tsx +58 -58
  173. package/test/errors.test.ts +21 -0
  174. package/test/io.test.ts +117 -93
  175. package/test/navlink.test.ts +28 -28
  176. package/test/placeholder.test.ts +9 -9
  177. package/test/prettier-plugin.test.ts +46 -0
  178. package/test/routes.test.ts +76 -76
  179. package/test/rpc.test.ts +50 -0
  180. package/test/seo.test.ts +175 -164
  181. package/test/slot-layouts.test.ts +69 -69
  182. package/test/ssg.test.ts +36 -36
  183. package/test/update.test.ts +44 -44
  184. package/test/validate.test.ts +42 -42
  185. package/tests/data-parity/generated-parity.ts +99 -0
  186. package/tests/data-parity/parity.ts +80 -0
  187. package/tests/data-parity/spec.ts +46 -0
  188. package/toil-routes.d.ts +7 -0
  189. package/tsconfig.backend.json +13 -13
  190. package/tsconfig.base.json +35 -35
  191. package/tsconfig.cli.json +13 -13
  192. package/tsconfig.client.json +14 -14
  193. package/tsconfig.compiler.json +13 -13
  194. package/tsconfig.io.json +12 -12
  195. package/tsconfig.json +22 -22
  196. package/tsconfig.logger.json +12 -12
  197. package/tsconfig.server.json +10 -10
  198. package/tsconfig.shared.json +12 -12
  199. package/vitest.config.ts +26 -26
  200. package/.idea/codeStyles/Project.xml +0 -54
  201. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  202. package/.idea/inspectionProfiles/Project_Default.xml +0 -6
  203. package/.idea/modules.xml +0 -8
  204. package/.idea/prettier.xml +0 -7
  205. package/.idea/toiljs.iml +0 -8
  206. package/.idea/vcs.xml +0 -6
  207. package/.toil/entry.tsx +0 -9
  208. package/.toil/index.html +0 -12
  209. package/.toil/routes.ts +0 -9
  210. package/build/cli/configure.d.ts +0 -16
  211. package/build/cli/configure.js +0 -272
  212. package/build/cli/create.d.ts +0 -16
  213. package/build/cli/create.js +0 -420
  214. package/build/cli/diagnostics.d.ts +0 -55
  215. package/build/cli/diagnostics.js +0 -333
  216. package/build/cli/doctor.d.ts +0 -6
  217. package/build/cli/doctor.js +0 -249
  218. package/build/cli/features.d.ts +0 -25
  219. package/build/cli/features.js +0 -107
  220. package/build/cli/index.d.ts +0 -2
  221. package/build/cli/proc.d.ts +0 -6
  222. package/build/cli/proc.js +0 -31
  223. package/build/cli/ui.d.ts +0 -9
  224. package/build/cli/ui.js +0 -75
  225. package/build/cli/update.d.ts +0 -7
  226. package/build/cli/update.js +0 -117
  227. package/build/cli/updates.d.ts +0 -10
  228. package/build/cli/updates.js +0 -45
  229. package/build/cli/validate.d.ts +0 -4
  230. package/build/cli/validate.js +0 -19
  231. package/build/client/Link.d.ts +0 -8
  232. package/build/client/Link.js +0 -44
  233. package/build/client/NavLink.d.ts +0 -14
  234. package/build/client/NavLink.js +0 -37
  235. package/build/client/Router.d.ts +0 -7
  236. package/build/client/Router.js +0 -55
  237. package/build/client/channel.d.ts +0 -23
  238. package/build/client/channel.js +0 -94
  239. package/build/client/error-boundary.d.ts +0 -16
  240. package/build/client/error-boundary.js +0 -19
  241. package/build/client/head.d.ts +0 -26
  242. package/build/client/head.js +0 -87
  243. package/build/client/hooks.d.ts +0 -17
  244. package/build/client/hooks.js +0 -48
  245. package/build/client/lazy.d.ts +0 -16
  246. package/build/client/lazy.js +0 -53
  247. package/build/client/match.d.ts +0 -2
  248. package/build/client/match.js +0 -32
  249. package/build/client/mount.d.ts +0 -2
  250. package/build/client/mount.js +0 -13
  251. package/build/client/navigation.d.ts +0 -13
  252. package/build/client/navigation.js +0 -97
  253. package/build/client/params-context.d.ts +0 -2
  254. package/build/client/params-context.js +0 -2
  255. package/build/client/prefetch.d.ts +0 -11
  256. package/build/client/prefetch.js +0 -100
  257. package/build/client/runtime.d.ts +0 -31
  258. package/build/client/runtime.js +0 -112
  259. package/build/client/scroll.d.ts +0 -8
  260. package/build/client/scroll.js +0 -36
  261. package/build/io/BinaryReader.d.ts +0 -44
  262. package/build/io/BinaryReader.js +0 -244
  263. package/build/io/BinaryWriter.d.ts +0 -44
  264. package/build/io/BinaryWriter.js +0 -297
  265. package/build/server/release.wasm +0 -0
  266. package/build/server/release.wat +0 -9
  267. package/src/io/BinaryReader.ts +0 -340
  268. package/src/io/BinaryWriter.ts +0 -385
  269. package/src/server/index.ts +0 -10
  270. package/src/server/main.ts +0 -13
  271. package/src/server/tsconfig.json +0 -4
  272. package/toil-env.d.ts +0 -16
  273. package/toilconfig.json +0 -30
@@ -0,0 +1,479 @@
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
+ const AI_CODE_MAX = 8000;
253
+ function AiTab({ info, routes }) {
254
+ const url = useCurrentUrl();
255
+ const [question, setQuestion] = useState('');
256
+ const [answer, setAnswer] = useState(null);
257
+ const [busy, setBusy] = useState(false);
258
+ const [source, setSource] = useState(null);
259
+ const configured = info?.ai === true;
260
+ const pathname = url.split('?')[0];
261
+ let file;
262
+ for (const r of routes) {
263
+ if (matchRoute(r.pattern, pathname)) {
264
+ file = info?.routes[r.pattern];
265
+ break;
266
+ }
267
+ }
268
+ useEffect(() => {
269
+ if (!file) {
270
+ setSource(null);
271
+ return;
272
+ }
273
+ let cancelled = false;
274
+ void fetch(`/__toil/source?file=${encodeURIComponent(file)}`)
275
+ .then((r) => (r.ok ? r.text() : null))
276
+ .then((code) => {
277
+ if (!cancelled)
278
+ setSource(code !== null ? { file, code } : null);
279
+ })
280
+ .catch(() => {
281
+ if (!cancelled)
282
+ setSource(null);
283
+ });
284
+ return () => {
285
+ cancelled = true;
286
+ };
287
+ }, [file]);
288
+ const prompt = () => {
289
+ const q = question.trim() || 'Explain this page and suggest improvements.';
290
+ const parts = [buildAiContext()];
291
+ if (source) {
292
+ const code = source.code.slice(0, AI_CODE_MAX);
293
+ const cut = source.code.length > AI_CODE_MAX ? '\n... (truncated)' : '';
294
+ parts.push(`\nPage source (${source.file}):\n\`\`\`tsx\n${code}${cut}\n\`\`\``);
295
+ }
296
+ parts.push(`\nQuestion: ${q}`);
297
+ return parts.join('\n');
298
+ };
299
+ const handOff = (base) => {
300
+ window.open(`${base}${encodeURIComponent(prompt())}`, '_blank', 'noopener');
301
+ };
302
+ const copy = () => {
303
+ void navigator.clipboard.writeText(prompt()).catch(() => undefined);
304
+ };
305
+ const askInline = () => {
306
+ setBusy(true);
307
+ setAnswer(null);
308
+ void fetch('/__toil/ai', {
309
+ method: 'POST',
310
+ headers: { 'content-type': 'application/json' },
311
+ body: JSON.stringify({ prompt: prompt() }),
312
+ })
313
+ .then((r) => r.ok
314
+ ? r.json()
315
+ : Promise.reject(new Error(`HTTP ${String(r.status)}`)))
316
+ .then((d) => {
317
+ setAnswer(d.text ?? '(empty response)');
318
+ })
319
+ .catch((e) => {
320
+ setAnswer(`Error: ${e instanceof Error ? e.message : String(e)}`);
321
+ })
322
+ .finally(() => {
323
+ setBusy(false);
324
+ });
325
+ };
326
+ 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) => {
327
+ setQuestion(e.target.value);
328
+ } }), _jsxs("div", { className: "toil-dt-ai-btns", children: [_jsxs("button", { className: "toil-dt-ai-btn", onClick: () => {
329
+ handOff('https://claude.ai/new?q=');
330
+ }, children: [_jsx(ClaudeLogo, { size: 14 }), " Claude"] }), _jsxs("button", { className: "toil-dt-ai-btn", onClick: () => {
331
+ handOff('https://chatgpt.com/?q=');
332
+ }, 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' }))] }), source && (_jsxs("p", { className: "toil-dt-k", children: ["Prompt includes this route's source (", source.file.split('/').pop(), ")."] })), !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)))] }));
333
+ }
334
+ function Palette({ routes, onClose }) {
335
+ const [q, setQ] = useState('');
336
+ const [sel, setSel] = useState(0);
337
+ const items = [];
338
+ for (const r of routes) {
339
+ if (!isDynamic(r.pattern)) {
340
+ items.push({
341
+ label: r.pattern,
342
+ kind: 'route',
343
+ run: () => {
344
+ navigate(r.pattern);
345
+ onClose();
346
+ },
347
+ });
348
+ }
349
+ }
350
+ items.push({
351
+ label: 'Revalidate current route',
352
+ kind: 'action',
353
+ run: () => {
354
+ revalidate();
355
+ onClose();
356
+ },
357
+ }, {
358
+ label: 'Clear loader cache',
359
+ kind: 'action',
360
+ run: () => {
361
+ clearLoaderData();
362
+ onClose();
363
+ },
364
+ }, {
365
+ label: 'Ask AI about this page',
366
+ kind: 'action',
367
+ run: () => {
368
+ setPrefs({ open: true, tab: 'ai' });
369
+ onClose();
370
+ },
371
+ }, {
372
+ label: 'Open preferences',
373
+ kind: 'action',
374
+ run: () => {
375
+ setPrefs({ open: true, tab: 'prefs' });
376
+ onClose();
377
+ },
378
+ });
379
+ const needle = q.toLowerCase();
380
+ const filtered = items.filter((it) => it.label.toLowerCase().includes(needle));
381
+ const clamped = Math.min(sel, Math.max(0, filtered.length - 1));
382
+ return (_jsx("div", { className: "toil-dt-pal-wrap", onClick: onClose, children: _jsxs("div", { className: "toil-dt-pal", onClick: (e) => {
383
+ e.stopPropagation();
384
+ }, children: [_jsx("input", { autoFocus: true, placeholder: "Go to a route or run an action...", value: q, onChange: (e) => {
385
+ setQ(e.target.value);
386
+ setSel(0);
387
+ }, onKeyDown: (e) => {
388
+ if (e.key === 'ArrowDown') {
389
+ e.preventDefault();
390
+ setSel((s) => Math.min(s + 1, filtered.length - 1));
391
+ }
392
+ else if (e.key === 'ArrowUp') {
393
+ e.preventDefault();
394
+ setSel((s) => Math.max(s - 1, 0));
395
+ }
396
+ else if (e.key === 'Enter') {
397
+ e.preventDefault();
398
+ filtered[clamped]?.run();
399
+ }
400
+ else if (e.key === 'Escape') {
401
+ onClose();
402
+ }
403
+ } }), _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: () => {
404
+ setSel(i);
405
+ }, onClick: it.run, children: [_jsx("span", { children: it.label }), _jsx("span", { className: "toil-dt-pal-kind", children: it.kind })] }, `${it.kind}:${it.label}`)))] })] }) }));
406
+ }
407
+ function PrefsTab() {
408
+ const p = usePrefs();
409
+ const [flags, setFlags] = useState({ viewTransitions: false, transitions: false });
410
+ const toggle = (key) => {
411
+ const next = !flags[key];
412
+ setFlags((f) => ({ ...f, [key]: next }));
413
+ if (key === 'viewTransitions')
414
+ setViewTransitions(next);
415
+ else
416
+ setTransitions(next);
417
+ };
418
+ 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: () => {
419
+ toggle('viewTransitions');
420
+ }, children: flags.viewTransitions ? 'on' : 'off' })] }), _jsxs("div", { className: "toil-dt-sw", children: [_jsx("span", { children: "Loader transition" }), _jsx("button", { className: "toil-dt-btn", onClick: () => {
421
+ toggle('transitions');
422
+ }, children: flags.transitions ? 'on' : 'off' })] }), _jsxs("div", { className: "toil-dt-sw", children: [_jsx("span", { children: "Toolbar side" }), _jsx("button", { className: "toil-dt-btn", onClick: () => {
423
+ setPrefs({ side: p.side === 'left' ? 'right' : 'left' });
424
+ }, children: p.side })] })] }));
425
+ }
426
+ const TABS = [
427
+ { id: 'route', label: 'Route' },
428
+ { id: 'data', label: 'Data' },
429
+ { id: 'head', label: 'Head' },
430
+ { id: 'build', label: 'Build' },
431
+ { id: 'errors', label: 'Errors' },
432
+ { id: 'ai', label: 'AI' },
433
+ { id: 'prefs', label: 'Prefs' },
434
+ ];
435
+ export function DevToolbar({ routes, slots, }) {
436
+ const p = usePrefs();
437
+ const pending = usePending();
438
+ const errors = useErrors();
439
+ const [info, setInfo] = useState(null);
440
+ const [palette, setPalette] = useState(false);
441
+ useEffect(() => {
442
+ injectStyles();
443
+ void fetch('/__toil/devinfo')
444
+ .then((r) => (r.ok ? r.json() : null))
445
+ .then((data) => {
446
+ if (data)
447
+ setInfo(data);
448
+ })
449
+ .catch(() => undefined);
450
+ }, []);
451
+ useEffect(() => {
452
+ const onKey = (e) => {
453
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
454
+ e.preventDefault();
455
+ setPalette((v) => !v);
456
+ }
457
+ };
458
+ window.addEventListener('keydown', onKey);
459
+ return () => {
460
+ window.removeEventListener('keydown', onKey);
461
+ };
462
+ }, []);
463
+ if (info && !info.enabled)
464
+ return null;
465
+ const dotClass = errors.length > 0 ? 'error' : pending ? 'pending' : '';
466
+ const pal = palette ? (_jsx(Palette, { routes: routes, onClose: () => {
467
+ setPalette(false);
468
+ } })) : null;
469
+ if (!p.open) {
470
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: `toil-dt ${p.side}`, children: _jsxs("div", { className: "toil-dt-badge", onClick: () => {
471
+ setPrefs({ open: true });
472
+ }, title: "toiljs devtools (cmd+K)", children: [_jsx(ToilLogo, { size: 16 }), _jsx("span", { className: `toil-dt-dot ${dotClass}` })] }) }), pal] }));
473
+ }
474
+ 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: () => {
475
+ setPrefs({ open: false });
476
+ }, children: "\u2715" })] }), _jsx("div", { className: "toil-dt-tabs", children: TABS.map((t) => (_jsxs("button", { className: `toil-dt-tab ${p.tab === t.id ? 'active' : ''}`, onClick: () => {
477
+ setPrefs({ tab: t.id });
478
+ }, 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, routes: routes }), p.tab === 'prefs' && _jsx(PrefsTab, {})] }) }), pal] }));
479
+ }
@@ -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(() => {
@@ -0,0 +1 @@
1
+ export declare function parseError(err: unknown): string;
@@ -0,0 +1,3 @@
1
+ export function parseError(err) {
2
+ return err instanceof Error ? err.message : String(err);
3
+ }
@@ -34,3 +34,5 @@ export { Form } from './components/Form.js';
34
34
  export type { FormProps } from './components/Form.js';
35
35
  export { Slot } from './components/Slot.js';
36
36
  export type { SlotProps } from './components/Slot.js';
37
+ export { Server } from './rpc.js';
38
+ export { parseError } from './errors.js';
@@ -17,3 +17,5 @@ export { Image } from './components/Image.js';
17
17
  export { Script } from './components/Script.js';
18
18
  export { Form } from './components/Form.js';
19
19
  export { Slot } from './components/Slot.js';
20
+ export { Server } from './rpc.js';
21
+ export { parseError } from './errors.js';
@@ -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 });