effect-inspect 0.1.0
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/README.md +122 -0
- package/app/dist/client/assets/index-smV05cfr.js +25 -0
- package/app/dist/client/assets/rolldown-runtime-CbXtAM7H.js +1 -0
- package/app/dist/client/assets/routes-TKgeFdSW.js +5 -0
- package/app/dist/client/assets/styles-B3rAZGvS.css +2 -0
- package/app/dist/server/assets/_tanstack-start-manifest_v-Co953HeC.js +20 -0
- package/app/dist/server/assets/empty-plugin-adapters-D9UWiqvJ.js +5 -0
- package/app/dist/server/assets/router-CN98Ramo.js +491 -0
- package/app/dist/server/assets/routes-eZ4XqxE9.js +3999 -0
- package/app/dist/server/assets/start-5Z2QO8AU.js +4 -0
- package/app/dist/server/server.js +1812 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +29 -0
- package/dist/client/Client.d.ts +52 -0
- package/dist/client/Client.js +224 -0
- package/dist/client/Edge.d.ts +31 -0
- package/dist/client/Edge.js +108 -0
- package/dist/client/Inspect.d.ts +49 -0
- package/dist/client/Inspect.js +55 -0
- package/dist/client/Tracer.d.ts +31 -0
- package/dist/client/Tracer.js +119 -0
- package/dist/collector/Config.d.ts +8 -0
- package/dist/collector/Config.js +9 -0
- package/dist/collector/Server.d.ts +24 -0
- package/dist/collector/Server.js +172 -0
- package/dist/collector/Store.d.ts +86 -0
- package/dist/collector/Store.js +119 -0
- package/dist/collector/WebApp.d.ts +3 -0
- package/dist/collector/WebApp.js +36 -0
- package/dist/collector/main.d.ts +1 -0
- package/dist/collector/main.js +22 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/protocol/Codec.d.ts +575 -0
- package/dist/protocol/Codec.js +50 -0
- package/dist/protocol/Schema.d.ts +1237 -0
- package/dist/protocol/Schema.js +327 -0
- package/package.json +85 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import { useContext, useEffect, useSyncExternalStore } from "react";
|
|
2
|
+
import { HeadContent, Link, Outlet, Scripts, createFileRoute, createRootRoute, createRouter, lazyRouteComponent } from "@tanstack/react-router";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { ChevronLeft, ChevronRight, FileQuestion } from "lucide-react";
|
|
5
|
+
import { RegistryContext, RegistryProvider, useAtom } from "@effect/atom-react";
|
|
6
|
+
import { Atom } from "effect/unstable/reactivity";
|
|
7
|
+
import { cva } from "class-variance-authority";
|
|
8
|
+
import { clsx } from "clsx";
|
|
9
|
+
import { twMerge } from "tailwind-merge";
|
|
10
|
+
//#region \0rolldown/runtime.js
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __exportAll = (all, no_symbols) => {
|
|
13
|
+
let target = {};
|
|
14
|
+
for (var name in all) __defProp(target, name, {
|
|
15
|
+
get: all[name],
|
|
16
|
+
enumerable: true
|
|
17
|
+
});
|
|
18
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
19
|
+
return target;
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region app/src/state/panels.ts
|
|
23
|
+
/**
|
|
24
|
+
* Whether the sessions sidebar and the span detail panel are collapsed.
|
|
25
|
+
*
|
|
26
|
+
* Both panels are *chrome around* the chart, not the chart itself, so which of
|
|
27
|
+
* them a user wants open is a durable preference rather than per-visit state —
|
|
28
|
+
* it lives in `localStorage` alongside the theme.
|
|
29
|
+
*
|
|
30
|
+
* The hydration constraint is the same one {@link module:state/theme} has, and
|
|
31
|
+
* so is the answer: the server cannot read `localStorage`, so these atoms are
|
|
32
|
+
* seeded with the *default* and adopt storage in an effect. Unlike the theme
|
|
33
|
+
* there is no boot script and no flash to avoid, because a collapsed panel is
|
|
34
|
+
* a layout the user sees and re-renders once — not a colour that would flash.
|
|
35
|
+
*
|
|
36
|
+
* See `usePanel` in {@link module:components/Panel} for the read side, which
|
|
37
|
+
* defers to the server's value until hydration commits.
|
|
38
|
+
*/
|
|
39
|
+
/** `localStorage` key. One record, so a panel added later costs no migration. */
|
|
40
|
+
var PANELS_STORAGE_KEY = "effect-inspect:panels";
|
|
41
|
+
/** Both panels open — what the server renders, and a first visit gets. */
|
|
42
|
+
var DEFAULT_PANELS = {
|
|
43
|
+
sessions: false,
|
|
44
|
+
detail: false
|
|
45
|
+
};
|
|
46
|
+
/** Reads stored collapse state, ignoring anything that is not our shape. */
|
|
47
|
+
var stored = () => {
|
|
48
|
+
if (typeof localStorage === "undefined") return DEFAULT_PANELS;
|
|
49
|
+
try {
|
|
50
|
+
const raw = JSON.parse(localStorage.getItem("effect-inspect:panels") ?? "null");
|
|
51
|
+
if (typeof raw !== "object" || raw === null) return DEFAULT_PANELS;
|
|
52
|
+
const value = raw;
|
|
53
|
+
return {
|
|
54
|
+
sessions: value.sessions === true,
|
|
55
|
+
detail: value.detail === true
|
|
56
|
+
};
|
|
57
|
+
} catch {
|
|
58
|
+
return DEFAULT_PANELS;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/** The live collapse state. Seeded with the default; see the module comment. */
|
|
62
|
+
var panelsAtom = Atom.make(DEFAULT_PANELS);
|
|
63
|
+
/**
|
|
64
|
+
* Adopts the stored collapse state and persists every change after it.
|
|
65
|
+
*
|
|
66
|
+
* Started once from the root, for the same reason the theme sync is: there is
|
|
67
|
+
* one `localStorage` entry, so there is one owner of it.
|
|
68
|
+
*/
|
|
69
|
+
var startPanelSync = (registry) => {
|
|
70
|
+
const off = registry.subscribe(panelsAtom, (panels) => {
|
|
71
|
+
try {
|
|
72
|
+
localStorage.setItem(PANELS_STORAGE_KEY, JSON.stringify(panels));
|
|
73
|
+
} catch {}
|
|
74
|
+
});
|
|
75
|
+
const saved = stored();
|
|
76
|
+
const current = registry.get(panelsAtom);
|
|
77
|
+
if (saved.sessions !== current.sessions || saved.detail !== current.detail) registry.set(panelsAtom, saved);
|
|
78
|
+
return off;
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region app/src/lib/utils.ts
|
|
82
|
+
function cn(...inputs) {
|
|
83
|
+
return twMerge(clsx(inputs));
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region app/src/components/atoms/Button.tsx
|
|
87
|
+
/**
|
|
88
|
+
* Beautiful UI's `button` atom, installed from
|
|
89
|
+
* `https://www.beautifului.dev/r/button.json`.
|
|
90
|
+
*
|
|
91
|
+
* Adapted to repo convention only: relative import with an explicit extension,
|
|
92
|
+
* `readonly` props, and no `"use client"` (nothing in this app is an RSC).
|
|
93
|
+
* The variant table itself is the registry's, unedited — it is the file's whole
|
|
94
|
+
* reason for existing.
|
|
95
|
+
*/
|
|
96
|
+
var filledShadow = "shadow-[inset_0_1px_0_rgba(255,255,255,0.14)]";
|
|
97
|
+
var buttonVariants = cva(`inline-flex items-center justify-center font-medium select-none
|
|
98
|
+
transition-[transform,background-color,opacity] duration-150 ease-out
|
|
99
|
+
active:scale-[0.96] disabled:opacity-50 disabled:pointer-events-none`, {
|
|
100
|
+
variants: {
|
|
101
|
+
variant: {
|
|
102
|
+
primary: `bg-ink text-canvas hover:opacity-90 dark:bg-ink dark:text-canvas ${filledShadow}`,
|
|
103
|
+
secondary: "bg-surface text-ink shadow-btn hover:bg-inset aria-expanded:bg-hover",
|
|
104
|
+
ghost: "bg-hover-2 text-ink hover:bg-line-strong",
|
|
105
|
+
accent: `bg-accent text-white hover:bg-accent-ink ${filledShadow}`,
|
|
106
|
+
success: `bg-green text-white hover:brightness-95 ${filledShadow}`,
|
|
107
|
+
quiet: "text-ink hover:bg-hover"
|
|
108
|
+
},
|
|
109
|
+
size: {
|
|
110
|
+
xs: "h-7 rounded-full px-2.5 text-[12px] font-normal leading-none gap-1",
|
|
111
|
+
sm: "h-[27px] px-3 text-[13px] leading-none rounded-full gap-1.5",
|
|
112
|
+
md: "px-4 py-[9px] text-sm leading-none rounded-full gap-2"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
defaultVariants: {
|
|
116
|
+
variant: "secondary",
|
|
117
|
+
size: "md"
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
var Button = ({ variant, size, className, ...props }) => /* @__PURE__ */ jsx("button", {
|
|
121
|
+
className: cn(buttonVariants({
|
|
122
|
+
variant,
|
|
123
|
+
size
|
|
124
|
+
}), className),
|
|
125
|
+
...props
|
|
126
|
+
});
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region app/src/components/Panel.tsx
|
|
129
|
+
/**
|
|
130
|
+
* The shared furniture the side panels are built from: the collapse hook, the
|
|
131
|
+
* rail a collapsed panel leaves behind, and the designed empty state.
|
|
132
|
+
*
|
|
133
|
+
* Both side panels are the same object seen from opposite edges — a titled
|
|
134
|
+
* surface that can fold to a rail — so the collapse affordance, its width and
|
|
135
|
+
* its label orientation live here once rather than being written twice with a
|
|
136
|
+
* left/right difference in each.
|
|
137
|
+
*/
|
|
138
|
+
/** No-op subscribe: {@link useHydrated} never changes after the first commit. */
|
|
139
|
+
var noSubscribe = () => () => {};
|
|
140
|
+
/**
|
|
141
|
+
* `false` during server render and while hydrating, `true` afterwards.
|
|
142
|
+
*
|
|
143
|
+
* Same mechanism, and the same reason, as the copy in `ThemeToggle`: the
|
|
144
|
+
* stored panel state is adopted in an effect that can land before a lazily
|
|
145
|
+
* loaded component hydrates, so reading the atom directly would render markup
|
|
146
|
+
* that disagrees with what React is hydrating against.
|
|
147
|
+
*/
|
|
148
|
+
var useHydrated = () => useSyncExternalStore(noSubscribe, () => true, () => false);
|
|
149
|
+
/**
|
|
150
|
+
* One panel's collapsed flag and a toggle for it.
|
|
151
|
+
*
|
|
152
|
+
* Returns the *server's* value until hydration commits, so the first client
|
|
153
|
+
* render always matches the markup; the stored preference arrives one commit
|
|
154
|
+
* later, which for a layout is a single re-render rather than a flash.
|
|
155
|
+
*/
|
|
156
|
+
var usePanel = (key) => {
|
|
157
|
+
const [panels, setPanels] = useAtom(panelsAtom);
|
|
158
|
+
return [useHydrated() ? panels[key] : DEFAULT_PANELS[key], () => setPanels({
|
|
159
|
+
...panels,
|
|
160
|
+
[key]: !panels[key]
|
|
161
|
+
})];
|
|
162
|
+
};
|
|
163
|
+
/** Which way the chevron points, per edge and state. */
|
|
164
|
+
var chevron = (edge, collapsed) => {
|
|
165
|
+
return (edge === "left" ? collapsed : !collapsed) ? ChevronRight : ChevronLeft;
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* The collapse control. Quiet until hovered, like every other chrome button.
|
|
169
|
+
*
|
|
170
|
+
* `aria-expanded` and `aria-controls` rather than a changing label, so the
|
|
171
|
+
* button's accessible name stays stable while its state is announced — the
|
|
172
|
+
* same shape the drawer's collapse button already uses.
|
|
173
|
+
*/
|
|
174
|
+
var CollapseButton = ({ edge, collapsed, onToggle, label, controls }) => {
|
|
175
|
+
const Icon = chevron(edge, collapsed);
|
|
176
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
177
|
+
type: "button",
|
|
178
|
+
variant: "quiet",
|
|
179
|
+
size: "xs",
|
|
180
|
+
onClick: onToggle,
|
|
181
|
+
"aria-expanded": !collapsed,
|
|
182
|
+
"aria-controls": controls,
|
|
183
|
+
"aria-label": label,
|
|
184
|
+
title: label,
|
|
185
|
+
className: "size-6 shrink-0 px-0 text-ink-3 hover:text-ink",
|
|
186
|
+
children: /* @__PURE__ */ jsx(Icon, { className: "size-3.5" })
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* A collapsed panel, as a narrow rail carrying its own name and re-open control.
|
|
191
|
+
*
|
|
192
|
+
* A rail rather than nothing at all: a panel that vanishes entirely leaves no
|
|
193
|
+
* clue that it existed, and the user who collapsed it last week has to
|
|
194
|
+
* rediscover the feature. 28px is the button plus the panel's border.
|
|
195
|
+
*/
|
|
196
|
+
var CollapsedRail = ({ edge, title, onToggle, id }) => /* @__PURE__ */ jsxs("aside", {
|
|
197
|
+
id,
|
|
198
|
+
className: `flex w-7 shrink-0 flex-col items-center gap-2 bg-surface py-1.5 ${edge === "left" ? "border-r" : "border-l"} border-line`,
|
|
199
|
+
children: [/* @__PURE__ */ jsx(CollapseButton, {
|
|
200
|
+
edge,
|
|
201
|
+
collapsed: true,
|
|
202
|
+
onToggle,
|
|
203
|
+
label: `Show ${title.toLowerCase()}`,
|
|
204
|
+
controls: id
|
|
205
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
206
|
+
className: "text-[10px] tracking-wider whitespace-nowrap text-ink-3 uppercase",
|
|
207
|
+
style: {
|
|
208
|
+
writingMode: "vertical-rl",
|
|
209
|
+
transform: edge === "left" ? "rotate(180deg)" : void 0
|
|
210
|
+
},
|
|
211
|
+
children: title
|
|
212
|
+
})]
|
|
213
|
+
});
|
|
214
|
+
/**
|
|
215
|
+
* The panel header: a section label, and whatever control belongs beside it.
|
|
216
|
+
*
|
|
217
|
+
* Fixed 28px content height so the sidebar's header, the detail panel's header
|
|
218
|
+
* and the drawer's tab bar all land on the same baseline grid.
|
|
219
|
+
*/
|
|
220
|
+
var PanelHeader = ({ title, children }) => /* @__PURE__ */ jsxs("div", {
|
|
221
|
+
className: "flex h-8 shrink-0 items-center gap-2 border-b border-line px-2",
|
|
222
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
223
|
+
className: "min-w-0 flex-1 truncate text-[10px] tracking-wider text-ink-3 uppercase",
|
|
224
|
+
children: title
|
|
225
|
+
}), children]
|
|
226
|
+
});
|
|
227
|
+
/**
|
|
228
|
+
* The designed empty state: an icon, what is missing, and the action that fixes it.
|
|
229
|
+
*
|
|
230
|
+
* Three lines rather than one sentence, because "Select a span." tells a new
|
|
231
|
+
* user what is absent but not what to do about it. The icon is what makes the
|
|
232
|
+
* block read as a deliberate state rather than as a failed render, and the
|
|
233
|
+
* hint carries the verb.
|
|
234
|
+
*/
|
|
235
|
+
var Empty = ({ icon: Icon, title, hint, className = "" }) => /* @__PURE__ */ jsxs("div", {
|
|
236
|
+
className: `flex flex-col items-center justify-center gap-2 px-4 py-8 text-center ${className}`,
|
|
237
|
+
children: [
|
|
238
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
239
|
+
className: "size-5 text-ink-3",
|
|
240
|
+
strokeWidth: 1.5,
|
|
241
|
+
"aria-hidden": true
|
|
242
|
+
}),
|
|
243
|
+
/* @__PURE__ */ jsx("p", {
|
|
244
|
+
className: "text-xs text-ink-2",
|
|
245
|
+
children: title
|
|
246
|
+
}),
|
|
247
|
+
/* @__PURE__ */ jsx("p", {
|
|
248
|
+
className: "max-w-56 text-[11px] leading-relaxed text-balance text-ink-3",
|
|
249
|
+
children: hint
|
|
250
|
+
})
|
|
251
|
+
]
|
|
252
|
+
});
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region app/src/state/theme.ts
|
|
255
|
+
/**
|
|
256
|
+
* Theme preference, and the one place that owns the `dark` class.
|
|
257
|
+
*
|
|
258
|
+
* Three moving parts, and the split between them is the whole design:
|
|
259
|
+
*
|
|
260
|
+
* - **The preference** (`light | dark | system`) is what the user chose. It
|
|
261
|
+
* lives in an atom and in `localStorage`.
|
|
262
|
+
* - **The resolution** (`light | dark`) is what that preference means right
|
|
263
|
+
* now, which for `system` depends on `prefers-color-scheme` and can change
|
|
264
|
+
* under a stationary preference.
|
|
265
|
+
* - **The class on `<html>`** is what CSS and the canvas actually read.
|
|
266
|
+
*
|
|
267
|
+
* The class is applied *imperatively*, not by React rendering it. TanStack
|
|
268
|
+
* Start renders the document on the server, where `localStorage` and
|
|
269
|
+
* `prefers-color-scheme` are both unavailable, so a React-rendered class can
|
|
270
|
+
* only ever be the default — and correcting it in an effect is a flash. See
|
|
271
|
+
* {@link THEME_BOOT_SCRIPT}, which runs before first paint.
|
|
272
|
+
*/
|
|
273
|
+
/** `localStorage` key. Shared with {@link THEME_BOOT_SCRIPT}, which is a string. */
|
|
274
|
+
var THEME_STORAGE_KEY = "effect-inspect:theme";
|
|
275
|
+
/** Dark is the default, per the spec, and is what the server renders. */
|
|
276
|
+
var DEFAULT_THEME = "dark";
|
|
277
|
+
var isTheme = (value) => value === "light" || value === "dark" || value === "system";
|
|
278
|
+
/**
|
|
279
|
+
* Blocking inline script for `<head>`.
|
|
280
|
+
*
|
|
281
|
+
* This is the no-flash mechanism. The server always renders `class="dark"`
|
|
282
|
+
* (the default), so a user whose stored preference is `light` would otherwise
|
|
283
|
+
* see a dark frame before hydration corrects it. Running synchronously in
|
|
284
|
+
* `<head>`, before the body paints, means the correction happens in the same
|
|
285
|
+
* frame as the first paint and there is nothing to see.
|
|
286
|
+
*
|
|
287
|
+
* It is deliberately duplicated logic rather than an import: it must run before
|
|
288
|
+
* any module graph is fetched, so it cannot be a module. It is small and both
|
|
289
|
+
* copies read the same {@link THEME_STORAGE_KEY}.
|
|
290
|
+
*
|
|
291
|
+
* `color-scheme` is set alongside the class so form controls, scrollbars and
|
|
292
|
+
* the canvas's own backdrop match before any stylesheet has applied.
|
|
293
|
+
*/
|
|
294
|
+
var THEME_BOOT_SCRIPT = `(function(){try{
|
|
295
|
+
var s=localStorage.getItem(${JSON.stringify(THEME_STORAGE_KEY)});
|
|
296
|
+
var t=(s==='light'||s==='dark'||s==='system')?s:${JSON.stringify(DEFAULT_THEME)};
|
|
297
|
+
var d=t==='dark'||(t==='system'&&window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
298
|
+
document.documentElement.classList.toggle('dark',d);
|
|
299
|
+
document.documentElement.style.colorScheme=d?'dark':'light';
|
|
300
|
+
}catch(e){}})()`;
|
|
301
|
+
/** Reads the stored preference. Returns the default on the server or bad data. */
|
|
302
|
+
var storedTheme = () => {
|
|
303
|
+
if (typeof localStorage === "undefined") return DEFAULT_THEME;
|
|
304
|
+
try {
|
|
305
|
+
const stored = localStorage.getItem(THEME_STORAGE_KEY);
|
|
306
|
+
return isTheme(stored) ? stored : DEFAULT_THEME;
|
|
307
|
+
} catch {
|
|
308
|
+
return DEFAULT_THEME;
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
/**
|
|
312
|
+
* The user's preference.
|
|
313
|
+
*
|
|
314
|
+
* Seeded with {@link DEFAULT_THEME} rather than with the stored value, even
|
|
315
|
+
* though the stored value is readable on the client. React hydrates against
|
|
316
|
+
* the *server's* markup, so a toggle that rendered `light` as checked on the
|
|
317
|
+
* first client pass would mismatch a server that rendered `dark` — a real
|
|
318
|
+
* hydration error, not a cosmetic one. The stored preference is adopted a beat
|
|
319
|
+
* later by {@link startThemeSync}, in an effect, where a divergence is a normal
|
|
320
|
+
* state update instead.
|
|
321
|
+
*
|
|
322
|
+
* This costs nothing visually: the *class* on `<html>` is already correct by
|
|
323
|
+
* then — {@link THEME_BOOT_SCRIPT} set it before the first paint — so the only
|
|
324
|
+
* thing catching up is which of three radios is checked.
|
|
325
|
+
*/
|
|
326
|
+
var themeAtom = Atom.make(DEFAULT_THEME);
|
|
327
|
+
/**
|
|
328
|
+
* What the preference resolves to right now.
|
|
329
|
+
*
|
|
330
|
+
* A separate atom rather than a derived read because it has a second input the
|
|
331
|
+
* preference does not see: the OS setting can change while `system` is
|
|
332
|
+
* selected. {@link startThemeSync} owns writing it.
|
|
333
|
+
*/
|
|
334
|
+
var resolvedThemeAtom = Atom.make(typeof document === "undefined" ? DEFAULT_THEME : currentResolved());
|
|
335
|
+
/** Resolves a preference against the OS setting. */
|
|
336
|
+
var resolve = (theme) => {
|
|
337
|
+
if (theme !== "system") return theme;
|
|
338
|
+
if (typeof matchMedia === "undefined") return DEFAULT_THEME;
|
|
339
|
+
return matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
340
|
+
};
|
|
341
|
+
/** What the DOM is showing, read from the class the boot script set. */
|
|
342
|
+
function currentResolved() {
|
|
343
|
+
return document.documentElement.classList.contains("dark") ? "dark" : "light";
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Keeps `<html>`, `localStorage` and {@link resolvedThemeAtom} in step with the
|
|
347
|
+
* preference, for as long as the returned function is not called.
|
|
348
|
+
*
|
|
349
|
+
* Started once from the root component rather than from a hook per consumer:
|
|
350
|
+
* there is exactly one `<html>` element, so there is exactly one owner of its
|
|
351
|
+
* class. Everything else — the toggle, the canvas — reads atoms.
|
|
352
|
+
*/
|
|
353
|
+
var startThemeSync = (registry) => {
|
|
354
|
+
const apply = (theme) => {
|
|
355
|
+
const resolved = resolve(theme);
|
|
356
|
+
const root = document.documentElement;
|
|
357
|
+
if (root.classList.contains("dark") !== (resolved === "dark")) {
|
|
358
|
+
root.classList.add("theme-switching");
|
|
359
|
+
root.classList.toggle("dark", resolved === "dark");
|
|
360
|
+
requestAnimationFrame(() => root.classList.remove("theme-switching"));
|
|
361
|
+
}
|
|
362
|
+
root.style.colorScheme = resolved;
|
|
363
|
+
if (registry.get(resolvedThemeAtom) !== resolved) registry.set(resolvedThemeAtom, resolved);
|
|
364
|
+
};
|
|
365
|
+
const offAtom = registry.subscribe(themeAtom, (theme) => {
|
|
366
|
+
try {
|
|
367
|
+
localStorage.setItem(THEME_STORAGE_KEY, theme);
|
|
368
|
+
} catch {}
|
|
369
|
+
apply(theme);
|
|
370
|
+
});
|
|
371
|
+
const stored = storedTheme();
|
|
372
|
+
if (stored === registry.get(themeAtom)) apply(stored);
|
|
373
|
+
else registry.set(themeAtom, stored);
|
|
374
|
+
const query = matchMedia("(prefers-color-scheme: dark)");
|
|
375
|
+
const onSystemChange = () => apply(registry.get(themeAtom));
|
|
376
|
+
query.addEventListener("change", onSystemChange);
|
|
377
|
+
return () => {
|
|
378
|
+
offAtom();
|
|
379
|
+
query.removeEventListener("change", onSystemChange);
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region app/src/styles.css?url
|
|
384
|
+
var styles_default = "/assets/styles-B3rAZGvS.css";
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region app/src/routes/__root.tsx
|
|
387
|
+
var Route$1 = createRootRoute({
|
|
388
|
+
head: () => ({
|
|
389
|
+
meta: [
|
|
390
|
+
{ charSet: "utf-8" },
|
|
391
|
+
{
|
|
392
|
+
name: "viewport",
|
|
393
|
+
content: "width=device-width, initial-scale=1"
|
|
394
|
+
},
|
|
395
|
+
{ title: "effect-inspect" }
|
|
396
|
+
],
|
|
397
|
+
links: [{
|
|
398
|
+
rel: "stylesheet",
|
|
399
|
+
href: styles_default
|
|
400
|
+
}]
|
|
401
|
+
}),
|
|
402
|
+
component: RootComponent
|
|
403
|
+
});
|
|
404
|
+
function RootComponent() {
|
|
405
|
+
return /* @__PURE__ */ jsx(RootDocument, { children: /* @__PURE__ */ jsxs(RegistryProvider, { children: [
|
|
406
|
+
/* @__PURE__ */ jsx(ThemeSync, {}),
|
|
407
|
+
/* @__PURE__ */ jsx(PanelSync, {}),
|
|
408
|
+
/* @__PURE__ */ jsx(Outlet, {})
|
|
409
|
+
] }) });
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Owns the `dark` class on `<html>` for the life of the app.
|
|
413
|
+
*
|
|
414
|
+
* Renders nothing: the theme is not React state that something displays, it is
|
|
415
|
+
* a property of the document. One component starts the sync, everything else —
|
|
416
|
+
* the toggle, the flame chart — reads atoms.
|
|
417
|
+
*/
|
|
418
|
+
function ThemeSync() {
|
|
419
|
+
const registry = useContext(RegistryContext);
|
|
420
|
+
useEffect(() => startThemeSync(registry), [registry]);
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Adopts and persists the side panels' collapse state.
|
|
425
|
+
*
|
|
426
|
+
* Separate from `ThemeSync` because they own different storage keys and have
|
|
427
|
+
* different failure modes: the theme has a boot script and a flash to avoid,
|
|
428
|
+
* the panels have neither.
|
|
429
|
+
*/
|
|
430
|
+
function PanelSync() {
|
|
431
|
+
const registry = useContext(RegistryContext);
|
|
432
|
+
useEffect(() => startPanelSync(registry), [registry]);
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
function RootDocument({ children }) {
|
|
436
|
+
return /* @__PURE__ */ jsxs("html", {
|
|
437
|
+
lang: "en",
|
|
438
|
+
className: "dark",
|
|
439
|
+
suppressHydrationWarning: true,
|
|
440
|
+
children: [/* @__PURE__ */ jsxs("head", { children: [/* @__PURE__ */ jsx(HeadContent, {}), /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: THEME_BOOT_SCRIPT } })] }), /* @__PURE__ */ jsxs("body", { children: [children, /* @__PURE__ */ jsx(Scripts, {})] })]
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
//#endregion
|
|
444
|
+
//#region app/src/routes/index.tsx
|
|
445
|
+
var $$splitComponentImporter = () => import("./routes-eZ4XqxE9.js");
|
|
446
|
+
//#endregion
|
|
447
|
+
//#region app/src/routeTree.gen.ts
|
|
448
|
+
var rootRouteChildren = { IndexRoute: createFileRoute("/")({ component: lazyRouteComponent($$splitComponentImporter, "component") }).update({
|
|
449
|
+
id: "/",
|
|
450
|
+
path: "/",
|
|
451
|
+
getParentRoute: () => Route$1
|
|
452
|
+
}) };
|
|
453
|
+
var routeTree = Route$1._addFileChildren(rootRouteChildren)._addFileTypes();
|
|
454
|
+
//#endregion
|
|
455
|
+
//#region app/src/router.tsx
|
|
456
|
+
var router_exports = /* @__PURE__ */ __exportAll({ getRouter: () => getRouter });
|
|
457
|
+
/**
|
|
458
|
+
* Shown for any URL the route tree does not match.
|
|
459
|
+
*
|
|
460
|
+
* Configured at the router rather than per route because the app is a
|
|
461
|
+
* single-page inspector: `/` is the only real route, so *every* miss is the
|
|
462
|
+
* same miss, and a `notFoundComponent` on each route would be the same
|
|
463
|
+
* component repeated. Without this, TanStack falls back to a bare
|
|
464
|
+
* `<p>Not Found</p>` that ignores the theme entirely — which is what a stray
|
|
465
|
+
* request (a probe for `/favicon.ico`, a stale bookmark) would otherwise render.
|
|
466
|
+
*/
|
|
467
|
+
var NotFound = () => /* @__PURE__ */ jsx("div", {
|
|
468
|
+
className: "flex h-screen flex-col items-center justify-center bg-page font-mono text-ink antialiased",
|
|
469
|
+
children: /* @__PURE__ */ jsx(Empty, {
|
|
470
|
+
icon: FileQuestion,
|
|
471
|
+
title: "No such page",
|
|
472
|
+
hint: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
473
|
+
"effect-inspect is a single page. Head back to",
|
|
474
|
+
" ",
|
|
475
|
+
/* @__PURE__ */ jsx(Link, {
|
|
476
|
+
to: "/",
|
|
477
|
+
className: "text-accent underline underline-offset-2",
|
|
478
|
+
children: "the inspector"
|
|
479
|
+
}),
|
|
480
|
+
"."
|
|
481
|
+
] })
|
|
482
|
+
})
|
|
483
|
+
});
|
|
484
|
+
/** TanStack Start calls this per request to build the router. */
|
|
485
|
+
var getRouter = () => createRouter({
|
|
486
|
+
routeTree,
|
|
487
|
+
scrollRestoration: true,
|
|
488
|
+
defaultNotFoundComponent: NotFound
|
|
489
|
+
});
|
|
490
|
+
//#endregion
|
|
491
|
+
export { CollapseButton as a, PanelHeader as c, getRouter, themeAtom as i, usePanel as l, DEFAULT_THEME as n, CollapsedRail as o, resolvedThemeAtom as r, Empty as s, router_exports as t, Button as u };
|