rerender-lens 0.5.0 → 0.7.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +50 -1
  2. package/README.md +23 -4
  3. package/dist/{budget-CRswn-nC.d.cts → budget-CA1MS7lB.d.cts} +1 -1
  4. package/dist/{budget-BnJlYwrV.d.ts → budget-ok1VDJ4t.d.ts} +1 -1
  5. package/dist/cli.cjs +78 -26
  6. package/dist/cli.cjs.map +1 -1
  7. package/dist/cli.js +78 -26
  8. package/dist/cli.js.map +1 -1
  9. package/dist/{devtools-BYWat7nQ.d.ts → devtools-Cl5n_52v.d.ts} +11 -2
  10. package/dist/{devtools-DJi_6AfJ.d.cts → devtools-UwQx2VYS.d.cts} +11 -2
  11. package/dist/index.cjs +99 -9
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +13 -7
  14. package/dist/index.d.ts +13 -7
  15. package/dist/index.js +99 -10
  16. package/dist/index.js.map +1 -1
  17. package/dist/jest-setup.cjs +20 -3
  18. package/dist/jest-setup.cjs.map +1 -1
  19. package/dist/jest-setup.d.cts +4 -4
  20. package/dist/jest-setup.d.ts +4 -4
  21. package/dist/jest-setup.js +20 -3
  22. package/dist/jest-setup.js.map +1 -1
  23. package/dist/jest.cjs +20 -3
  24. package/dist/jest.cjs.map +1 -1
  25. package/dist/jest.d.cts +4 -4
  26. package/dist/jest.d.ts +4 -4
  27. package/dist/jest.js +20 -3
  28. package/dist/jest.js.map +1 -1
  29. package/dist/{notifiers-DwKlvv4H.d.cts → notifiers-B5poAEvE.d.cts} +2 -2
  30. package/dist/{notifiers-FNzKHs0a.d.ts → notifiers-CqQW0Eum.d.ts} +2 -2
  31. package/dist/playwright.d.cts +4 -4
  32. package/dist/playwright.d.ts +4 -4
  33. package/dist/relay.cjs +65 -21
  34. package/dist/relay.cjs.map +1 -1
  35. package/dist/relay.d.cts +25 -3
  36. package/dist/relay.d.ts +25 -3
  37. package/dist/relay.js +65 -21
  38. package/dist/relay.js.map +1 -1
  39. package/dist/rerender-lens.iife.js +105 -12
  40. package/dist/{runner-report-CW8X5RwK.d.ts → runner-report-Bvqoe89M.d.ts} +2 -2
  41. package/dist/{runner-report-B8Sc3FZO.d.cts → runner-report-CvpDg2zt.d.cts} +2 -2
  42. package/dist/setup.cjs +98 -9
  43. package/dist/setup.cjs.map +1 -1
  44. package/dist/setup.js +98 -9
  45. package/dist/setup.js.map +1 -1
  46. package/dist/{types-DW5-N2XH.d.cts → types-DwNN5cWe.d.cts} +28 -1
  47. package/dist/{types-DW5-N2XH.d.ts → types-DwNN5cWe.d.ts} +28 -1
  48. package/dist/vite.d.cts +1 -1
  49. package/dist/vite.d.ts +1 -1
  50. package/dist/vitest-setup.cjs +20 -3
  51. package/dist/vitest-setup.cjs.map +1 -1
  52. package/dist/vitest-setup.d.cts +4 -4
  53. package/dist/vitest-setup.d.ts +4 -4
  54. package/dist/vitest-setup.js +20 -3
  55. package/dist/vitest-setup.js.map +1 -1
  56. package/dist/vitest.cjs +20 -3
  57. package/dist/vitest.cjs.map +1 -1
  58. package/dist/vitest.d.cts +5 -5
  59. package/dist/vitest.d.ts +5 -5
  60. package/dist/vitest.js +20 -3
  61. package/dist/vitest.js.map +1 -1
  62. package/package.json +3 -2
  63. package/panel/panel.css +48 -0
  64. package/panel/panel.js +348 -20
