@oddsquad/vite-plugin-lit 0.3.0 → 0.4.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/CHANGELOG.md +48 -0
- package/README.md +10 -6
- package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-BvWe4sTJ.js → browser-agent-rpc-BXhoSh1z-CdtPhBL-.js} +2 -2
- package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-BvWe4sTJ.js.map → browser-agent-rpc-BXhoSh1z-CdtPhBL-.js.map} +1 -1
- package/dist/client/assets/flow-DK8vmxvN.js +745 -0
- package/dist/client/assets/flow-DK8vmxvN.js.map +1 -0
- package/dist/client/assets/{index-CFrVgNfS.js → index-DVe6M4HV.js} +976 -102
- package/dist/client/assets/index-DVe6M4HV.js.map +1 -0
- package/dist/client/index.html +1 -1
- package/lib/devframe/protocol.d.ts.map +1 -1
- package/lib/devframe/protocol.js +1 -0
- package/lib/devframe/protocol.js.map +1 -1
- package/lib/devframe/vite.d.ts.map +1 -1
- package/lib/devframe/vite.js +1 -0
- package/lib/devframe/vite.js.map +1 -1
- package/lib/options.d.ts +131 -0
- package/lib/options.d.ts.map +1 -0
- package/lib/options.js +128 -0
- package/lib/options.js.map +1 -0
- package/lib/plugin.d.ts +6 -148
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +14 -477
- package/lib/plugin.js.map +1 -1
- package/lib/plugins/css-literals.d.ts +15 -0
- package/lib/plugins/css-literals.d.ts.map +1 -0
- package/lib/plugins/css-literals.js +154 -0
- package/lib/plugins/css-literals.js.map +1 -0
- package/lib/plugins/css-queries.d.ts +20 -0
- package/lib/plugins/css-queries.d.ts.map +1 -0
- package/lib/plugins/css-queries.js +139 -0
- package/lib/plugins/css-queries.js.map +1 -0
- package/lib/plugins/open-in-editor.d.ts +16 -0
- package/lib/plugins/open-in-editor.d.ts.map +1 -0
- package/lib/plugins/open-in-editor.js +91 -0
- package/lib/plugins/open-in-editor.js.map +1 -0
- package/lib/plugins/shared.d.ts +13 -0
- package/lib/plugins/shared.d.ts.map +1 -0
- package/lib/plugins/shared.js +23 -0
- package/lib/plugins/shared.js.map +1 -0
- package/lib/plugins/timeline-virtual.d.ts +19 -0
- package/lib/plugins/timeline-virtual.d.ts.map +1 -0
- package/lib/plugins/timeline-virtual.js +53 -0
- package/lib/plugins/timeline-virtual.js.map +1 -0
- package/lib/runtime/css.d.ts.map +1 -1
- package/lib/runtime/css.js +9 -2
- package/lib/runtime/css.js.map +1 -1
- package/lib/runtime/inspector/install.js +61 -11
- package/lib/runtime/inspector/install.js.map +1 -1
- package/lib/runtime/source-overlay/overlay-element.d.ts.map +1 -1
- package/lib/runtime/source-overlay/overlay-element.js +19 -6
- package/lib/runtime/source-overlay/overlay-element.js.map +1 -1
- package/lib/runtime/timeline/install.js +10 -5
- package/lib/runtime/timeline/install.js.map +1 -1
- package/lib/runtime/timeline/public-api.d.ts +2 -2
- package/lib/runtime/timeline/public-api.js +2 -2
- package/lib/runtime/timeline/public-api.js.map +1 -1
- package/lib/runtime/timeline/render.d.ts +15 -6
- package/lib/runtime/timeline/render.d.ts.map +1 -1
- package/lib/runtime/timeline/render.js +150 -11
- package/lib/runtime/timeline/render.js.map +1 -1
- package/package.json +4 -1
- package/types/timeline.d.ts +12 -1
- package/types/timeline.d.ts.map +1 -1
- package/types/timeline.js +2 -0
- package/types/timeline.js.map +1 -1
- package/dist/client/assets/index-CFrVgNfS.js.map +0 -1
- package/lib/timeline-plugin.d.ts +0 -72
- package/lib/timeline-plugin.d.ts.map +0 -1
- package/lib/timeline-plugin.js +0 -359
- package/lib/timeline-plugin.js.map +0 -1
package/lib/timeline-plugin.d.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
-
*/
|
|
6
|
-
import type { Plugin } from 'vite';
|
|
7
|
-
import { type FeatureSettings } from '../types/timeline.js';
|
|
8
|
-
interface DockEntry {
|
|
9
|
-
id: string;
|
|
10
|
-
type: 'iframe';
|
|
11
|
-
title: string;
|
|
12
|
-
icon: string;
|
|
13
|
-
url: string;
|
|
14
|
-
category?: string;
|
|
15
|
-
}
|
|
16
|
-
interface RpcFn {
|
|
17
|
-
name: string;
|
|
18
|
-
type?: string;
|
|
19
|
-
handler?: (...args: unknown[]) => unknown;
|
|
20
|
-
}
|
|
21
|
-
/** Subset of @devframes/hub's command-input we register. */
|
|
22
|
-
interface CommandInput {
|
|
23
|
-
id: string;
|
|
24
|
-
title: string;
|
|
25
|
-
description?: string;
|
|
26
|
-
icon?: string;
|
|
27
|
-
category?: string;
|
|
28
|
-
keybindings?: Array<{
|
|
29
|
-
key: string;
|
|
30
|
-
}>;
|
|
31
|
-
handler?: (...args: unknown[]) => unknown;
|
|
32
|
-
}
|
|
33
|
-
interface TimelineCtx {
|
|
34
|
-
docks: {
|
|
35
|
-
register: (entry: DockEntry) => unknown;
|
|
36
|
-
};
|
|
37
|
-
commands?: {
|
|
38
|
-
register: (command: CommandInput) => unknown;
|
|
39
|
-
};
|
|
40
|
-
rpc: {
|
|
41
|
-
register: (fn: RpcFn) => void;
|
|
42
|
-
functions?: Record<string, (...args: unknown[]) => unknown>;
|
|
43
|
-
};
|
|
44
|
-
views: {
|
|
45
|
-
hostStatic: (baseUrl: string, distDir: string) => void;
|
|
46
|
-
};
|
|
47
|
-
viteServer?: {
|
|
48
|
-
middlewares: {
|
|
49
|
-
use: (path: string, handler: (req: {
|
|
50
|
-
url?: string;
|
|
51
|
-
}, res: {
|
|
52
|
-
statusCode: number;
|
|
53
|
-
setHeader: (k: string, v: string) => void;
|
|
54
|
-
end: (body: string) => void;
|
|
55
|
-
}, next: () => void) => void) => void;
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
viteConfig?: {
|
|
59
|
-
root: string;
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
declare module 'vite' {
|
|
63
|
-
interface Plugin {
|
|
64
|
-
devtools?: {
|
|
65
|
-
capabilities?: unknown;
|
|
66
|
-
setup: (ctx: TimelineCtx) => void | Promise<void>;
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
export declare const litTimelinePlugin: (getSettings?: () => FeatureSettings | undefined) => Plugin;
|
|
71
|
-
export {};
|
|
72
|
-
//# sourceMappingURL=timeline-plugin.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-plugin.d.ts","sourceRoot":"","sources":["../src/lib/timeline-plugin.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAC,MAAM,EAAgB,MAAM,MAAM,CAAC;AAEhD,OAAO,EAEL,KAAK,eAAe,EAIrB,MAAM,sBAAsB,CAAC;AAkB9B,UAAU,SAAS;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;CAC3C;AAED,4DAA4D;AAC5D,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;CAC3C;AAED,UAAU,WAAW;IACnB,KAAK,EAAE;QAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAA;KAAC,CAAC;IACjD,QAAQ,CAAC,EAAE;QAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAA;KAAC,CAAC;IAC1D,GAAG,EAAE;QACH,QAAQ,EAAE,CAAC,EAAE,EAAE,KAAK,KAAK,IAAI,CAAC;QAC9B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC;KAC7D,CAAC;IACF,KAAK,EAAE;QAAC,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;KAAC,CAAC;IAChE,UAAU,CAAC,EAAE;QACX,WAAW,EAAE;YACX,GAAG,EAAE,CACH,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CACP,GAAG,EAAE;gBAAC,GAAG,CAAC,EAAE,MAAM,CAAA;aAAC,EACnB,GAAG,EAAE;gBACH,UAAU,EAAE,MAAM,CAAC;gBACnB,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;gBAC1C,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;aAC7B,EACD,IAAI,EAAE,MAAM,IAAI,KACb,IAAI,KACN,IAAI,CAAC;SACX,CAAC;KACH,CAAC;IACF,UAAU,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,CAAC;CAC7B;AAID,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAU,MAAM;QACd,QAAQ,CAAC,EAAE;YACT,YAAY,CAAC,EAAE,OAAO,CAAC;YACvB,KAAK,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;SACnD,CAAC;KACH;CACF;AA0ID,eAAO,MAAM,iBAAiB,iBACd,MAAM,eAAe,GAAG,SAAS,KAC9C,MAoTF,CAAC"}
|
package/lib/timeline-plugin.js
DELETED
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
-
*/
|
|
6
|
-
import {existsSync} from 'node:fs';
|
|
7
|
-
import {readFile} from 'node:fs/promises';
|
|
8
|
-
import {join} from 'node:path';
|
|
9
|
-
import {fileURLToPath} from 'node:url';
|
|
10
|
-
import {isTrustedRequest} from './http.js';
|
|
11
|
-
import {SETTINGS_OVERRIDE_CHANNEL} from '../types/timeline.js';
|
|
12
|
-
import {
|
|
13
|
-
INSPECT_CMD_CHANNEL,
|
|
14
|
-
INSPECT_DATA_CHANNEL,
|
|
15
|
-
INSPECT_OVERLAY_TOGGLE_CHANNEL,
|
|
16
|
-
INSPECT_PATH,
|
|
17
|
-
INSPECT_SSE_EVENT,
|
|
18
|
-
} from '../types/inspector.js';
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// SSE push channel — streams batches of TimelineEvents from the server to
|
|
21
|
-
// the panel iframe. The panel subscribes to /__lit-devtools-events using the
|
|
22
|
-
// native EventSource API; the browser runtime pushes events via Vite HMR.
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
const PANEL_PATH = '/__lit-devtools';
|
|
25
|
-
const SSE_PATH = '/__lit-devtools-events';
|
|
26
|
-
const CONTROL_PATH = '/__lit-devtools-control';
|
|
27
|
-
/** Read-only feature settings consumed by the panel's Settings tab. */
|
|
28
|
-
const SETTINGS_PATH = '/__lit-devtools-settings';
|
|
29
|
-
// The official Lit logo mark (Iconify `logos:lit-icon`). Its native viewBox is
|
|
30
|
-
// 256×320 — taller than wide — so when the DevTools dock sizes an icon to its
|
|
31
|
-
// width it overflowed the square slot and looked bigger than the other icons.
|
|
32
|
-
// We pad it to a square viewBox (-32 0 320 320, centering the 256-wide art) so
|
|
33
|
-
// it renders at the same box size as the built-in phosphor icons. Inlined as a
|
|
34
|
-
// data: URI rather than `logos:lit-icon` so it also works offline (no Iconify
|
|
35
|
-
// API fetch).
|
|
36
|
-
const LIT_LOGO_SVG =
|
|
37
|
-
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 0 320 320">' +
|
|
38
|
-
'<path fill="#00e8ff" d="m64 192l25.926-44.727l38.233-19.114l63.974 63.974l10.833 61.754L192 320l-64-64l-38.074-25.615z"/>' +
|
|
39
|
-
'<path fill="#283198" d="M128 256V128l64-64v128zM0 256l64 64l9.202-60.602L64 192l-37.542 23.71z"/>' +
|
|
40
|
-
'<path fill="#324fff" d="M64 192V64l64-64v128zm128 128V192l64-64v128zM0 256V128l64 64z"/>' +
|
|
41
|
-
'<path fill="#0ff" d="M64 320V192l64 64z"/></svg>';
|
|
42
|
-
const LIT_LOGO_ICON = `data:image/svg+xml,${encodeURIComponent(LIT_LOGO_SVG)}`;
|
|
43
|
-
/**
|
|
44
|
-
* Resolves the panel files, checking the compiled output first (`panel/`) then
|
|
45
|
-
* the TypeScript source (`src/panel/`). In dev, the source `.ts` file is
|
|
46
|
-
* served via Vite's /@fs/ handler so TypeScript is compiled on the fly.
|
|
47
|
-
*/
|
|
48
|
-
const resolvePanel = () => {
|
|
49
|
-
for (const [htmlRel, appRel] of [
|
|
50
|
-
['../panel/index.html', '../panel/lit-devtools-panel.js'],
|
|
51
|
-
['../src/panel/index.html', '../src/panel/lit-devtools-panel.ts'],
|
|
52
|
-
]) {
|
|
53
|
-
const htmlUrl = new URL(htmlRel, import.meta.url);
|
|
54
|
-
if (existsSync(htmlUrl)) {
|
|
55
|
-
const htmlDir = fileURLToPath(new URL('.', htmlUrl));
|
|
56
|
-
const appModule = fileURLToPath(new URL(appRel, import.meta.url));
|
|
57
|
-
return {htmlDir, appModule};
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
throw new Error('[lit-plugin:timeline] panel directory not found');
|
|
61
|
-
};
|
|
62
|
-
/**
|
|
63
|
-
* Reads and JSON-parses a request body, invoking `done` with the parsed object
|
|
64
|
-
* (or `null` on a malformed/empty body). Used by the control + settings POST
|
|
65
|
-
* endpoints.
|
|
66
|
-
*/
|
|
67
|
-
const readJsonBody = (req, done) => {
|
|
68
|
-
const chunks = [];
|
|
69
|
-
req.on?.('data', (chunk) => chunks.push(chunk));
|
|
70
|
-
req.on?.('end', () => {
|
|
71
|
-
try {
|
|
72
|
-
done(JSON.parse(Buffer.concat(chunks).toString()));
|
|
73
|
-
} catch {
|
|
74
|
-
done(null);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
const installSseMiddleware = (server, clients, onConnect) => {
|
|
79
|
-
server.middlewares.use(SSE_PATH, (req, res, next) => {
|
|
80
|
-
if (req.method !== 'GET') {
|
|
81
|
-
next();
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
if (!isTrustedRequest(req.headers ?? {})) {
|
|
85
|
-
res.statusCode = 403;
|
|
86
|
-
res.end?.('forbidden');
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
res.statusCode = 200;
|
|
90
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
91
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
92
|
-
res.setHeader('Connection', 'keep-alive');
|
|
93
|
-
res.flushHeaders?.();
|
|
94
|
-
res.write(':\n\n'); // keep-alive comment
|
|
95
|
-
clients.add(res);
|
|
96
|
-
res.on('close', () => clients.delete(res));
|
|
97
|
-
// Replay a just-emitted overlay pick to a panel that connects right after
|
|
98
|
-
// it (the cold-start case: picking an element opens the dock, but the
|
|
99
|
-
// panel iframe subscribes here only after the `pick` was already
|
|
100
|
-
// broadcast to zero clients). See `pendingPick` in `litTimelinePlugin`.
|
|
101
|
-
onConnect?.(res);
|
|
102
|
-
});
|
|
103
|
-
};
|
|
104
|
-
/** How long a pending overlay pick stays replayable to a freshly-connected
|
|
105
|
-
* panel. Long enough to cover the dock iframe mounting + subscribing, short
|
|
106
|
-
* enough that an unrelated later reconnect won't resurrect a stale selection. */
|
|
107
|
-
const PICK_REPLAY_WINDOW_MS = 3000;
|
|
108
|
-
export const litTimelinePlugin = (getSettings) => {
|
|
109
|
-
const sseClients = new Set();
|
|
110
|
-
// Captured in configureServer so the (server-side) DevTools command handler
|
|
111
|
-
// can broadcast to the app runtime; it only runs after the server is up.
|
|
112
|
-
let devServer;
|
|
113
|
-
// The most recent overlay `pick`, framed as an SSE payload, kept briefly so a
|
|
114
|
-
// panel that mounts in response to the pick (cold start) still receives it.
|
|
115
|
-
let pendingPick = null;
|
|
116
|
-
/** Write a pre-framed SSE payload to every client, pruning dead sockets. */
|
|
117
|
-
const broadcast = (payload) => {
|
|
118
|
-
for (const client of sseClients) {
|
|
119
|
-
try {
|
|
120
|
-
client.write(payload);
|
|
121
|
-
} catch {
|
|
122
|
-
sseClients.delete(client);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
/** Push a batch of events to all subscribed panel SSE clients. */
|
|
127
|
-
const pushToPanel = (events) => {
|
|
128
|
-
if (sseClients.size === 0 || events.length === 0) return;
|
|
129
|
-
broadcast(`data: ${JSON.stringify(events)}\n\n`);
|
|
130
|
-
};
|
|
131
|
-
/** Push a named SSE event (the panel listens via `addEventListener(name)`). */
|
|
132
|
-
const pushEvent = (name, data) => {
|
|
133
|
-
if (sseClients.size === 0) return;
|
|
134
|
-
broadcast(`event: ${name}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
135
|
-
};
|
|
136
|
-
return {
|
|
137
|
-
name: 'lit-devtools',
|
|
138
|
-
apply: 'serve',
|
|
139
|
-
configureServer(server) {
|
|
140
|
-
devServer = server;
|
|
141
|
-
// SSE endpoint: panel iframe subscribes here for event push. A client that
|
|
142
|
-
// connects within the replay window gets the pending pick replayed.
|
|
143
|
-
installSseMiddleware(server, sseClients, (client) => {
|
|
144
|
-
if (pendingPick === null) return;
|
|
145
|
-
if (Date.now() >= pendingPick.expires) {
|
|
146
|
-
pendingPick = null;
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
// Replay to every client connecting within the window, not just the
|
|
150
|
-
// first: the panel opens two SSE subscriptions (components-view and
|
|
151
|
-
// timeline-view) and only components-view acts on `pick`. A one-shot
|
|
152
|
-
// replay could land on timeline-view and be dropped. Replaying to all
|
|
153
|
-
// is safe — timeline-view ignores the event, and components-view's
|
|
154
|
-
// selection is idempotent. The pick clears on expiry, below.
|
|
155
|
-
try {
|
|
156
|
-
client.write(pendingPick.payload);
|
|
157
|
-
} catch {
|
|
158
|
-
// Socket already gone — nothing to replay to.
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
// Settings endpoint. GET returns the resolved feature settings (the
|
|
162
|
-
// baseline the panel renders). POST carries a SettingsOverride which we
|
|
163
|
-
// rebroadcast to the app runtime via HMR so it takes effect live.
|
|
164
|
-
server.middlewares.use(SETTINGS_PATH, (req, res, next) => {
|
|
165
|
-
if (!isTrustedRequest(req.headers ?? {})) {
|
|
166
|
-
res.statusCode = 403;
|
|
167
|
-
res.end('forbidden');
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
if (req.method === 'GET') {
|
|
171
|
-
res.statusCode = 200;
|
|
172
|
-
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
173
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
174
|
-
res.end(JSON.stringify(getSettings?.() ?? null));
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
if (req.method === 'POST') {
|
|
178
|
-
readJsonBody(req, (body) => {
|
|
179
|
-
if (body !== null) {
|
|
180
|
-
server.hot.send(SETTINGS_OVERRIDE_CHANNEL, body);
|
|
181
|
-
}
|
|
182
|
-
res.statusCode = 204;
|
|
183
|
-
res.end();
|
|
184
|
-
});
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
next();
|
|
188
|
-
});
|
|
189
|
-
// Serve the panel HTML with the Lit SPA entry injected via /@fs/ so
|
|
190
|
-
// Vite can transform the TypeScript source on the fly.
|
|
191
|
-
let panel;
|
|
192
|
-
try {
|
|
193
|
-
panel = resolvePanel();
|
|
194
|
-
} catch (e) {
|
|
195
|
-
console.warn(String(e));
|
|
196
|
-
}
|
|
197
|
-
if (panel !== undefined) {
|
|
198
|
-
const {htmlDir, appModule} = panel;
|
|
199
|
-
server.middlewares.use(PANEL_PATH, async (req, res, next) => {
|
|
200
|
-
const url = req.url ?? '/';
|
|
201
|
-
if (url !== '/' && url !== '' && url !== '/index.html') {
|
|
202
|
-
next();
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
let html;
|
|
206
|
-
try {
|
|
207
|
-
html = await readFile(join(htmlDir, 'index.html'), 'utf-8');
|
|
208
|
-
} catch {
|
|
209
|
-
next();
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
// Inject the panel entry as a /@fs/ module so Vite's transform
|
|
213
|
-
// pipeline compiles TypeScript and resolves bare specifiers (lit, etc).
|
|
214
|
-
const scriptTag = ` <script type="module" src="/@fs${appModule}"></script>\n`;
|
|
215
|
-
html = html.replace('</body>', scriptTag + '</body>');
|
|
216
|
-
res.statusCode = 200;
|
|
217
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
218
|
-
res.end(html);
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
// Control endpoint: the panel POSTs recording/layer state changes here.
|
|
222
|
-
// The server rebroadcasts the state to all connected app tabs via HMR.
|
|
223
|
-
server.middlewares.use(CONTROL_PATH, (req, res, next) => {
|
|
224
|
-
const method = req.method;
|
|
225
|
-
if (method !== 'POST') {
|
|
226
|
-
next();
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
const r403 = res;
|
|
230
|
-
if (!isTrustedRequest(req.headers ?? {})) {
|
|
231
|
-
r403.statusCode = 403;
|
|
232
|
-
r403.end('forbidden');
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
readJsonBody(req, (body) => {
|
|
236
|
-
if (body !== null) {
|
|
237
|
-
if (typeof body.recording === 'boolean') {
|
|
238
|
-
server.hot.send('lit:timeline:recording-changed', {
|
|
239
|
-
recording: body.recording,
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
const layerKeys = [
|
|
243
|
-
'litLifecycleEnabled',
|
|
244
|
-
'litRenderEnabled',
|
|
245
|
-
'mouseEventEnabled',
|
|
246
|
-
'keyboardEventEnabled',
|
|
247
|
-
];
|
|
248
|
-
const layersUpdate = {};
|
|
249
|
-
for (const key of layerKeys) {
|
|
250
|
-
if (typeof body[key] === 'boolean') {
|
|
251
|
-
layersUpdate[key] = body[key];
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
if (Object.keys(layersUpdate).length > 0) {
|
|
255
|
-
server.hot.send('lit:timeline:layers-changed', layersUpdate);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
const r = res;
|
|
259
|
-
r.statusCode = 204;
|
|
260
|
-
r.end();
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
// Accept batches of events from the browser runtime via Vite HMR.
|
|
264
|
-
server.hot.on('lit:timeline:push-event', (data) => {
|
|
265
|
-
pushToPanel(data.events ?? []);
|
|
266
|
-
});
|
|
267
|
-
// Legacy: panel postMessage → devtools shell → server (keep for compat).
|
|
268
|
-
server.hot.on('lit:timeline:set-recording', (data) => {
|
|
269
|
-
server.hot.send('lit:timeline:recording-changed', {
|
|
270
|
-
recording: data.recording,
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
// Custom layer announced by app code via addTimelineLayer().
|
|
274
|
-
// Forward to the panel as a named SSE event so it can add the layer.
|
|
275
|
-
server.hot.on('lit:timeline:custom-layer', (data) => {
|
|
276
|
-
if (!data.layer?.id) return;
|
|
277
|
-
pushEvent('layer', data.layer);
|
|
278
|
-
});
|
|
279
|
-
// Components inspector. The panel POSTs an InspectorCommand here; we
|
|
280
|
-
// forward it to the app runtime over HMR. The runtime answers over
|
|
281
|
-
// INSPECT_DATA_CHANNEL, which we relay to the panel as a named SSE event.
|
|
282
|
-
server.middlewares.use(INSPECT_PATH, (req, res, next) => {
|
|
283
|
-
const method = req.method;
|
|
284
|
-
if (method !== 'POST') {
|
|
285
|
-
next();
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
const r403 = res;
|
|
289
|
-
if (!isTrustedRequest(req.headers ?? {})) {
|
|
290
|
-
r403.statusCode = 403;
|
|
291
|
-
r403.end('forbidden');
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
readJsonBody(req, (body) => {
|
|
295
|
-
if (body !== null) {
|
|
296
|
-
// "Pick" starts the overlay's inspect picker (a server-side toggle),
|
|
297
|
-
// not a runtime query, so it's routed to the overlay channel.
|
|
298
|
-
if (body.type === 'pick') {
|
|
299
|
-
server.hot.send(INSPECT_OVERLAY_TOGGLE_CHANNEL);
|
|
300
|
-
} else {
|
|
301
|
-
server.hot.send(INSPECT_CMD_CHANNEL, body);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
const r = res;
|
|
305
|
-
r.statusCode = 204;
|
|
306
|
-
r.end();
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
server.hot.on(INSPECT_DATA_CHANNEL, (data) => {
|
|
310
|
-
// Remember overlay picks so a cold-started panel (opened by the pick
|
|
311
|
-
// itself) can replay it on connect — see `pendingPick`.
|
|
312
|
-
if (data.type === 'pick') {
|
|
313
|
-
pendingPick = {
|
|
314
|
-
payload: `event: ${INSPECT_SSE_EVENT}\ndata: ${JSON.stringify(data)}\n\n`,
|
|
315
|
-
expires: Date.now() + PICK_REPLAY_WINDOW_MS,
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
pushEvent(INSPECT_SSE_EVENT, data);
|
|
319
|
-
});
|
|
320
|
-
},
|
|
321
|
-
devtools: {
|
|
322
|
-
setup(ctx) {
|
|
323
|
-
// Register the dock entry. HTML is served by the configureServer
|
|
324
|
-
// middleware above (with /@fs/ injection); hostStatic is not used.
|
|
325
|
-
ctx.docks.register({
|
|
326
|
-
id: 'lit-devtools',
|
|
327
|
-
type: 'iframe',
|
|
328
|
-
// This dock represents the whole Lit plugin (HMR, source overlay,
|
|
329
|
-
// timeline), so it's branded "Lit" rather than the timeline panel
|
|
330
|
-
// it currently opens.
|
|
331
|
-
title: 'Lit',
|
|
332
|
-
// Square, inlined Lit logo — see LIT_LOGO_ICON above. DevTools renders
|
|
333
|
-
// a `data:` icon directly as an <img>, no Iconify API fetch.
|
|
334
|
-
icon: LIT_LOGO_ICON,
|
|
335
|
-
url: PANEL_PATH + '/',
|
|
336
|
-
category: 'framework',
|
|
337
|
-
});
|
|
338
|
-
// Register the overlay toggle as a DevTools command so it shows in the
|
|
339
|
-
// command palette and as a managed shortcut. The handler runs
|
|
340
|
-
// server-side; it broadcasts to the app runtime, which toggles the
|
|
341
|
-
// overlay. Only meaningful when the overlay is enabled.
|
|
342
|
-
const so = getSettings?.()?.sourceOverlay;
|
|
343
|
-
if (so?.enabled && ctx.commands?.register) {
|
|
344
|
-
ctx.commands.register({
|
|
345
|
-
id: 'lit:overlay:toggle',
|
|
346
|
-
title: 'Pick Lit Element',
|
|
347
|
-
description:
|
|
348
|
-
'Click to inspect in the Components panel, hold Meta/Ctrl and click to open in your editor',
|
|
349
|
-
icon: 'ph:crosshair-duotone',
|
|
350
|
-
category: 'Lit',
|
|
351
|
-
keybindings: [{key: 'Meta+Shift+E'}],
|
|
352
|
-
handler: () => devServer?.hot.send(INSPECT_OVERLAY_TOGGLE_CHANNEL),
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
},
|
|
356
|
-
},
|
|
357
|
-
};
|
|
358
|
-
};
|
|
359
|
-
//# sourceMappingURL=timeline-plugin.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-plugin.js","sourceRoot":"","sources":["../src/lib/timeline-plugin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license\n * Copyright 2026 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\nimport {existsSync} from 'node:fs';\nimport {readFile} from 'node:fs/promises';\nimport {join} from 'node:path';\nimport {fileURLToPath} from 'node:url';\nimport type {Plugin, ViteDevServer} from 'vite';\nimport {isTrustedRequest, type TrustHeaders} from './http.js';\nimport {\n SETTINGS_OVERRIDE_CHANNEL,\n type FeatureSettings,\n type SettingsOverride,\n type TimelineEvent,\n type TimelineLayer,\n} from '../types/timeline.js';\nimport {\n INSPECT_CMD_CHANNEL,\n INSPECT_DATA_CHANNEL,\n INSPECT_OVERLAY_TOGGLE_CHANNEL,\n INSPECT_PATH,\n INSPECT_SSE_EVENT,\n type InspectorCommand,\n type InspectorMessage,\n} from '../types/inspector.js';\n\n// ---------------------------------------------------------------------------\n// Minimal type shims for the @vitejs/devtools-kit surface we use.\n// These are structurally compatible with the real types; we duck-type against\n// the context object passed by @vitejs/devtools' devtools.setup hook.\n// Install @vitejs/devtools-kit as a peer dep to get the real declarations.\n// ---------------------------------------------------------------------------\n\ninterface DockEntry {\n id: string;\n type: 'iframe';\n title: string;\n icon: string;\n url: string;\n category?: string;\n}\n\ninterface RpcFn {\n name: string;\n type?: string;\n handler?: (...args: unknown[]) => unknown;\n}\n\n/** Subset of @devframes/hub's command-input we register. */\ninterface CommandInput {\n id: string;\n title: string;\n description?: string;\n icon?: string;\n category?: string;\n keybindings?: Array<{key: string}>;\n handler?: (...args: unknown[]) => unknown;\n}\n\ninterface TimelineCtx {\n docks: {register: (entry: DockEntry) => unknown};\n commands?: {register: (command: CommandInput) => unknown};\n rpc: {\n register: (fn: RpcFn) => void;\n functions?: Record<string, (...args: unknown[]) => unknown>;\n };\n views: {hostStatic: (baseUrl: string, distDir: string) => void};\n viteServer?: {\n middlewares: {\n use: (\n path: string,\n handler: (\n req: {url?: string},\n res: {\n statusCode: number;\n setHeader: (k: string, v: string) => void;\n end: (body: string) => void;\n },\n next: () => void\n ) => void\n ) => void;\n };\n };\n viteConfig?: {root: string};\n}\n\n// Augment Vite's Plugin interface to accept the devtools hook without\n// requiring @vitejs/devtools-kit as a direct dep of this package.\ndeclare module 'vite' {\n interface Plugin {\n devtools?: {\n capabilities?: unknown;\n setup: (ctx: TimelineCtx) => void | Promise<void>;\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// SSE push channel — streams batches of TimelineEvents from the server to\n// the panel iframe. The panel subscribes to /__lit-devtools-events using the\n// native EventSource API; the browser runtime pushes events via Vite HMR.\n// ---------------------------------------------------------------------------\n\nconst PANEL_PATH = '/__lit-devtools';\nconst SSE_PATH = '/__lit-devtools-events';\nconst CONTROL_PATH = '/__lit-devtools-control';\n/** Read-only feature settings consumed by the panel's Settings tab. */\nconst SETTINGS_PATH = '/__lit-devtools-settings';\n\n// The official Lit logo mark (Iconify `logos:lit-icon`). Its native viewBox is\n// 256×320 — taller than wide — so when the DevTools dock sizes an icon to its\n// width it overflowed the square slot and looked bigger than the other icons.\n// We pad it to a square viewBox (-32 0 320 320, centering the 256-wide art) so\n// it renders at the same box size as the built-in phosphor icons. Inlined as a\n// data: URI rather than `logos:lit-icon` so it also works offline (no Iconify\n// API fetch).\nconst LIT_LOGO_SVG =\n '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"-32 0 320 320\">' +\n '<path fill=\"#00e8ff\" d=\"m64 192l25.926-44.727l38.233-19.114l63.974 63.974l10.833 61.754L192 320l-64-64l-38.074-25.615z\"/>' +\n '<path fill=\"#283198\" d=\"M128 256V128l64-64v128zM0 256l64 64l9.202-60.602L64 192l-37.542 23.71z\"/>' +\n '<path fill=\"#324fff\" d=\"M64 192V64l64-64v128zm128 128V192l64-64v128zM0 256V128l64 64z\"/>' +\n '<path fill=\"#0ff\" d=\"M64 320V192l64 64z\"/></svg>';\nconst LIT_LOGO_ICON = `data:image/svg+xml,${encodeURIComponent(LIT_LOGO_SVG)}`;\n\ninterface PanelPaths {\n /** Directory that contains index.html. */\n htmlDir: string;\n /** Absolute path to the panel entry module (.ts in dev, .js in published). */\n appModule: string;\n}\n\n/**\n * Resolves the panel files, checking the compiled output first (`panel/`) then\n * the TypeScript source (`src/panel/`). In dev, the source `.ts` file is\n * served via Vite's /@fs/ handler so TypeScript is compiled on the fly.\n */\nconst resolvePanel = (): PanelPaths => {\n for (const [htmlRel, appRel] of [\n ['../panel/index.html', '../panel/lit-devtools-panel.js'],\n ['../src/panel/index.html', '../src/panel/lit-devtools-panel.ts'],\n ] as const) {\n const htmlUrl = new URL(htmlRel, import.meta.url);\n if (existsSync(htmlUrl)) {\n const htmlDir = fileURLToPath(new URL('.', htmlUrl));\n const appModule = fileURLToPath(new URL(appRel, import.meta.url));\n return {htmlDir, appModule};\n }\n }\n throw new Error('[lit-plugin:timeline] panel directory not found');\n};\n\n/** Subset of Node's readable-stream `on` used to collect a request body. */\ntype ReadableOn = (event: string, handler: (chunk: Buffer) => void) => void;\n\n/**\n * Reads and JSON-parses a request body, invoking `done` with the parsed object\n * (or `null` on a malformed/empty body). Used by the control + settings POST\n * endpoints.\n */\nconst readJsonBody = (\n req: {on?: ReadableOn},\n done: (body: Record<string, unknown> | null) => void\n): void => {\n const chunks: Buffer[] = [];\n req.on?.('data', (chunk) => chunks.push(chunk));\n req.on?.('end', () => {\n try {\n done(\n JSON.parse(Buffer.concat(chunks).toString()) as Record<string, unknown>\n );\n } catch {\n done(null);\n }\n });\n};\n\n/** Minimal typing for Node's ServerResponse (already fully typed by `node:http`\n * but we want to avoid pulling in @types/node in a browser runtime module). */\ntype SseClient = {\n write: (chunk: string) => boolean;\n on: (event: string, listener: () => void) => void;\n socket?: {destroyed?: boolean} | null;\n};\n\nconst installSseMiddleware = (\n server: ViteDevServer,\n clients: Set<SseClient>,\n onConnect?: (client: SseClient) => void\n): void => {\n server.middlewares.use(\n SSE_PATH,\n (\n req: {method?: string; headers?: TrustHeaders},\n res: SseClient & {\n statusCode: number;\n setHeader: (k: string, v: string) => void;\n flushHeaders?: () => void;\n end?: (body?: string) => void;\n },\n next: () => void\n ) => {\n if (req.method !== 'GET') {\n next();\n return;\n }\n if (!isTrustedRequest(req.headers ?? {})) {\n res.statusCode = 403;\n res.end?.('forbidden');\n return;\n }\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.flushHeaders?.();\n res.write(':\\n\\n'); // keep-alive comment\n\n clients.add(res);\n res.on('close', () => clients.delete(res));\n // Replay a just-emitted overlay pick to a panel that connects right after\n // it (the cold-start case: picking an element opens the dock, but the\n // panel iframe subscribes here only after the `pick` was already\n // broadcast to zero clients). See `pendingPick` in `litTimelinePlugin`.\n onConnect?.(res);\n }\n );\n};\n\n/** How long a pending overlay pick stays replayable to a freshly-connected\n * panel. Long enough to cover the dock iframe mounting + subscribing, short\n * enough that an unrelated later reconnect won't resurrect a stale selection. */\nconst PICK_REPLAY_WINDOW_MS = 3000;\n\nexport const litTimelinePlugin = (\n getSettings?: () => FeatureSettings | undefined\n): Plugin => {\n const sseClients = new Set<SseClient>();\n // Captured in configureServer so the (server-side) DevTools command handler\n // can broadcast to the app runtime; it only runs after the server is up.\n let devServer: ViteDevServer | undefined;\n // The most recent overlay `pick`, framed as an SSE payload, kept briefly so a\n // panel that mounts in response to the pick (cold start) still receives it.\n let pendingPick: {payload: string; expires: number} | null = null;\n\n /** Write a pre-framed SSE payload to every client, pruning dead sockets. */\n const broadcast = (payload: string): void => {\n for (const client of sseClients) {\n try {\n client.write(payload);\n } catch {\n sseClients.delete(client);\n }\n }\n };\n\n /** Push a batch of events to all subscribed panel SSE clients. */\n const pushToPanel = (events: TimelineEvent[]): void => {\n if (sseClients.size === 0 || events.length === 0) return;\n broadcast(`data: ${JSON.stringify(events)}\\n\\n`);\n };\n\n /** Push a named SSE event (the panel listens via `addEventListener(name)`). */\n const pushEvent = (name: string, data: unknown): void => {\n if (sseClients.size === 0) return;\n broadcast(`event: ${name}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n };\n\n return {\n name: 'lit-devtools',\n apply: 'serve',\n\n configureServer(server) {\n devServer = server;\n // SSE endpoint: panel iframe subscribes here for event push. A client that\n // connects within the replay window gets the pending pick replayed.\n installSseMiddleware(server, sseClients, (client) => {\n if (pendingPick === null) return;\n if (Date.now() >= pendingPick.expires) {\n pendingPick = null;\n return;\n }\n // Replay to every client connecting within the window, not just the\n // first: the panel opens two SSE subscriptions (components-view and\n // timeline-view) and only components-view acts on `pick`. A one-shot\n // replay could land on timeline-view and be dropped. Replaying to all\n // is safe — timeline-view ignores the event, and components-view's\n // selection is idempotent. The pick clears on expiry, below.\n try {\n client.write(pendingPick.payload);\n } catch {\n // Socket already gone — nothing to replay to.\n }\n });\n\n // Settings endpoint. GET returns the resolved feature settings (the\n // baseline the panel renders). POST carries a SettingsOverride which we\n // rebroadcast to the app runtime via HMR so it takes effect live.\n server.middlewares.use(\n SETTINGS_PATH,\n (\n req: {\n method?: string;\n on?: ReadableOn;\n headers?: TrustHeaders;\n },\n res: {\n statusCode: number;\n setHeader: (k: string, v: string) => void;\n end: (body?: string) => void;\n },\n next: () => void\n ) => {\n if (!isTrustedRequest(req.headers ?? {})) {\n res.statusCode = 403;\n res.end('forbidden');\n return;\n }\n if (req.method === 'GET') {\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json; charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache');\n res.end(JSON.stringify(getSettings?.() ?? null));\n return;\n }\n if (req.method === 'POST') {\n readJsonBody(req, (body) => {\n if (body !== null) {\n server.hot.send(\n SETTINGS_OVERRIDE_CHANNEL,\n body as SettingsOverride\n );\n }\n res.statusCode = 204;\n res.end();\n });\n return;\n }\n next();\n }\n );\n\n // Serve the panel HTML with the Lit SPA entry injected via /@fs/ so\n // Vite can transform the TypeScript source on the fly.\n let panel: PanelPaths | undefined;\n try {\n panel = resolvePanel();\n } catch (e) {\n console.warn(String(e));\n }\n\n if (panel !== undefined) {\n const {htmlDir, appModule} = panel;\n server.middlewares.use(\n PANEL_PATH,\n async (\n req: {url?: string},\n res: {\n statusCode: number;\n setHeader: (k: string, v: string) => void;\n end: (body: string) => void;\n },\n next: () => void\n ) => {\n const url = req.url ?? '/';\n if (url !== '/' && url !== '' && url !== '/index.html') {\n next();\n return;\n }\n let html: string;\n try {\n html = await readFile(join(htmlDir, 'index.html'), 'utf-8');\n } catch {\n next();\n return;\n }\n // Inject the panel entry as a /@fs/ module so Vite's transform\n // pipeline compiles TypeScript and resolves bare specifiers (lit, etc).\n const scriptTag = ` <script type=\"module\" src=\"/@fs${appModule}\"></script>\\n`;\n html = html.replace('</body>', scriptTag + '</body>');\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.end(html);\n }\n );\n }\n\n // Control endpoint: the panel POSTs recording/layer state changes here.\n // The server rebroadcasts the state to all connected app tabs via HMR.\n server.middlewares.use(CONTROL_PATH, (req, res, next) => {\n const method = (req as {method?: string}).method;\n if (method !== 'POST') {\n next();\n return;\n }\n const r403 = res as {statusCode: number; end: (body?: string) => void};\n if (\n !isTrustedRequest((req as {headers?: TrustHeaders}).headers ?? {})\n ) {\n r403.statusCode = 403;\n r403.end('forbidden');\n return;\n }\n readJsonBody(req as {on?: ReadableOn}, (body) => {\n if (body !== null) {\n if (typeof body.recording === 'boolean') {\n server.hot.send('lit:timeline:recording-changed', {\n recording: body.recording,\n });\n }\n const layerKeys = [\n 'litLifecycleEnabled',\n 'litRenderEnabled',\n 'mouseEventEnabled',\n 'keyboardEventEnabled',\n ] as const;\n const layersUpdate: Record<string, boolean> = {};\n for (const key of layerKeys) {\n if (typeof body[key] === 'boolean') {\n layersUpdate[key] = body[key] as boolean;\n }\n }\n if (Object.keys(layersUpdate).length > 0) {\n server.hot.send('lit:timeline:layers-changed', layersUpdate);\n }\n }\n const r = res as {statusCode: number; end: () => void};\n r.statusCode = 204;\n r.end();\n });\n });\n\n // Accept batches of events from the browser runtime via Vite HMR.\n server.hot.on(\n 'lit:timeline:push-event',\n (data: {events?: TimelineEvent[]}) => {\n pushToPanel(data.events ?? []);\n }\n );\n\n // Legacy: panel postMessage → devtools shell → server (keep for compat).\n server.hot.on(\n 'lit:timeline:set-recording',\n (data: {recording: boolean}) => {\n server.hot.send('lit:timeline:recording-changed', {\n recording: data.recording,\n });\n }\n );\n\n // Custom layer announced by app code via addTimelineLayer().\n // Forward to the panel as a named SSE event so it can add the layer.\n server.hot.on(\n 'lit:timeline:custom-layer',\n (data: {layer: TimelineLayer}) => {\n if (!data.layer?.id) return;\n pushEvent('layer', data.layer);\n }\n );\n\n // Components inspector. The panel POSTs an InspectorCommand here; we\n // forward it to the app runtime over HMR. The runtime answers over\n // INSPECT_DATA_CHANNEL, which we relay to the panel as a named SSE event.\n server.middlewares.use(INSPECT_PATH, (req, res, next) => {\n const method = (req as {method?: string}).method;\n if (method !== 'POST') {\n next();\n return;\n }\n const r403 = res as {statusCode: number; end: (body?: string) => void};\n if (\n !isTrustedRequest((req as {headers?: TrustHeaders}).headers ?? {})\n ) {\n r403.statusCode = 403;\n r403.end('forbidden');\n return;\n }\n readJsonBody(req as {on?: ReadableOn}, (body) => {\n if (body !== null) {\n // \"Pick\" starts the overlay's inspect picker (a server-side toggle),\n // not a runtime query, so it's routed to the overlay channel.\n if ((body as InspectorCommand).type === 'pick') {\n server.hot.send(INSPECT_OVERLAY_TOGGLE_CHANNEL);\n } else {\n server.hot.send(INSPECT_CMD_CHANNEL, body as InspectorCommand);\n }\n }\n const r = res as {statusCode: number; end: () => void};\n r.statusCode = 204;\n r.end();\n });\n });\n\n server.hot.on(INSPECT_DATA_CHANNEL, (data: InspectorMessage) => {\n // Remember overlay picks so a cold-started panel (opened by the pick\n // itself) can replay it on connect — see `pendingPick`.\n if (data.type === 'pick') {\n pendingPick = {\n payload: `event: ${INSPECT_SSE_EVENT}\\ndata: ${JSON.stringify(data)}\\n\\n`,\n expires: Date.now() + PICK_REPLAY_WINDOW_MS,\n };\n }\n pushEvent(INSPECT_SSE_EVENT, data);\n });\n },\n\n devtools: {\n setup(ctx: TimelineCtx) {\n // Register the dock entry. HTML is served by the configureServer\n // middleware above (with /@fs/ injection); hostStatic is not used.\n ctx.docks.register({\n id: 'lit-devtools',\n type: 'iframe',\n // This dock represents the whole Lit plugin (HMR, source overlay,\n // timeline), so it's branded \"Lit\" rather than the timeline panel\n // it currently opens.\n title: 'Lit',\n // Square, inlined Lit logo — see LIT_LOGO_ICON above. DevTools renders\n // a `data:` icon directly as an <img>, no Iconify API fetch.\n icon: LIT_LOGO_ICON,\n url: PANEL_PATH + '/',\n category: 'framework',\n });\n\n // Register the overlay toggle as a DevTools command so it shows in the\n // command palette and as a managed shortcut. The handler runs\n // server-side; it broadcasts to the app runtime, which toggles the\n // overlay. Only meaningful when the overlay is enabled.\n const so = getSettings?.()?.sourceOverlay;\n if (so?.enabled && ctx.commands?.register) {\n ctx.commands.register({\n id: 'lit:overlay:toggle',\n title: 'Pick Lit Element',\n description:\n 'Click to inspect in the Components panel, hold Meta/Ctrl and click to open in your editor',\n icon: 'ph:crosshair-duotone',\n category: 'Lit',\n keybindings: [{key: 'Meta+Shift+E'}],\n handler: () => devServer?.hot.send(INSPECT_OVERLAY_TOGGLE_CHANNEL),\n });\n }\n },\n },\n };\n};\n"]}
|