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.
- package/.babelrc +13 -13
- package/.gitattributes +2 -2
- package/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
- package/.github/ISSUE_TEMPLATE/bug_report.yml +90 -90
- package/.github/ISSUE_TEMPLATE/config.yml +8 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
- package/.github/PULL_REQUEST_TEMPLATE.md +43 -43
- package/.github/changelog-config.json +45 -45
- package/.github/dependabot.yml +27 -27
- package/.github/workflows/ci.yml +191 -191
- package/.prettierrc.json +11 -11
- package/.vscode/settings.json +9 -9
- package/CHANGELOG.md +116 -5
- package/LICENSE +187 -187
- package/README.md +524 -315
- package/as-pect.asconfig.json +34 -34
- package/as-pect.config.js +65 -65
- package/assets/logo.svg +36 -36
- package/build/backend/.tsbuildinfo +1 -1
- package/build/backend/index.d.ts +1 -0
- package/build/backend/index.js +20 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +1320 -696
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/dev/devtools.d.ts +6 -0
- package/build/client/dev/devtools.js +479 -0
- package/build/client/dev/error-overlay.d.ts +9 -0
- package/build/client/dev/error-overlay.js +19 -4
- package/build/client/errors.d.ts +1 -0
- package/build/client/errors.js +3 -0
- package/build/client/index.d.ts +2 -0
- package/build/client/index.js +2 -0
- package/build/client/navigation/prefetch.d.ts +1 -0
- package/build/client/navigation/prefetch.js +35 -0
- package/build/client/routing/Router.js +1 -1
- package/build/client/routing/hooks.js +6 -2
- package/build/client/routing/loader.d.ts +23 -0
- package/build/client/routing/loader.js +53 -7
- package/build/client/routing/mount.js +4 -3
- package/build/client/rpc.d.ts +1 -0
- package/build/client/rpc.js +37 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/config.d.ts +16 -0
- package/build/compiler/config.js +9 -0
- package/build/compiler/docs.js +78 -21
- package/build/compiler/generate.js +5 -4
- package/build/compiler/index.d.ts +3 -2
- package/build/compiler/index.js +2 -2
- package/build/compiler/plugin.js +228 -0
- package/build/compiler/prerender.d.ts +1 -0
- package/build/compiler/prerender.js +1 -1
- package/build/compiler/seo.d.ts +1 -1
- package/build/compiler/seo.js +20 -5
- package/build/compiler/ssg.js +39 -2
- package/build/compiler/vite.js +25 -0
- package/build/io/.tsbuildinfo +1 -1
- package/build/io/codec.d.ts +54 -0
- package/build/io/codec.js +143 -0
- package/build/io/index.d.ts +1 -2
- package/build/io/index.js +1 -2
- package/build/logger/.tsbuildinfo +1 -1
- package/build/shared/.tsbuildinfo +1 -1
- package/eslint.config.js +48 -48
- package/examples/basic/client/404.tsx +11 -11
- package/examples/basic/client/components/.gitkeep +1 -1
- package/examples/basic/client/global-error.tsx +13 -13
- package/examples/basic/client/layout.tsx +25 -25
- package/examples/basic/client/public/images/.gitkeep +1 -1
- package/examples/basic/client/public/images/logo.svg +36 -36
- package/examples/basic/client/public/robots.txt +2 -2
- package/examples/basic/client/routes/docs/[...slug].tsx +12 -12
- package/examples/basic/client/routes/features/error/error.tsx +16 -16
- package/examples/basic/client/routes/features/index.tsx +1 -1
- package/examples/basic/client/routes/features/template/b.tsx +14 -14
- package/examples/basic/client/routes/files/[[...slug]].tsx +21 -21
- package/examples/basic/client/routes/gallery/layout.tsx +13 -13
- package/examples/basic/client/routes/io.tsx +23 -24
- package/examples/basic/client/routes/loader-demo/loading.tsx +13 -13
- package/examples/basic/client/routes/rest.tsx +74 -0
- package/examples/basic/client/routes/rpc.tsx +43 -0
- package/examples/basic/client/routes/search.tsx +61 -61
- package/examples/basic/client/toil.tsx +5 -5
- package/package.json +167 -148
- package/presets/eslint.js +88 -88
- package/presets/no-uint8array-tostring.js +200 -200
- package/presets/prettier-plugin.js +51 -0
- package/presets/prettier.json +19 -18
- package/presets/tsconfig.json +37 -37
- package/server/runtime/README.md +97 -0
- package/server/runtime/abort/abort.ts +27 -0
- package/server/runtime/env/Server.ts +61 -0
- package/server/runtime/envelope.ts +191 -0
- package/server/runtime/exports/index.ts +52 -0
- package/server/runtime/handlers/ToilHandler.ts +34 -0
- package/server/runtime/index.ts +26 -0
- package/server/runtime/lang/Potential.ts +5 -0
- package/server/runtime/memory.ts +81 -0
- package/server/runtime/request.ts +55 -0
- package/server/runtime/response.ts +86 -0
- package/server/runtime/rest/Rest.ts +39 -0
- package/server/runtime/rest/RestHandler.ts +20 -0
- package/server/runtime/rest/RouteContext.ts +82 -0
- package/server/runtime/rest/match.ts +48 -0
- package/server/runtime/tsconfig.json +7 -0
- package/src/backend/index.ts +202 -160
- package/src/cli/create.ts +15 -5
- package/src/cli/diagnostics.ts +81 -0
- package/src/cli/doctor.ts +384 -7
- package/src/cli/index.ts +11 -2
- package/src/cli/proc.ts +50 -50
- package/src/cli/updates.ts +69 -69
- package/src/cli/validate.ts +31 -31
- package/src/client/channel/channel.ts +146 -146
- package/src/client/components/Form.tsx +65 -65
- package/src/client/components/Script.tsx +113 -113
- package/src/client/components/Slot.tsx +21 -21
- package/src/client/dev/devtools.tsx +1018 -0
- package/src/client/dev/error-overlay.tsx +30 -4
- package/src/client/errors.ts +11 -0
- package/src/client/head/head.ts +167 -167
- package/src/client/head/metadata.ts +112 -112
- package/src/client/index.ts +91 -89
- package/src/client/navigation/NavLink.tsx +86 -86
- package/src/client/navigation/navigation.ts +235 -235
- package/src/client/navigation/prefetch.ts +169 -130
- package/src/client/navigation/scroll.ts +53 -53
- package/src/client/routing/Router.tsx +8 -2
- package/src/client/routing/action.ts +122 -122
- package/src/client/routing/error-boundary.tsx +43 -43
- package/src/client/routing/hooks.ts +21 -6
- package/src/client/routing/loader.ts +325 -235
- package/src/client/routing/match.ts +47 -47
- package/src/client/routing/mount.tsx +54 -52
- package/src/client/routing/params-context.ts +10 -10
- package/src/client/routing/slot-context.ts +7 -7
- package/src/client/rpc.ts +64 -0
- package/src/client/search/search.ts +189 -189
- package/src/client/search/use-page-search.ts +73 -73
- package/src/client/types.ts +73 -73
- package/src/compiler/config.ts +221 -182
- package/src/compiler/docs.ts +285 -228
- package/src/compiler/generate.ts +395 -394
- package/src/compiler/index.ts +66 -57
- package/src/compiler/pages.ts +70 -70
- package/src/compiler/plugin.ts +258 -2
- package/src/compiler/prerender.ts +156 -156
- package/src/compiler/seo.ts +417 -390
- package/src/compiler/ssg.ts +171 -126
- package/src/compiler/vite.ts +34 -0
- package/src/io/FastMap.ts +151 -127
- package/src/io/FastSet.ts +15 -1
- package/src/io/codec.ts +217 -0
- package/src/io/index.ts +10 -11
- package/src/io/lengths.ts +14 -14
- package/src/io/types.ts +19 -18
- package/src/logger/index.ts +22 -22
- package/src/shared/index.ts +10 -10
- package/std/client/index.d.ts +15 -15
- package/std/client/package.json +3 -3
- package/test/assembly/example.spec.ts +17 -7
- package/test/channel.test.ts +21 -21
- package/test/doctor.test.ts +65 -0
- package/test/dom/Link.test.tsx +47 -47
- package/test/dom/NavLink.test.tsx +37 -37
- package/test/dom/error-overlay.test.tsx +44 -44
- package/test/dom/loader.test.tsx +121 -121
- package/test/dom/navigation.test.ts +59 -59
- package/test/dom/revalidate.test.tsx +38 -38
- package/test/dom/route-head.test.tsx +78 -78
- package/test/dom/router-loading.test.tsx +44 -44
- package/test/dom/scroll.test.ts +56 -56
- package/test/dom/use-metadata.test.tsx +58 -58
- package/test/errors.test.ts +21 -0
- package/test/io.test.ts +117 -93
- package/test/navlink.test.ts +28 -28
- package/test/placeholder.test.ts +9 -9
- package/test/prettier-plugin.test.ts +46 -0
- package/test/routes.test.ts +76 -76
- package/test/rpc.test.ts +50 -0
- package/test/seo.test.ts +175 -164
- package/test/slot-layouts.test.ts +69 -69
- package/test/ssg.test.ts +36 -36
- package/test/update.test.ts +44 -44
- package/test/validate.test.ts +42 -42
- package/tests/data-parity/generated-parity.ts +99 -0
- package/tests/data-parity/parity.ts +80 -0
- package/tests/data-parity/spec.ts +46 -0
- package/toil-routes.d.ts +7 -0
- package/tsconfig.backend.json +13 -13
- package/tsconfig.base.json +35 -35
- package/tsconfig.cli.json +13 -13
- package/tsconfig.client.json +14 -14
- package/tsconfig.compiler.json +13 -13
- package/tsconfig.io.json +12 -12
- package/tsconfig.json +22 -22
- package/tsconfig.logger.json +12 -12
- package/tsconfig.server.json +10 -10
- package/tsconfig.shared.json +12 -12
- package/vitest.config.ts +26 -26
- package/.idea/codeStyles/Project.xml +0 -54
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/prettier.xml +0 -7
- package/.idea/toiljs.iml +0 -8
- package/.idea/vcs.xml +0 -6
- package/.toil/entry.tsx +0 -9
- package/.toil/index.html +0 -12
- package/.toil/routes.ts +0 -9
- package/build/cli/configure.d.ts +0 -16
- package/build/cli/configure.js +0 -272
- package/build/cli/create.d.ts +0 -16
- package/build/cli/create.js +0 -420
- package/build/cli/diagnostics.d.ts +0 -55
- package/build/cli/diagnostics.js +0 -333
- package/build/cli/doctor.d.ts +0 -6
- package/build/cli/doctor.js +0 -249
- package/build/cli/features.d.ts +0 -25
- package/build/cli/features.js +0 -107
- package/build/cli/index.d.ts +0 -2
- package/build/cli/proc.d.ts +0 -6
- package/build/cli/proc.js +0 -31
- package/build/cli/ui.d.ts +0 -9
- package/build/cli/ui.js +0 -75
- package/build/cli/update.d.ts +0 -7
- package/build/cli/update.js +0 -117
- package/build/cli/updates.d.ts +0 -10
- package/build/cli/updates.js +0 -45
- package/build/cli/validate.d.ts +0 -4
- package/build/cli/validate.js +0 -19
- package/build/client/Link.d.ts +0 -8
- package/build/client/Link.js +0 -44
- package/build/client/NavLink.d.ts +0 -14
- package/build/client/NavLink.js +0 -37
- package/build/client/Router.d.ts +0 -7
- package/build/client/Router.js +0 -55
- package/build/client/channel.d.ts +0 -23
- package/build/client/channel.js +0 -94
- package/build/client/error-boundary.d.ts +0 -16
- package/build/client/error-boundary.js +0 -19
- package/build/client/head.d.ts +0 -26
- package/build/client/head.js +0 -87
- package/build/client/hooks.d.ts +0 -17
- package/build/client/hooks.js +0 -48
- package/build/client/lazy.d.ts +0 -16
- package/build/client/lazy.js +0 -53
- package/build/client/match.d.ts +0 -2
- package/build/client/match.js +0 -32
- package/build/client/mount.d.ts +0 -2
- package/build/client/mount.js +0 -13
- package/build/client/navigation.d.ts +0 -13
- package/build/client/navigation.js +0 -97
- package/build/client/params-context.d.ts +0 -2
- package/build/client/params-context.js +0 -2
- package/build/client/prefetch.d.ts +0 -11
- package/build/client/prefetch.js +0 -100
- package/build/client/runtime.d.ts +0 -31
- package/build/client/runtime.js +0 -112
- package/build/client/scroll.d.ts +0 -8
- package/build/client/scroll.js +0 -36
- package/build/io/BinaryReader.d.ts +0 -44
- package/build/io/BinaryReader.js +0 -244
- package/build/io/BinaryWriter.d.ts +0 -44
- package/build/io/BinaryWriter.js +0 -297
- package/build/server/release.wasm +0 -0
- package/build/server/release.wat +0 -9
- package/src/io/BinaryReader.ts +0 -340
- package/src/io/BinaryWriter.ts +0 -385
- package/src/server/index.ts +0 -10
- package/src/server/main.ts +0 -13
- package/src/server/tsconfig.json +0 -4
- package/toil-env.d.ts +0 -16
- 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({
|
|
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;
|
package/build/client/index.d.ts
CHANGED
|
@@ -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';
|
package/build/client/index.js
CHANGED
|
@@ -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';
|
|
@@ -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 });
|