package/dist/relay.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/relay.ts"],"names":[],"mappings":"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","file":"relay.js","sourcesContent":["/**\n * A tiny local relay (`npx rerender-lens panel`): serves the panel and forwards messages between\n * app pages and panel pages over Server-Sent Events + POST, so any app (Next.js, Webpack, a\n * production-like build) gets the panel without the extension and without a same-origin channel.\n *\n * GET / redirect to /panel.html?relay=<this server>\n * GET /panel.(html|js|css) the panel page\n * GET /events?role=app SSE: commands from panels\n * GET /events?role=panel SSE: hello / report / clear from apps, replies to commands\n * POST /message one message or an array; `{ __rerenderLens }` and replies go to\n * panels, `{ __rerenderLensCmd }` goes to apps\n *\n * Dependency-free (node:http). CORS is open: the relay is a local dev tool, bind it to 127.0.0.1.\n */\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport interface RelayOptions {\n port?: number;\n host?: string;\n /** Directory with panel.html/js/css; found automatically in the package. */\n panelDir?: string | null;\n /** Milliseconds between SSE keep-alive comments. Default 15000. */\n keepAliveMs?: number;\n}\n\nexport interface RelayServer {\n server: Server;\n /** `http://127.0.0.1:4141` once listening. */\n url: string;\n port: number;\n close(): Promise<void>;\n /** Connected app and panel streams. */\n counts(): { apps: number; panels: number };\n}\n\nconst MIME: Record<string, string> = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8' };\n\nexport function findPanelDir(): string | null {\n const here = dirname(fileURLToPath(import.meta.url));\n for (const dir of [join(here, '..', 'panel'), join(here, '..', 'extension'), join(here, '..', '..', 'extension')]) {\n if (existsSync(join(dir, 'panel.html')) && existsSync(join(dir, 'panel.js'))) return dir;\n }\n return null;\n}\n\nconst cors = (res: ServerResponse): void => {\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n};\n\nfunction readBody(req: IncomingMessage, limit = 8 * 1024 * 1024): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on('data', (c: Buffer) => {\n size += c.length;\n if (size > limit) {\n reject(new Error('body too large'));\n req.destroy();\n return;\n }\n chunks.push(c);\n });\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));\n req.on('error', reject);\n });\n}\n\nexport function createRelayServer(options: RelayOptions = {}): Promise<RelayServer> {\n const host = options.host ?? '127.0.0.1';\n const panelDir = options.panelDir === undefined ? findPanelDir() : options.panelDir;\n const keepAliveMs = options.keepAliveMs ?? 15_000;\n const apps = new Set<ServerResponse>();\n const panels = new Set<ServerResponse>();\n let url = '';\n\n const send = (targets: Set<ServerResponse>, message: unknown): void => {\n const line = `data: ${JSON.stringify(message)}\\n\\n`;\n for (const res of targets) {\n try {\n res.write(line);\n } catch {\n targets.delete(res);\n }\n }\n };\n\n const route = (message: unknown): void => {\n if (!message || typeof message !== 'object') return;\n const m = message as Record<string, unknown>;\n if (m.__rerenderLensCmd === true) send(apps, m);\n else if (m.__rerenderLens === true || m.__rerenderLensReply === true) send(panels, m);\n };\n\n const server = createServer(async (req, res) => {\n cors(res);\n const u = new URL(req.url || '/', 'http://relay');\n if (req.method === 'OPTIONS') {\n res.statusCode = 204;\n res.end();\n return;\n }\n if (u.pathname === '/' && req.method === 'GET') {\n res.statusCode = 302;\n res.setHeader('Location', `/panel.html?relay=${encodeURIComponent(url)}`);\n res.end();\n return;\n }\n if (u.pathname === '/events' && req.method === 'GET') {\n const role = u.searchParams.get('role') === 'app' ? apps : panels;\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-store');\n res.setHeader('Connection', 'keep-alive');\n res.write(': connected\\n\\n');\n role.add(res);\n const ping = setInterval(() => {\n try {\n res.write(': ping\\n\\n');\n } catch {\n /* closed */\n }\n }, keepAliveMs);\n req.on('close', () => {\n clearInterval(ping);\n role.delete(res);\n });\n // Panels learn the app count as soon as their stream is open (that first message is also how a\n // panel knows it is safe to send commands: replies only reach panels that are already connected),\n // and again whenever an app comes or goes, so they show \"disconnected\" instead of waiting.\n const count = (): unknown => ({ __rerenderLens: true, version: 2, type: 'relay', payload: { apps: apps.size } });\n if (role === apps) {\n send(panels, count());\n req.on('close', () => send(panels, count()));\n } else {\n send(new Set([res]), count());\n }\n return;\n }\n if (u.pathname === '/favicon.ico') {\n res.statusCode = 204;\n res.end();\n return;\n }\n if (u.pathname === '/message' && req.method === 'POST') {\n try {\n const parsed = JSON.parse((await readBody(req)) || 'null') as unknown;\n for (const m of Array.isArray(parsed) ? parsed : [parsed]) route(m);\n res.statusCode = 204;\n res.end();\n } catch (e) {\n res.statusCode = 400;\n res.setHeader('Content-Type', 'text/plain');\n res.end(String((e as Error).message || e));\n }\n return;\n }\n if (u.pathname === '/status' && req.method === 'GET') {\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ ok: true, apps: apps.size, panels: panels.size }));\n return;\n }\n const file = u.pathname.replace(/^\\//, '');\n if (/^panel\\.(html|js|css)$/.test(file) && req.method === 'GET') {\n if (!panelDir) {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'text/plain');\n res.end('rerender-lens: panel files not found (is the package built?)');\n return;\n }\n res.statusCode = 200;\n res.setHeader('Content-Type', MIME[file.slice(file.lastIndexOf('.'))] || 'application/octet-stream');\n res.setHeader('Cache-Control', 'no-store');\n res.end(readFileSync(join(panelDir, file)));\n return;\n }\n res.statusCode = 404;\n res.setHeader('Content-Type', 'text/plain');\n res.end('not found');\n });\n\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(options.port ?? 4141, host, () => {\n const addr = server.address();\n const port = typeof addr === 'object' && addr ? addr.port : options.port ?? 4141;\n url = `http://${host === '0.0.0.0' || host === '::' ? 'localhost' : host}:${port}`;\n resolve({\n server,\n url,\n port,\n counts: () => ({ apps: apps.size, panels: panels.size }),\n close: () =>\n new Promise<void>((done) => {\n for (const res of [...apps, ...panels]) res.end();\n apps.clear();\n panels.clear();\n server.close(() => done());\n }),\n });\n });\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/relay.ts"],"names":[],"mappings":"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","file":"relay.js","sourcesContent":["/**\n * A tiny local relay (`npx rerender-lens panel`): serves the panel and forwards messages between\n * app pages and panel pages over Server-Sent Events + POST, so any app (Next.js, Webpack, a\n * production-like build) gets the panel without the extension and without a same-origin channel.\n *\n * GET / redirect to /panel.html?relay=<this server>\n * GET /panel.(html|js|css) the panel page\n * GET /events?role=app SSE: commands from panels (first message: this app's id)\n * GET /events?role=panel SSE: hello / report / clear from apps, replies to commands\n * POST /message one message or an array; `{ __rerenderLens }` and replies go to\n * panels, `{ __rerenderLensCmd }` goes to apps\n *\n * Several apps can share one relay. Each app connection gets an id; the relay tells the app its id,\n * stamps `app` on everything that app sends, and sends panels the roster whenever it changes. A\n * command carrying `app` goes to that one app, a command without it goes to all of them, so a panel\n * or a library that knows nothing about ids behaves exactly as before.\n *\n * Dependency-free (node:http). Bound to 127.0.0.1 by default, where CORS is open and no token is\n * needed. On any other host `rerender-lens panel` generates a token: `/events`, `/message` and\n * `/status` then require it (`?token=` or the `x-rerender-lens-token` header) and the URL it prints\n * carries it, because reports contain the app's prop, state and context values.\n */\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport interface RelayOptions {\n port?: number;\n host?: string;\n /** Directory with panel.html/js/css; found automatically in the package. */\n panelDir?: string | null;\n /** Milliseconds between SSE keep-alive comments. Default 15000. */\n keepAliveMs?: number;\n /**\n * Require this token on `/events`, `/message` and `/status`. `rerender-lens panel` generates one\n * whenever it binds to anything but loopback; the URL it prints carries it as `?token=`.\n */\n token?: string;\n}\n\n/** One connected app page. `label` is what the panel's app chooser shows. */\nexport interface RelayApp {\n id: string;\n label: string;\n}\n\nexport interface RelayServer {\n server: Server;\n /** `http://127.0.0.1:4141` once listening. */\n url: string;\n /** The URL to hand to a panel or an app: `url`, plus `?token=` when one is required. */\n connectUrl: string;\n port: number;\n close(): Promise<void>;\n /** Connected app and panel streams. */\n counts(): { apps: number; panels: number };\n /** The connected apps, in connection order. */\n appList(): RelayApp[];\n}\n\n/** Same marker and protocol the library and the panel use (`src/devtools.ts`); kept literal so the relay stays dependency-free. */\nconst MARKER = '__rerenderLens';\nconst PROTOCOL = 2;\n\nconst MIME: Record<string, string> = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8' };\n\nexport function findPanelDir(): string | null {\n const here = dirname(fileURLToPath(import.meta.url));\n for (const dir of [join(here, '..', 'panel'), join(here, '..', 'extension'), join(here, '..', '..', 'extension')]) {\n if (existsSync(join(dir, 'panel.html')) && existsSync(join(dir, 'panel.js'))) return dir;\n }\n return null;\n}\n\nconst cors = (res: ServerResponse): void => {\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n};\n\nfunction readBody(req: IncomingMessage, limit = 8 * 1024 * 1024): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on('data', (c: Buffer) => {\n size += c.length;\n if (size > limit) {\n reject(new Error('body too large'));\n req.destroy();\n return;\n }\n chunks.push(c);\n });\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));\n req.on('error', reject);\n });\n}\n\nexport function createRelayServer(options: RelayOptions = {}): Promise<RelayServer> {\n const host = options.host ?? '127.0.0.1';\n const panelDir = options.panelDir === undefined ? findPanelDir() : options.panelDir;\n const keepAliveMs = options.keepAliveMs ?? 15_000;\n const token = options.token || null;\n const apps = new Map<ServerResponse, RelayApp>();\n const panels = new Set<ServerResponse>();\n let url = '';\n let nextAppId = 1;\n const connectUrl = (): string => (token ? `${url}?token=${encodeURIComponent(token)}` : url);\n\n const line = (message: unknown): string => `data: ${JSON.stringify(message)}\\n\\n`;\n const sendTo = (res: ServerResponse, message: unknown): void => {\n try {\n res.write(line(message));\n } catch {\n apps.delete(res);\n panels.delete(res);\n }\n };\n const send = (targets: Iterable<ServerResponse>, message: unknown): void => {\n const text = line(message);\n for (const res of [...targets]) {\n try {\n res.write(text);\n } catch {\n apps.delete(res);\n panels.delete(res);\n }\n }\n };\n\n const appList = (): RelayApp[] => [...apps.values()];\n const roster = (): unknown => ({ [MARKER]: true, version: PROTOCOL, type: 'relay', payload: { apps: apps.size, list: appList() } });\n const streamOf = (id: string): ServerResponse | null => {\n for (const [res, app] of apps) if (app.id === id) return res;\n return null;\n };\n\n const route = (message: unknown, from?: RelayApp): void => {\n if (!message || typeof message !== 'object') return;\n const m = message as Record<string, unknown>;\n if (m.__rerenderLensCmd === true) {\n // A command names its app when the panel is watching one of several; otherwise every app answers.\n const target = typeof m.app === 'string' ? streamOf(m.app) : null;\n if (target) sendTo(target, m);\n else send(apps.keys(), m);\n } else if (m.__rerenderLens === true || m.__rerenderLensReply === true) {\n send(panels, from ? { ...m, app: from.id } : m);\n }\n };\n\n /** With a token set, everything that carries data needs it; the static panel files do not. */\n const authorized = (req: IncomingMessage, u: URL): boolean => {\n if (!token) return true;\n const given = u.searchParams.get('token') || (typeof req.headers['x-rerender-lens-token'] === 'string' ? req.headers['x-rerender-lens-token'] : '');\n return given === token;\n };\n\n const server = createServer(async (req, res) => {\n cors(res);\n const u = new URL(req.url || '/', 'http://relay');\n if (req.method === 'OPTIONS') {\n res.statusCode = 204;\n res.end();\n return;\n }\n if (u.pathname === '/' && req.method === 'GET') {\n res.statusCode = 302;\n res.setHeader('Location', `/panel.html?relay=${encodeURIComponent(connectUrl())}`);\n res.end();\n return;\n }\n if (/^\\/(events|message|status)$/.test(u.pathname) && !authorized(req, u)) {\n res.statusCode = 401;\n res.setHeader('Content-Type', 'text/plain');\n res.end('rerender-lens relay: this relay needs the token from the URL it printed (?token=...)');\n return;\n }\n if (u.pathname === '/events' && req.method === 'GET') {\n const isApp = u.searchParams.get('role') === 'app';\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-store');\n res.setHeader('Connection', 'keep-alive');\n res.write(': connected\\n\\n');\n const app: RelayApp | null = isApp ? { id: `a${nextAppId++}`, label: (u.searchParams.get('label') || req.headers.origin || '').slice(0, 120) || `app ${nextAppId - 1}` } : null;\n if (app) apps.set(res, app);\n else panels.add(res);\n const ping = setInterval(() => {\n try {\n res.write(': ping\\n\\n');\n } catch {\n /* closed */\n }\n }, keepAliveMs);\n req.on('close', () => {\n clearInterval(ping);\n apps.delete(res);\n panels.delete(res);\n });\n // Panels learn the roster as soon as their stream is open (that first message is also how a\n // panel knows it is safe to send commands: replies only reach panels that are already connected),\n // and again whenever an app comes or goes, so they show \"disconnected\" instead of waiting.\n if (app) {\n sendTo(res, { __rerenderLensRelay: true, version: PROTOCOL, app: app.id }); // the app stamps this on what it sends\n send(panels, roster());\n req.on('close', () => send(panels, roster()));\n } else {\n sendTo(res, roster());\n }\n return;\n }\n if (u.pathname === '/favicon.ico') {\n res.statusCode = 204;\n res.end();\n return;\n }\n if (u.pathname === '/message' && req.method === 'POST') {\n try {\n const parsed = JSON.parse((await readBody(req)) || 'null') as unknown;\n const sender = u.searchParams.get('app');\n const from = sender ? (appList().find((a) => a.id === sender) ?? undefined) : undefined;\n for (const m of Array.isArray(parsed) ? parsed : [parsed]) route(m, from);\n res.statusCode = 204;\n res.end();\n } catch (e) {\n res.statusCode = 400;\n res.setHeader('Content-Type', 'text/plain');\n res.end(String((e as Error).message || e));\n }\n return;\n }\n if (u.pathname === '/status' && req.method === 'GET') {\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ ok: true, apps: apps.size, panels: panels.size, list: appList() }));\n return;\n }\n const file = u.pathname.replace(/^\\//, '');\n if (/^panel\\.(html|js|css)$/.test(file) && req.method === 'GET') {\n if (!panelDir) {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'text/plain');\n res.end('rerender-lens: panel files not found (is the package built?)');\n return;\n }\n res.statusCode = 200;\n res.setHeader('Content-Type', MIME[file.slice(file.lastIndexOf('.'))] || 'application/octet-stream');\n res.setHeader('Cache-Control', 'no-store');\n res.end(readFileSync(join(panelDir, file)));\n return;\n }\n res.statusCode = 404;\n res.setHeader('Content-Type', 'text/plain');\n res.end('not found');\n });\n\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(options.port ?? 4141, host, () => {\n const addr = server.address();\n const port = typeof addr === 'object' && addr ? addr.port : options.port ?? 4141;\n url = `http://${host === '0.0.0.0' || host === '::' ? 'localhost' : host}:${port}`;\n resolve({\n server,\n url,\n connectUrl: connectUrl(),\n port,\n counts: () => ({ apps: apps.size, panels: panels.size }),\n appList,\n close: () =>\n new Promise<void>((done) => {\n for (const res of [...apps.keys(), ...panels]) res.end();\n apps.clear();\n panels.clear();\n server.close(() => done());\n }),\n });\n });\n });\n}\n"]}
@@ -1,4 +1,4 @@
1
- /* rerender-lens 0.5.0 — rerender-lens.core.js: types, version, diff, report, state, overlay. Built by scripts/build-vendor.mjs; do not edit. */
1
+ /* rerender-lens 0.7.0 — rerender-lens.core.js: types, version, diff, report, state, overlay. Built by scripts/build-vendor.mjs; do not edit. */
2
2
  (() => {
3
3
  var __defProp = Object.defineProperty;
4
4
  var __export = (target, all) => {
@@ -18,7 +18,7 @@
18
18
  __export(version_exports, {
19
19
  VERSION: () => VERSION
20
20
  });
21
- var VERSION = true ? "0.5.0" : "dev";
21
+ var VERSION = true ? "0.7.0" : "dev";
22
22
 
23
23
  // src/diff.ts
24
24
  var diff_exports = {};
@@ -443,20 +443,33 @@
443
443
  // src/state.ts
444
444
  var state_exports = {};
445
445
  __export(state_exports, {
446
+ MAX_SEEN_COMPONENTS: () => MAX_SEEN_COMPONENTS,
446
447
  dispatch: () => dispatch,
447
448
  getState: () => getState,
449
+ noteRendered: () => noteRendered,
448
450
  warnOnce: () => warnOnce
449
451
  });
452
+ var MAX_SEEN_COMPONENTS = 500;
450
453
  var KEY = /* @__PURE__ */ Symbol.for("rerender-lens.state");
451
454
  function getState() {
452
455
  const g = globalThis;
453
456
  let s = g[KEY];
454
457
  if (!s) {
455
- s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0, truncated: 0 };
458
+ s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0, truncated: 0, seen: /* @__PURE__ */ new Set(), seenTracked: /* @__PURE__ */ new Set(), seenOverflow: false };
456
459
  g[KEY] = s;
457
460
  }
458
461
  return s;
459
462
  }
463
+ function noteRendered(s, name, tracked) {
464
+ if (!s.seen.has(name)) {
465
+ if (s.seen.size >= MAX_SEEN_COMPONENTS) {
466
+ s.seenOverflow = true;
467
+ return;
468
+ }
469
+ s.seen.add(name);
470
+ }
471
+ if (tracked) s.seenTracked.add(name);
472
+ }
460
473
  function warnOnce(key, message) {
461
474
  const s = getState();
462
475
  if (s.warnedOnce.has(key)) return;
@@ -612,7 +625,7 @@
612
625
  })();
613
626
 
614
627
  ;
615
- /* rerender-lens 0.5.0 — rerender-lens.engine.js: fiber, tracker, hookNames. Built by scripts/build-vendor.mjs; do not edit. */
628
+ /* rerender-lens 0.7.0 — rerender-lens.engine.js: fiber, tracker, hookNames. Built by scripts/build-vendor.mjs; do not edit. */
616
629
  (() => {
617
630
  var __create = Object.create;
618
631
  var __defProp = Object.defineProperty;
@@ -717,6 +730,7 @@
717
730
  __export(tracker_exports, {
718
731
  configure: () => configure,
719
732
  disable: () => disable,
733
+ explainTracking: () => explainTracking,
720
734
  getDisplayName: () => getDisplayName,
721
735
  init: () => init,
722
736
  isEnabled: () => isEnabled,
@@ -764,10 +778,53 @@
764
778
  if (o.trackAllMemoized) return isMemo(type) || isPureClass(type);
765
779
  return false;
766
780
  }
781
+ var ANONYMOUS = "Anonymous";
782
+ function explainTracking(type, o) {
783
+ if (typeof type === "string")
784
+ return { tracked: false, name: type, memoized: false, reason: `<${type}> is a DOM element, not a React component; only components are tracked.` };
785
+ if (!isComponentLike(type))
786
+ return { tracked: false, name: ANONYMOUS, memoized: false, reason: "This is not a React component type, so it is never tracked." };
787
+ const name = getDisplayName(type);
788
+ const memo = isMemo(type);
789
+ const memoized = memo || isPureClass(type);
790
+ if (o.exclude?.some((m) => matches(m, name)))
791
+ return { tracked: false, name, memoized, reason: `<${name}> matches "exclude", which wins over every other rule.`, fix: `Remove "${name}" from the exclude list.` };
792
+ if (hasMarker(type)) return { tracked: true, name, memoized, reason: `<${name}> is marked with track() (the ${import_types.MARKER} static).` };
793
+ if (o.include?.some((m) => matches(m, name))) return { tracked: true, name, memoized, reason: `<${name}> matches "include".` };
794
+ if (o.trackAllComponents) return { tracked: true, name, memoized, reason: '"Track every component" is on, so every component is tracked.' };
795
+ if (o.trackAllMemoized && memoized)
796
+ return { tracked: true, name, memoized, reason: `<${name}> is ${memo ? "a React.memo component" : "a PureComponent"}, and "Track every React.memo and PureComponent" is on.` };
797
+ if (name === ANONYMOUS)
798
+ return {
799
+ tracked: false,
800
+ name,
801
+ memoized,
802
+ reason: 'This component has no display name, so "include" and "exclude" cannot address it.',
803
+ fix: "Give the function a name, set displayName, or mark it with track(fn, 'Name')."
804
+ };
805
+ if (o.trackAllMemoized)
806
+ return {
807
+ tracked: false,
808
+ name,
809
+ memoized,
810
+ reason: `"Track every React.memo and PureComponent" only covers React.memo components and PureComponent classes, and <${name}> is neither.`,
811
+ fix: `Add "${name}" to include, wrap it in React.memo, or turn on "Track every component".`
812
+ };
813
+ return {
814
+ tracked: false,
815
+ name,
816
+ memoized,
817
+ reason: `Nothing selects <${name}>: it is not marked with track(), no "include" pattern matches it, and neither "track every" option is on.`,
818
+ fix: `Add "${name}" to include, or turn on "Track every React.memo and PureComponent" / "Track every component".`
819
+ };
820
+ }
767
821
  function init(options = {}) {
768
822
  const s = (0, import_state.getState)();
769
823
  s.options = { ...options };
770
824
  s.printed.clear();
825
+ s.seen.clear();
826
+ s.seenTracked.clear();
827
+ s.seenOverflow = false;
771
828
  if (!s.enabled) {
772
829
  s.detach = attach();
773
830
  s.enabled = true;
@@ -1502,8 +1559,11 @@
1502
1559
  const alt = fiber.alternate;
1503
1560
  if (alt && isComponentTag(fiber.tag) && didRender(fiber)) {
1504
1561
  if (isHotSwapped(fiber, alt)) hot = true;
1505
- renderedNames.add(fiberName(fiber));
1506
- if (shouldTrack(fiberType(fiber), o)) rendered.push(fiber);
1562
+ const name = fiberName(fiber);
1563
+ renderedNames.add(name);
1564
+ const tracked = shouldTrack(fiberType(fiber), o);
1565
+ (0, import_state2.noteRendered)(s, name, tracked);
1566
+ if (tracked) rendered.push(fiber);
1507
1567
  }
1508
1568
  if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) (resolvedBoundaries ?? (resolvedBoundaries = [])).push(fiber);
1509
1569
  if (!alt || fiber.child !== alt.child) {
@@ -1596,7 +1656,7 @@
1596
1656
  })();
1597
1657
 
1598
1658
  ;
1599
- /* rerender-lens 0.5.0 — rerender-lens.js: devtools bridge + notifiers (window.RerenderLens). Built by scripts/build-vendor.mjs; do not edit. */
1659
+ /* rerender-lens 0.7.0 — rerender-lens.js: devtools bridge + notifiers (window.RerenderLens). Built by scripts/build-vendor.mjs; do not edit. */
1600
1660
  "use strict";
1601
1661
  var RerenderLens = (() => {
1602
1662
  var __create = Object.create;
@@ -1719,6 +1779,7 @@ var RerenderLens = (() => {
1719
1779
  diffRecords: () => import_diff4.diffRecords,
1720
1780
  disable: () => import_tracker2.disable,
1721
1781
  ensureDevtoolsHook: () => import_fiber2.ensureDevtoolsHook,
1782
+ explainTracking: () => import_tracker2.explainTracking,
1722
1783
  getDisplayName: () => import_tracker2.getDisplayName,
1723
1784
  getRenderers: () => import_fiber2.getRenderers,
1724
1785
  init: () => import_tracker2.init,
@@ -2267,6 +2328,18 @@ ${formatFixes(reports)}`);
2267
2328
  target.postMessage(msg, "*");
2268
2329
  };
2269
2330
  const source = options.source ?? "page";
2331
+ const trackingSummary = () => {
2332
+ const s = (0, import_state.getState)();
2333
+ const o = s.options;
2334
+ return {
2335
+ mode: o.trackAllComponents ? "all" : o.trackAllMemoized ? "memoized" : "marked",
2336
+ include: (o.include ?? []).map(matcherToString).filter((x) => x !== null),
2337
+ exclude: (o.exclude ?? []).map(matcherToString).filter((x) => x !== null),
2338
+ renderedCount: s.seen.size,
2339
+ trackedCount: s.seenTracked.size,
2340
+ overflow: s.seenOverflow
2341
+ };
2342
+ };
2270
2343
  const info = () => ({
2271
2344
  count: buffer.length,
2272
2345
  library: import_version.VERSION,
@@ -2280,7 +2353,8 @@ ${formatFixes(reports)}`);
2280
2353
  scheduled: (0, import_state.getState)().scheduled,
2281
2354
  commits: (0, import_state.getState)().commits,
2282
2355
  overhead: { totalMs: (0, import_state.getState)().overheadMs, maxCommitMs: (0, import_state.getState)().maxCommitMs },
2283
- truncated: (0, import_state.getState)().truncated
2356
+ truncated: (0, import_state.getState)().truncated,
2357
+ tracking: trackingSummary()
2284
2358
  });
2285
2359
  const bridge = {
2286
2360
  replay: () => {
@@ -2326,6 +2400,10 @@ ${formatFixes(reports)}`);
2326
2400
  flashAvoidable: (on) => {
2327
2401
  flashOn = !!on;
2328
2402
  },
2403
+ explain: (target2) => {
2404
+ const comp = typeof target2 === "number" ? (0, import_fiber.fiberById)(target2) : (0, import_fiber.nearestComponent)((0, import_fiber.fiberForNode)(target2));
2405
+ return comp ? (0, import_tracker.explainTracking)((0, import_fiber.fiberType)(comp), (0, import_state.getState)().options) : null;
2406
+ },
2329
2407
  inspect: (node) => {
2330
2408
  const comp = (0, import_fiber.nearestComponent)((0, import_fiber.fiberForNode)(node));
2331
2409
  if (!comp) return null;
@@ -2340,6 +2418,7 @@ ${formatFixes(reports)}`);
2340
2418
  component: (0, import_fiber.fiberName)(comp),
2341
2419
  instanceId: id,
2342
2420
  tracked: (0, import_tracker.shouldTrack)((0, import_fiber.fiberType)(comp), (0, import_state.getState)().options),
2421
+ tracking: (0, import_tracker.explainTracking)((0, import_fiber.fiberType)(comp), (0, import_state.getState)().options),
2343
2422
  path,
2344
2423
  reports: id === null ? [] : buffer.filter((e) => e.instanceId === id).slice(-20).map((e) => e.payload)
2345
2424
  };
@@ -2374,6 +2453,9 @@ ${formatFixes(reports)}`);
2374
2453
  bridge.flashAvoidable(!!data.arg);
2375
2454
  reply.result = true;
2376
2455
  break;
2456
+ case "explain":
2457
+ reply.result = bridge.explain(data.arg);
2458
+ break;
2377
2459
  default:
2378
2460
  reply.error = `unknown command ${String(data.cmd)}`;
2379
2461
  }
@@ -2382,6 +2464,7 @@ ${formatFixes(reports)}`);
2382
2464
  }
2383
2465
  return reply;
2384
2466
  };
2467
+ const isRelayGreeting = (data) => !!data && typeof data === "object" && data.__rerenderLensRelay === true && typeof data.app === "string";
2385
2468
  const isCommand = (data) => !!data && typeof data === "object" && data.__rerenderLensCmd === true && typeof data.id === "string";
2386
2469
  const envelope = (type, payload) => ({ [DEVTOOLS_MARKER]: true, version: PROTOCOL_VERSION, type, payload });
2387
2470
  const sinks = [];
@@ -2404,14 +2487,20 @@ ${formatFixes(reports)}`);
2404
2487
  const relayUrl = options.relay ?? (typeof window !== "undefined" ? window.__RERENDER_LENS_RELAY__ : void 0);
2405
2488
  const ES = options.eventSource ?? (typeof EventSource === "function" ? EventSource : null);
2406
2489
  if (relayUrl && ES && typeof fetch === "function") {
2407
- const base = relayUrl.replace(/\/$/, "");
2490
+ const [rawBase, relayQuery = ""] = relayUrl.split("?");
2491
+ const base = (rawBase ?? "").replace(/\/$/, "");
2492
+ const relayToken = new URLSearchParams(relayQuery).get("token");
2493
+ const auth = relayToken ? `token=${encodeURIComponent(relayToken)}` : "";
2408
2494
  let queue = [];
2409
2495
  let scheduled = false;
2496
+ let appId = null;
2410
2497
  const flush = () => {
2411
2498
  scheduled = false;
2412
2499
  const batch = queue;
2413
2500
  queue = [];
2414
- fetch(`${base}/message`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(batch), keepalive: true }).catch(() => {
2501
+ const params = [appId ? `app=${encodeURIComponent(appId)}` : "", auth].filter(Boolean).join("&");
2502
+ const to = `${base}/message${params ? `?${params}` : ""}`;
2503
+ fetch(to, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(batch), keepalive: true }).catch(() => {
2415
2504
  });
2416
2505
  };
2417
2506
  const relaySend = (message) => {
@@ -2422,7 +2511,8 @@ ${formatFixes(reports)}`);
2422
2511
  }
2423
2512
  };
2424
2513
  try {
2425
- const stream = new ES(`${base}/events?role=app`);
2514
+ const label = typeof location !== "undefined" ? `${location.host}${location.pathname}`.slice(0, 120) : "";
2515
+ const stream = new ES(`${base}/events?role=app${label ? `&label=${encodeURIComponent(label)}` : ""}${auth ? `&${auth}` : ""}`);
2426
2516
  stream.onmessage = (event) => {
2427
2517
  let parsed;
2428
2518
  try {
@@ -2430,7 +2520,10 @@ ${formatFixes(reports)}`);
2430
2520
  } catch {
2431
2521
  return;
2432
2522
  }
2433
- for (const m of Array.isArray(parsed) ? parsed : [parsed]) if (isCommand(m)) relaySend(runCommand(m));
2523
+ for (const m of Array.isArray(parsed) ? parsed : [parsed]) {
2524
+ if (isRelayGreeting(m)) appId = m.app;
2525
+ else if (isCommand(m)) relaySend(runCommand(m));
2526
+ }
2434
2527
  };
2435
2528
  stream.onerror = () => {
2436
2529
  };
@@ -1,5 +1,5 @@
1
- import { R as RenderReport, O as Options } from './types-DW5-N2XH.js';
2
- import { C as Collector } from './notifiers-FNzKHs0a.js';
1
+ import { R as RenderReport, O as Options } from './types-DwNN5cWe.js';
2
+ import { C as Collector } from './notifiers-CqQW0Eum.js';
3
3
 
4
4
  /** Environment variable carrying the path of the run's JSONL file from the reporter to the workers. */
5
5
  declare const OUT_ENV = "RERENDER_LENS_OUT";
@@ -1,5 +1,5 @@
1
- import { R as RenderReport, O as Options } from './types-DW5-N2XH.cjs';
2
- import { C as Collector } from './notifiers-DwKlvv4H.cjs';
1
+ import { R as RenderReport, O as Options } from './types-DwNN5cWe.cjs';
2
+ import { C as Collector } from './notifiers-B5poAEvE.cjs';
3
3
 
4
4
  /** Environment variable carrying the path of the run's JSONL file from the reporter to the workers. */
5
5
  declare const OUT_ENV = "RERENDER_LENS_OUT";
package/dist/setup.cjs CHANGED
@@ -383,16 +383,27 @@ function printReport(report, options) {
383
383
  }
384
384
 
385
385
  // src/state.ts
386
+ var MAX_SEEN_COMPONENTS = 500;
386
387
  var KEY = /* @__PURE__ */ Symbol.for("rerender-lens.state");
387
388
  function getState() {
388
389
  const g = globalThis;
389
390
  let s = g[KEY];
390
391
  if (!s) {
391
- s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0, truncated: 0 };
392
+ s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0, truncated: 0, seen: /* @__PURE__ */ new Set(), seenTracked: /* @__PURE__ */ new Set(), seenOverflow: false };
392
393
  g[KEY] = s;
393
394
  }
394
395
  return s;
395
396
  }
397
+ function noteRendered(s, name, tracked) {
398
+ if (!s.seen.has(name)) {
399
+ if (s.seen.size >= MAX_SEEN_COMPONENTS) {
400
+ s.seenOverflow = true;
401
+ return;
402
+ }
403
+ s.seen.add(name);
404
+ }
405
+ if (tracked) s.seenTracked.add(name);
406
+ }
396
407
  function warnOnce(key, message) {
397
408
  const s = getState();
398
409
  if (s.warnedOnce.has(key)) return;
@@ -1119,8 +1130,11 @@ function onCommit(root, commitPriority) {
1119
1130
  const alt = fiber.alternate;
1120
1131
  if (alt && isComponentTag(fiber.tag) && didRender(fiber)) {
1121
1132
  if (isHotSwapped(fiber, alt)) hot = true;
1122
- renderedNames.add(fiberName(fiber));
1123
- if (shouldTrack(fiberType(fiber), o)) rendered.push(fiber);
1133
+ const name = fiberName(fiber);
1134
+ renderedNames.add(name);
1135
+ const tracked = shouldTrack(fiberType(fiber), o);
1136
+ noteRendered(s, name, tracked);
1137
+ if (tracked) rendered.push(fiber);
1124
1138
  }
1125
1139
  if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) (resolvedBoundaries ?? (resolvedBoundaries = [])).push(fiber);
1126
1140
  if (!alt || fiber.child !== alt.child) {
@@ -1245,10 +1259,53 @@ function shouldTrack(type, o) {
1245
1259
  if (o.trackAllMemoized) return isMemo(type) || isPureClass(type);
1246
1260
  return false;
1247
1261
  }
1262
+ var ANONYMOUS = "Anonymous";
1263
+ function explainTracking(type, o) {
1264
+ if (typeof type === "string")
1265
+ return { tracked: false, name: type, memoized: false, reason: `<${type}> is a DOM element, not a React component; only components are tracked.` };
1266
+ if (!isComponentLike(type))
1267
+ return { tracked: false, name: ANONYMOUS, memoized: false, reason: "This is not a React component type, so it is never tracked." };
1268
+ const name = getDisplayName(type);
1269
+ const memo = isMemo(type);
1270
+ const memoized = memo || isPureClass(type);
1271
+ if (o.exclude?.some((m) => matches(m, name)))
1272
+ return { tracked: false, name, memoized, reason: `<${name}> matches "exclude", which wins over every other rule.`, fix: `Remove "${name}" from the exclude list.` };
1273
+ if (hasMarker(type)) return { tracked: true, name, memoized, reason: `<${name}> is marked with track() (the ${MARKER} static).` };
1274
+ if (o.include?.some((m) => matches(m, name))) return { tracked: true, name, memoized, reason: `<${name}> matches "include".` };
1275
+ if (o.trackAllComponents) return { tracked: true, name, memoized, reason: '"Track every component" is on, so every component is tracked.' };
1276
+ if (o.trackAllMemoized && memoized)
1277
+ return { tracked: true, name, memoized, reason: `<${name}> is ${memo ? "a React.memo component" : "a PureComponent"}, and "Track every React.memo and PureComponent" is on.` };
1278
+ if (name === ANONYMOUS)
1279
+ return {
1280
+ tracked: false,
1281
+ name,
1282
+ memoized,
1283
+ reason: 'This component has no display name, so "include" and "exclude" cannot address it.',
1284
+ fix: "Give the function a name, set displayName, or mark it with track(fn, 'Name')."
1285
+ };
1286
+ if (o.trackAllMemoized)
1287
+ return {
1288
+ tracked: false,
1289
+ name,
1290
+ memoized,
1291
+ reason: `"Track every React.memo and PureComponent" only covers React.memo components and PureComponent classes, and <${name}> is neither.`,
1292
+ fix: `Add "${name}" to include, wrap it in React.memo, or turn on "Track every component".`
1293
+ };
1294
+ return {
1295
+ tracked: false,
1296
+ name,
1297
+ memoized,
1298
+ reason: `Nothing selects <${name}>: it is not marked with track(), no "include" pattern matches it, and neither "track every" option is on.`,
1299
+ fix: `Add "${name}" to include, or turn on "Track every React.memo and PureComponent" / "Track every component".`
1300
+ };
1301
+ }
1248
1302
  function init(options = {}) {
1249
1303
  const s = getState();
1250
1304
  s.options = { ...options };
1251
1305
  s.printed.clear();
1306
+ s.seen.clear();
1307
+ s.seenTracked.clear();
1308
+ s.seenOverflow = false;
1252
1309
  if (!s.enabled) {
1253
1310
  s.detach = attach();
1254
1311
  s.enabled = true;
@@ -1374,7 +1431,7 @@ function flash(target, duration = 500) {
1374
1431
  }
1375
1432
 
1376
1433
  // src/version.ts
1377
- var VERSION = "0.5.0" ;
1434
+ var VERSION = "0.7.0" ;
1378
1435
 
1379
1436
  // src/devtools.ts
1380
1437
  var DEVTOOLS_MARKER = "__rerenderLens";
@@ -1500,6 +1557,18 @@ function createDevtoolsNotifier(options = {}) {
1500
1557
  target.postMessage(msg, "*");
1501
1558
  };
1502
1559
  const source = options.source ?? "page";
1560
+ const trackingSummary = () => {
1561
+ const s = getState();
1562
+ const o = s.options;
1563
+ return {
1564
+ mode: o.trackAllComponents ? "all" : o.trackAllMemoized ? "memoized" : "marked",
1565
+ include: (o.include ?? []).map(matcherToString).filter((x) => x !== null),
1566
+ exclude: (o.exclude ?? []).map(matcherToString).filter((x) => x !== null),
1567
+ renderedCount: s.seen.size,
1568
+ trackedCount: s.seenTracked.size,
1569
+ overflow: s.seenOverflow
1570
+ };
1571
+ };
1503
1572
  const info = () => ({
1504
1573
  count: buffer.length,
1505
1574
  library: VERSION,
@@ -1513,7 +1582,8 @@ function createDevtoolsNotifier(options = {}) {
1513
1582
  scheduled: getState().scheduled,
1514
1583
  commits: getState().commits,
1515
1584
  overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs },
1516
- truncated: getState().truncated
1585
+ truncated: getState().truncated,
1586
+ tracking: trackingSummary()
1517
1587
  });
1518
1588
  const bridge = {
1519
1589
  replay: () => {
@@ -1559,6 +1629,10 @@ function createDevtoolsNotifier(options = {}) {
1559
1629
  flashAvoidable: (on) => {
1560
1630
  flashOn = !!on;
1561
1631
  },
1632
+ explain: (target2) => {
1633
+ const comp = typeof target2 === "number" ? fiberById(target2) : nearestComponent(fiberForNode(target2));
1634
+ return comp ? explainTracking(fiberType(comp), getState().options) : null;
1635
+ },
1562
1636
  inspect: (node) => {
1563
1637
  const comp = nearestComponent(fiberForNode(node));
1564
1638
  if (!comp) return null;
@@ -1573,6 +1647,7 @@ function createDevtoolsNotifier(options = {}) {
1573
1647
  component: fiberName(comp),
1574
1648
  instanceId: id,
1575
1649
  tracked: shouldTrack(fiberType(comp), getState().options),
1650
+ tracking: explainTracking(fiberType(comp), getState().options),
1576
1651
  path,
1577
1652
  reports: id === null ? [] : buffer.filter((e) => e.instanceId === id).slice(-20).map((e) => e.payload)
1578
1653
  };
@@ -1607,6 +1682,9 @@ function createDevtoolsNotifier(options = {}) {
1607
1682
  bridge.flashAvoidable(!!data.arg);
1608
1683
  reply.result = true;
1609
1684
  break;
1685
+ case "explain":
1686
+ reply.result = bridge.explain(data.arg);
1687
+ break;
1610
1688
  default:
1611
1689
  reply.error = `unknown command ${String(data.cmd)}`;
1612
1690
  }
@@ -1615,6 +1693,7 @@ function createDevtoolsNotifier(options = {}) {
1615
1693
  }
1616
1694
  return reply;
1617
1695
  };
1696
+ const isRelayGreeting = (data) => !!data && typeof data === "object" && data.__rerenderLensRelay === true && typeof data.app === "string";
1618
1697
  const isCommand = (data) => !!data && typeof data === "object" && data.__rerenderLensCmd === true && typeof data.id === "string";
1619
1698
  const envelope = (type, payload) => ({ [DEVTOOLS_MARKER]: true, version: PROTOCOL_VERSION, type, payload });
1620
1699
  const sinks = [];
@@ -1637,14 +1716,20 @@ function createDevtoolsNotifier(options = {}) {
1637
1716
  const relayUrl = options.relay ?? (typeof window !== "undefined" ? window.__RERENDER_LENS_RELAY__ : void 0);
1638
1717
  const ES = options.eventSource ?? (typeof EventSource === "function" ? EventSource : null);
1639
1718
  if (relayUrl && ES && typeof fetch === "function") {
1640
- const base = relayUrl.replace(/\/$/, "");
1719
+ const [rawBase, relayQuery = ""] = relayUrl.split("?");
1720
+ const base = (rawBase ?? "").replace(/\/$/, "");
1721
+ const relayToken = new URLSearchParams(relayQuery).get("token");
1722
+ const auth = relayToken ? `token=${encodeURIComponent(relayToken)}` : "";
1641
1723
  let queue = [];
1642
1724
  let scheduled = false;
1725
+ let appId = null;
1643
1726
  const flush = () => {
1644
1727
  scheduled = false;
1645
1728
  const batch = queue;
1646
1729
  queue = [];
1647
- fetch(`${base}/message`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(batch), keepalive: true }).catch(() => {
1730
+ const params = [appId ? `app=${encodeURIComponent(appId)}` : "", auth].filter(Boolean).join("&");
1731
+ const to = `${base}/message${params ? `?${params}` : ""}`;
1732
+ fetch(to, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(batch), keepalive: true }).catch(() => {
1648
1733
  });
1649
1734
  };
1650
1735
  const relaySend = (message) => {
@@ -1655,7 +1740,8 @@ function createDevtoolsNotifier(options = {}) {
1655
1740
  }
1656
1741
  };
1657
1742
  try {
1658
- const stream = new ES(`${base}/events?role=app`);
1743
+ const label = typeof location !== "undefined" ? `${location.host}${location.pathname}`.slice(0, 120) : "";
1744
+ const stream = new ES(`${base}/events?role=app${label ? `&label=${encodeURIComponent(label)}` : ""}${auth ? `&${auth}` : ""}`);
1659
1745
  stream.onmessage = (event) => {
1660
1746
  let parsed;
1661
1747
  try {
@@ -1663,7 +1749,10 @@ function createDevtoolsNotifier(options = {}) {
1663
1749
  } catch {
1664
1750
  return;
1665
1751
  }
1666
- for (const m of Array.isArray(parsed) ? parsed : [parsed]) if (isCommand(m)) relaySend(runCommand(m));
1752
+ for (const m of Array.isArray(parsed) ? parsed : [parsed]) {
1753
+ if (isRelayGreeting(m)) appId = m.app;
1754
+ else if (isCommand(m)) relaySend(runCommand(m));
1755
+ }
1667
1756
  };
1668
1757
  stream.onerror = () => {
1669
1758
  };