@oddsquad/vite-plugin-lit 0.4.0 → 0.6.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 (100) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +16 -5
  3. package/bin.mjs +16 -12
  4. package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-CdtPhBL-.js → browser-agent-rpc-BXhoSh1z-DXAVFObe.js} +2 -2
  5. package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-CdtPhBL-.js.map → browser-agent-rpc-BXhoSh1z-DXAVFObe.js.map} +1 -1
  6. package/dist/client/assets/{index-DVe6M4HV.js → index-DVr5EnyB.js} +1859 -751
  7. package/dist/client/assets/index-DVr5EnyB.js.map +1 -0
  8. package/dist/client/index.html +11 -1
  9. package/lib/devframe/definition.d.ts +7 -0
  10. package/lib/devframe/definition.d.ts.map +1 -1
  11. package/lib/devframe/definition.js +56 -11
  12. package/lib/devframe/definition.js.map +1 -1
  13. package/lib/devframe/launch-editor.d.ts +20 -0
  14. package/lib/devframe/launch-editor.d.ts.map +1 -0
  15. package/lib/devframe/launch-editor.js +40 -0
  16. package/lib/devframe/launch-editor.js.map +1 -0
  17. package/lib/devframe/protocol.d.ts +31 -4
  18. package/lib/devframe/protocol.d.ts.map +1 -1
  19. package/lib/devframe/protocol.js +21 -3
  20. package/lib/devframe/protocol.js.map +1 -1
  21. package/lib/devframe/rpc-source.d.ts +44 -0
  22. package/lib/devframe/rpc-source.d.ts.map +1 -0
  23. package/lib/devframe/rpc-source.js +150 -0
  24. package/lib/devframe/rpc-source.js.map +1 -0
  25. package/lib/devframe/source.d.ts +12 -0
  26. package/lib/devframe/source.d.ts.map +1 -1
  27. package/lib/devframe/source.js +12 -0
  28. package/lib/devframe/source.js.map +1 -1
  29. package/lib/devframe/vite.d.ts +2 -0
  30. package/lib/devframe/vite.d.ts.map +1 -1
  31. package/lib/devframe/vite.js +3 -9
  32. package/lib/devframe/vite.js.map +1 -1
  33. package/lib/options.d.ts +6 -1
  34. package/lib/options.d.ts.map +1 -1
  35. package/lib/options.js +65 -18
  36. package/lib/options.js.map +1 -1
  37. package/lib/override-baselines.d.ts +17 -0
  38. package/lib/override-baselines.d.ts.map +1 -0
  39. package/lib/override-baselines.js +25 -0
  40. package/lib/override-baselines.js.map +1 -0
  41. package/lib/plugin.d.ts.map +1 -1
  42. package/lib/plugin.js +8 -0
  43. package/lib/plugin.js.map +1 -1
  44. package/lib/plugins/open-in-editor.d.ts.map +1 -1
  45. package/lib/plugins/open-in-editor.js +6 -1
  46. package/lib/plugins/open-in-editor.js.map +1 -1
  47. package/lib/runtime/indicator.js.map +1 -1
  48. package/lib/runtime/inspector/install.js +4 -2
  49. package/lib/runtime/inspector/install.js.map +1 -1
  50. package/lib/runtime/inspector/serialize.d.ts.map +1 -1
  51. package/lib/runtime/inspector/serialize.js +20 -0
  52. package/lib/runtime/inspector/serialize.js.map +1 -1
  53. package/lib/runtime/overrides.d.ts +4 -10
  54. package/lib/runtime/overrides.d.ts.map +1 -1
  55. package/lib/runtime/overrides.js +5 -3
  56. package/lib/runtime/overrides.js.map +1 -1
  57. package/lib/runtime/page-channel.d.ts +52 -0
  58. package/lib/runtime/page-channel.d.ts.map +1 -0
  59. package/lib/runtime/page-channel.js +67 -0
  60. package/lib/runtime/page-channel.js.map +1 -0
  61. package/lib/runtime/patch.d.ts.map +1 -1
  62. package/lib/runtime/patch.js +2 -1
  63. package/lib/runtime/patch.js.map +1 -1
  64. package/lib/runtime/rpc-transport.d.ts +46 -0
  65. package/lib/runtime/rpc-transport.d.ts.map +1 -0
  66. package/lib/runtime/rpc-transport.js +82 -0
  67. package/lib/runtime/rpc-transport.js.map +1 -0
  68. package/lib/runtime/source-overlay/overlay-element.d.ts.map +1 -1
  69. package/lib/runtime/source-overlay/overlay-element.js +13 -3
  70. package/lib/runtime/source-overlay/overlay-element.js.map +1 -1
  71. package/lib/runtime/timeline/chrome-tracks.d.ts +22 -0
  72. package/lib/runtime/timeline/chrome-tracks.d.ts.map +1 -0
  73. package/lib/runtime/timeline/chrome-tracks.js +74 -0
  74. package/lib/runtime/timeline/chrome-tracks.js.map +1 -0
  75. package/lib/runtime/timeline/clock.d.ts +6 -0
  76. package/lib/runtime/timeline/clock.d.ts.map +1 -1
  77. package/lib/runtime/timeline/clock.js +6 -0
  78. package/lib/runtime/timeline/clock.js.map +1 -1
  79. package/lib/runtime/timeline/install.js +42 -13
  80. package/lib/runtime/timeline/install.js.map +1 -1
  81. package/lib/runtime/timeline/public-api.d.ts.map +1 -1
  82. package/lib/runtime/timeline/public-api.js +11 -3
  83. package/lib/runtime/timeline/public-api.js.map +1 -1
  84. package/lib/runtime/timeline/transport.d.ts +6 -9
  85. package/lib/runtime/timeline/transport.d.ts.map +1 -1
  86. package/lib/runtime/timeline/transport.js.map +1 -1
  87. package/lib/timeline/filter.d.ts +38 -0
  88. package/lib/timeline/filter.d.ts.map +1 -0
  89. package/lib/timeline/filter.js +44 -0
  90. package/lib/timeline/filter.js.map +1 -0
  91. package/lib/timeline/tracks.d.ts +113 -0
  92. package/lib/timeline/tracks.d.ts.map +1 -0
  93. package/lib/timeline/tracks.js +168 -0
  94. package/lib/timeline/tracks.js.map +1 -0
  95. package/package.json +6 -1
  96. package/types/timeline.d.ts +44 -0
  97. package/types/timeline.d.ts.map +1 -1
  98. package/types/timeline.js +2 -0
  99. package/types/timeline.js.map +1 -1
  100. package/dist/client/assets/index-DVe6M4HV.js.map +0 -1
@@ -1044,103 +1044,12 @@ var setColorSchemePreference = (pref) => {
1044
1044
  localStorage.setItem(COLOR_SCHEME_LS_KEY, pref);
1045
1045
  } catch {}
1046
1046
  applyColorScheme(pref);
1047
- };
1048
- //#endregion
1049
- //#region \0@oxc-project+runtime@0.149.0/helpers/esm/decorate.js
1050
- function __decorate(decorators, target, key, desc) {
1051
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1052
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1053
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1054
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1055
- }
1056
- //#endregion
1057
- //#region src/panel/timeline-layers.ts
1058
- /**
1059
- * @license
1060
- * Copyright 2026 Google LLC
1061
- * SPDX-License-Identifier: BSD-3-Clause
1062
- */
1063
- var TimelineLayers = class TimelineLayers extends i$2 {
1064
- constructor(..._args) {
1065
- super(..._args);
1066
- this.layers = [];
1067
- }
1068
- static {
1069
- this.styles = [tokens, i$5`
1070
- :host {
1071
- display: flex;
1072
- gap: var(--lit-devtools-space-2);
1073
- padding: var(--lit-devtools-space-3) var(--lit-devtools-space-5);
1074
- border-bottom: 1px solid var(--lit-devtools-border);
1075
- background: var(--lit-devtools-surface-low);
1076
- flex-shrink: 0;
1077
- flex-wrap: wrap;
1078
- }
1079
- button {
1080
- display: flex;
1081
- align-items: center;
1082
- gap: var(--lit-devtools-space-2);
1083
- padding: var(--lit-devtools-space-2) var(--lit-devtools-space-4);
1084
- border-radius: var(--lit-devtools-radius-pill);
1085
- background: var(--lit-devtools-surface-elevated);
1086
- border: 1px solid transparent;
1087
- color: var(--lit-devtools-text-secondary);
1088
- font-size: var(--lit-devtools-text-2xs);
1089
- cursor: pointer;
1090
- user-select: none;
1091
- transition: opacity var(--lit-devtools-dur-fast)
1092
- var(--lit-devtools-ease-standard);
1093
- }
1094
- button.on {
1095
- color: var(--lit-devtools-text);
1096
- }
1097
- button:hover {
1098
- background: var(--lit-devtools-surface-hover);
1099
- }
1100
- .dot {
1101
- width: 8px;
1102
- height: 8px;
1103
- border-radius: 50%;
1104
- flex-shrink: 0;
1105
- opacity: 0.4;
1106
- }
1107
- button.on .dot {
1108
- opacity: 1;
1109
- }
1110
- `];
1111
- }
1112
- _toggle(id) {
1113
- this.dispatchEvent(new CustomEvent("layer-toggle", {
1114
- detail: { id },
1115
- bubbles: true,
1116
- composed: true
1117
- }));
1118
- }
1119
- render() {
1120
- return b`${this.layers.map((l) => b`
1121
- <button
1122
- class=${l.enabled ? "on" : ""}
1123
- title=${l.enabled ? `Hide ${l.label}` : `Show ${l.label}`}
1124
- @click=${() => this._toggle(l.id)}
1125
- >
1126
- <span
1127
- class="dot"
1128
- style=${"background:#" + l.color.toString(16).padStart(6, "0")}
1129
- ></span>
1130
- ${l.label}
1131
- </button>
1132
- `)}`;
1133
- }
1134
- };
1135
- __decorate([n$3({ type: Array })], TimelineLayers.prototype, "layers", void 0);
1136
- TimelineLayers = __decorate([t$2("timeline-layers")], TimelineLayers);
1137
- //#endregion
1138
- //#region node_modules/.pnpm/lit-html@3.3.3/node_modules/lit-html/directive-helpers.js
1139
- /**
1140
- * @license
1141
- * Copyright 2020 Google LLC
1142
- * SPDX-License-Identifier: BSD-3-Clause
1143
- */ var { I: t$1 } = j$1, i$1 = (o) => o, r$1 = (o) => void 0 === o.strings, s$1 = () => document.createComment(""), v = (o, n, e) => {
1047
+ }, { I: t$1 } = j$1, i$1 = (o) => o, r$1 = (o) => void 0 === o.strings, s$1 = () => document.createComment(""), v = (o, n, e) => {
1048
+ /**
1049
+ * @license
1050
+ * Copyright 2020 Google LLC
1051
+ * SPDX-License-Identifier: BSD-3-Clause
1052
+ */
1144
1053
  const l = o._$AA.parentNode, d = void 0 === n ? o._$AB : n._$AA;
1145
1054
  if (void 0 === e) e = new t$1(l.insertBefore(s$1(), d), l.insertBefore(s$1(), d), o, o.options);
1146
1055
  else {
@@ -1281,13 +1190,488 @@ var n = e$1(class extends f {
1281
1190
  get lt() {
1282
1191
  return "function" == typeof this.G ? o.get(this.ht ?? globalThis)?.get(this.G) : this.G?.value;
1283
1192
  }
1284
- disconnected() {
1285
- this.lt === this.ct && this.rt(void 0);
1193
+ disconnected() {
1194
+ this.lt === this.ct && this.rt(void 0);
1195
+ }
1196
+ reconnected() {
1197
+ this.rt(this.ct);
1198
+ }
1199
+ });
1200
+ //#endregion
1201
+ //#region src/lib/timeline/derive.ts
1202
+ /**
1203
+ * @license
1204
+ * Copyright 2026 Google LLC
1205
+ * SPDX-License-Identifier: BSD-3-Clause
1206
+ */
1207
+ /** Layer whose events describe Lit update ticks. */
1208
+ var LIFECYCLE_LAYER_ID = "lit-lifecycle";
1209
+ /** Layers whose events are developer input, for {@link attributeInput}. */
1210
+ var INPUT_LAYER_IDS = ["mouse", "keyboard"];
1211
+ /** The phase that brackets an entire update tick. */
1212
+ var ROOT_PHASE = "performUpdate";
1213
+ var START_SUFFIX = ":start";
1214
+ var END_SUFFIX = ":end";
1215
+ /** Reads `data.changed` defensively — `data` is `unknown` on the wire. */
1216
+ var changedOf = (event) => {
1217
+ const data = event.data;
1218
+ if (data === null || typeof data !== "object") return void 0;
1219
+ const changed = data.changed;
1220
+ if (!Array.isArray(changed)) return void 0;
1221
+ const keys = changed.filter((key) => typeof key === "string");
1222
+ return keys.length > 0 ? keys : void 0;
1223
+ };
1224
+ var pointSpan = (event, index) => ({
1225
+ layerId: event.layerId,
1226
+ key: `${event.layerId}:point:${index}`,
1227
+ name: event.title ?? event.layerId,
1228
+ start: event.time,
1229
+ changed: changedOf(event),
1230
+ subtitle: event.subtitle,
1231
+ logType: event.logType,
1232
+ meta: event.meta,
1233
+ events: [event]
1234
+ });
1235
+ /**
1236
+ * Collapses `:start` / `:end` event pairs into spans, passing anything else
1237
+ * through as a point span.
1238
+ *
1239
+ * Pairing is keyed on **layer + groupId + phase name**, never on `groupId`
1240
+ * alone: a whole update tick shares one groupId (`runtime/timeline/
1241
+ * lifecycle.ts` builds it as `${elementId}:${tick}`), so matching on the id
1242
+ * would marry `willUpdate:start` to `performUpdate:end`. Each phase occurs at
1243
+ * most once per tick, which makes the triple unique.
1244
+ *
1245
+ * Unmatched halves survive rather than disappear — the buffer is a bounded
1246
+ * ring, so its oldest end and its newest start routinely have no partner, and
1247
+ * an update that is still running has a genuinely open span.
1248
+ */
1249
+ var toSpans = (events) => {
1250
+ const spans = [];
1251
+ const open = /* @__PURE__ */ new Map();
1252
+ events.forEach((event, index) => {
1253
+ const title = event.title ?? event.layerId;
1254
+ const isStart = title.endsWith(START_SUFFIX);
1255
+ const isEnd = !isStart && title.endsWith(END_SUFFIX);
1256
+ if (event.groupId === void 0 || !isStart && !isEnd) {
1257
+ spans.push(pointSpan(event, index));
1258
+ return;
1259
+ }
1260
+ const name = title.slice(0, -(isStart ? 6 : 4));
1261
+ const key = `${event.layerId}:${event.groupId}:${name}`;
1262
+ if (isStart) {
1263
+ const span = {
1264
+ layerId: event.layerId,
1265
+ key,
1266
+ name,
1267
+ groupId: event.groupId,
1268
+ start: event.time,
1269
+ changed: changedOf(event),
1270
+ subtitle: event.subtitle,
1271
+ logType: event.logType,
1272
+ meta: event.meta,
1273
+ events: [event]
1274
+ };
1275
+ open.set(key, span);
1276
+ spans.push(span);
1277
+ return;
1278
+ }
1279
+ const span = open.get(key);
1280
+ if (span === void 0) {
1281
+ spans.push(pointSpan(event, index));
1282
+ return;
1283
+ }
1284
+ open.delete(key);
1285
+ span.events.push(event);
1286
+ if (event.time >= span.start) {
1287
+ span.end = event.time;
1288
+ span.duration = event.time - span.start;
1289
+ }
1290
+ span.changed ??= changedOf(event);
1291
+ });
1292
+ return spans.sort((a, b) => a.start - b.start);
1293
+ };
1294
+ /**
1295
+ * Groups lifecycle spans into update ticks.
1296
+ *
1297
+ * `duration` is taken from the `performUpdate` span alone, never from a sum of
1298
+ * the phases: `willUpdate` / `update` / `updated` all run *inside*
1299
+ * `performUpdate`, so summing counts the same milliseconds several times over.
1300
+ *
1301
+ * `changed` is the union across the tick's phases because only the phases that
1302
+ * receive a `PropertyValues` argument carry it — `performUpdate` takes none,
1303
+ * so the bracketing span is always silent about the reason.
1304
+ */
1305
+ var toUpdateCycles = (spans) => {
1306
+ const byGroup = /* @__PURE__ */ new Map();
1307
+ for (const span of spans) {
1308
+ const elementId = span.meta?.elementId;
1309
+ if (span.layerId !== LIFECYCLE_LAYER_ID || span.groupId === void 0 || elementId === void 0) continue;
1310
+ const key = String(span.groupId);
1311
+ let cycle = byGroup.get(key);
1312
+ if (cycle === void 0) {
1313
+ cycle = {
1314
+ key,
1315
+ elementId,
1316
+ tagName: span.meta?.tagName ?? "unknown",
1317
+ source: span.meta?.source,
1318
+ start: span.start,
1319
+ changed: [],
1320
+ phases: []
1321
+ };
1322
+ byGroup.set(key, cycle);
1323
+ }
1324
+ cycle.phases.push(span);
1325
+ if (span.start < cycle.start) cycle.start = span.start;
1326
+ if (span.name === ROOT_PHASE) cycle.duration = span.duration;
1327
+ for (const changedKey of span.changed ?? []) if (!cycle.changed.includes(changedKey)) cycle.changed.push(changedKey);
1328
+ }
1329
+ return [...byGroup.values()].sort((a, b) => a.start - b.start);
1330
+ };
1331
+ /**
1332
+ * Totals update cycles per component tag.
1333
+ *
1334
+ * Grouped by tag rather than by instance because the question this answers is
1335
+ * "which *component* re-renders too much" — a list of 200 rows each updating
1336
+ * twice is one component's problem, not 200. The instance ids are kept so the
1337
+ * caller can still drill in.
1338
+ */
1339
+ var rollup = (cycles) => {
1340
+ const byTag = /* @__PURE__ */ new Map();
1341
+ for (const cycle of cycles) {
1342
+ let record = byTag.get(cycle.tagName);
1343
+ if (record === void 0) {
1344
+ record = {
1345
+ entry: {
1346
+ tagName: cycle.tagName,
1347
+ elementIds: [],
1348
+ updates: 0,
1349
+ totalMs: 0,
1350
+ maxMs: 0,
1351
+ reasons: [],
1352
+ source: cycle.source
1353
+ },
1354
+ reasons: /* @__PURE__ */ new Map()
1355
+ };
1356
+ byTag.set(cycle.tagName, record);
1357
+ }
1358
+ const { entry, reasons } = record;
1359
+ entry.updates++;
1360
+ if (!entry.elementIds.includes(cycle.elementId)) entry.elementIds.push(cycle.elementId);
1361
+ entry.source ??= cycle.source;
1362
+ if (cycle.duration !== void 0) {
1363
+ entry.totalMs += cycle.duration;
1364
+ if (cycle.duration > entry.maxMs) entry.maxMs = cycle.duration;
1365
+ }
1366
+ for (const key of cycle.changed) reasons.set(key, (reasons.get(key) ?? 0) + 1);
1367
+ }
1368
+ const entries = [];
1369
+ for (const { entry, reasons } of byTag.values()) {
1370
+ entry.reasons = [...reasons].map(([key, count]) => ({
1371
+ key,
1372
+ count
1373
+ })).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
1374
+ entries.push(entry);
1375
+ }
1376
+ return entries.sort((a, b) => b.totalMs - a.totalMs || b.updates - a.updates);
1377
+ };
1378
+ /**
1379
+ * Attributes each update cycle to the input event that preceded it, within
1380
+ * `windowMs`.
1381
+ *
1382
+ * This is what the mouse and keyboard layers are for. On their own they
1383
+ * duplicate what the browser's own event log already shows; joined to the
1384
+ * updates they precede they answer the question the raw layers only imply —
1385
+ * an update with a cause is an interaction, and an update without one is a
1386
+ * timer, a signal, or a stray `requestUpdate()`.
1387
+ *
1388
+ * Returns a new array; cycles with no cause are returned as-is.
1389
+ */
1390
+ var attributeInput = (cycles, events, windowMs = 200) => {
1391
+ const inputs = events.filter((event) => INPUT_LAYER_IDS.includes(event.layerId)).sort((a, b) => a.time - b.time);
1392
+ if (inputs.length === 0) return [...cycles];
1393
+ let cursor = 0;
1394
+ return cycles.map((cycle) => {
1395
+ while (cursor + 1 < inputs.length && inputs[cursor + 1].time <= cycle.start) cursor++;
1396
+ const candidate = inputs[cursor];
1397
+ const delta = cycle.start - candidate.time;
1398
+ if (delta < 0 || delta > windowMs) return cycle;
1399
+ return {
1400
+ ...cycle,
1401
+ cause: {
1402
+ layerId: candidate.layerId,
1403
+ type: candidate.title ?? candidate.layerId,
1404
+ detail: candidate.subtitle,
1405
+ time: candidate.time
1406
+ }
1407
+ };
1408
+ });
1409
+ };
1410
+ //#endregion
1411
+ //#region src/lib/timeline/filter.ts
1412
+ /**
1413
+ * @license
1414
+ * Copyright 2026 Google LLC
1415
+ * SPDX-License-Identifier: BSD-3-Clause
1416
+ */
1417
+ /**
1418
+ * Compiles the regex box. An empty box matches everything. A pattern that
1419
+ * does not compile (the user is mid-typing `foo(`) disables the filter rather
1420
+ * than hiding every row, and is reported as `invalid` so the input can say so.
1421
+ */
1422
+ var compileRegex = (source) => {
1423
+ if (source === "") return {
1424
+ re: null,
1425
+ invalid: false
1426
+ };
1427
+ try {
1428
+ return {
1429
+ re: new RegExp(source, "i"),
1430
+ invalid: false
1431
+ };
1432
+ } catch {
1433
+ return {
1434
+ re: null,
1435
+ invalid: true
1436
+ };
1437
+ }
1438
+ };
1439
+ /** Text the regex filter searches: element tag, name, subtitle and the
1440
+ * changed property keys (the reason is worth searching for by name). */
1441
+ var spanHaystack = (span) => `${span.meta?.tagName ?? ""} ${span.name} ${span.subtitle ?? ""} ${(span.changed ?? []).join(" ")}`;
1442
+ /**
1443
+ * Applies the element and regex filters. `re` comes from {@link compileRegex}
1444
+ * so a caller filtering on every keystroke compiles once, not per span.
1445
+ */
1446
+ var filterSpans = (spans, elementId, re) => spans.filter((span) => (elementId === null || span.meta?.elementId === elementId) && (re === null || re.test(spanHaystack(span))));
1447
+ /** Distinct elements (by stable id) seen across the recorded events. */
1448
+ var listElements = (events) => {
1449
+ const seen = /* @__PURE__ */ new Map();
1450
+ for (const ev of events) {
1451
+ const id = ev.meta?.elementId;
1452
+ if (id != null && !seen.has(id)) seen.set(id, ev.meta?.tagName ?? "unknown");
1453
+ }
1454
+ return [...seen].map(([id, tag]) => ({
1455
+ id,
1456
+ tag
1457
+ }));
1458
+ };
1459
+ //#endregion
1460
+ //#region \0@oxc-project+runtime@0.149.0/helpers/esm/decorate.js
1461
+ function __decorate(decorators, target, key, desc) {
1462
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1463
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1464
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1465
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1466
+ }
1467
+ //#endregion
1468
+ //#region src/lib/segmented-tabs.ts
1469
+ /**
1470
+ * @license
1471
+ * Copyright 2026 Google LLC
1472
+ * SPDX-License-Identifier: BSD-3-Clause
1473
+ */
1474
+ var SegmentedTabs = class SegmentedTabs extends i$2 {
1475
+ static {
1476
+ this.styles = [tokens, i$5`
1477
+ :host {
1478
+ display: flex;
1479
+ gap: var(--lit-devtools-space-1);
1480
+ align-self: stretch;
1481
+ }
1482
+ button {
1483
+ appearance: none;
1484
+ display: flex;
1485
+ align-items: center;
1486
+ gap: var(--lit-devtools-space-2);
1487
+ border: 0;
1488
+ background: none;
1489
+ font: inherit;
1490
+ color: var(--lit-devtools-text-muted);
1491
+ padding: 0 var(--lit-devtools-space-5);
1492
+ cursor: pointer;
1493
+ border-bottom: 2px solid transparent;
1494
+ white-space: nowrap;
1495
+ transition:
1496
+ color var(--lit-devtools-dur-fast) var(--lit-devtools-ease-standard),
1497
+ border-color var(--lit-devtools-dur-fast)
1498
+ var(--lit-devtools-ease-standard),
1499
+ background var(--lit-devtools-dur-fast)
1500
+ var(--lit-devtools-ease-standard);
1501
+ }
1502
+ button:hover {
1503
+ color: var(--lit-devtools-text);
1504
+ background: var(--lit-devtools-surface-hover);
1505
+ }
1506
+ button.active {
1507
+ color: var(--lit-devtools-accent);
1508
+ border-bottom-color: var(--lit-devtools-accent);
1509
+ }
1510
+ button:focus-visible {
1511
+ outline: 2px solid var(--lit-devtools-accent-ring);
1512
+ outline-offset: -2px;
1513
+ }
1514
+ button svg {
1515
+ width: 18px;
1516
+ height: 18px;
1517
+ flex-shrink: 0;
1518
+ }
1519
+ .badge {
1520
+ flex-shrink: 0;
1521
+ min-width: 16px;
1522
+ padding: 0 var(--lit-devtools-space-1);
1523
+ border-radius: var(--lit-devtools-radius-pill);
1524
+ background: var(--lit-devtools-error-soft);
1525
+ color: var(--lit-devtools-error);
1526
+ font-size: var(--lit-devtools-text-xs);
1527
+ line-height: 16px;
1528
+ text-align: center;
1529
+ }
1530
+ :host([size='sm']) button {
1531
+ font-size: var(--lit-devtools-text-xs);
1532
+ padding: 0 var(--lit-devtools-space-4);
1533
+ }
1534
+ :host([size='md']) button,
1535
+ :host(:not([size])) button {
1536
+ font-size: var(--lit-devtools-text-sm);
1537
+ padding: 0 var(--lit-devtools-space-5);
1538
+ }
1539
+ `];
1540
+ }
1541
+ constructor() {
1542
+ super();
1543
+ this.items = [];
1544
+ this.value = "";
1545
+ this.size = "md";
1546
+ }
1547
+ _select(id) {
1548
+ this.value = id;
1549
+ this.dispatchEvent(new CustomEvent("change", {
1550
+ detail: { value: id },
1551
+ bubbles: true,
1552
+ composed: true
1553
+ }));
1554
+ }
1555
+ render() {
1556
+ return b`
1557
+ ${this.items.map((item) => b`
1558
+ <button
1559
+ role="tab"
1560
+ aria-selected=${item.id === this.value}
1561
+ class=${item.id === this.value ? "active" : ""}
1562
+ @click=${() => this._select(item.id)}
1563
+ >
1564
+ ${item.icon ? b`<svg
1565
+ viewBox="0 0 256 256"
1566
+ fill="currentColor"
1567
+ aria-hidden="true"
1568
+ >
1569
+ <path d=${item.icon} />
1570
+ </svg>` : A}
1571
+ ${item.label}
1572
+ ${item.badge ? b`<span class="badge">${item.badge}</span>` : A}
1573
+ </button>
1574
+ `)}
1575
+ `;
1576
+ }
1577
+ };
1578
+ __decorate([n$3({ type: Array })], SegmentedTabs.prototype, "items", void 0);
1579
+ __decorate([n$3()], SegmentedTabs.prototype, "value", void 0);
1580
+ __decorate([n$3()], SegmentedTabs.prototype, "size", void 0);
1581
+ SegmentedTabs = __decorate([t$2("segmented-tabs")], SegmentedTabs);
1582
+ //#endregion
1583
+ //#region src/panel/timeline-layers.ts
1584
+ /**
1585
+ * @license
1586
+ * Copyright 2026 Google LLC
1587
+ * SPDX-License-Identifier: BSD-3-Clause
1588
+ */
1589
+ /** A layer's `0xRRGGBB` colour as a CSS hex string. */
1590
+ var hexColor = (color) => "#" + color.toString(16).padStart(6, "0");
1591
+ /** CSS colour of layer `id`, grey for a layer `layers` does not know. */
1592
+ var layerColor = (layers, id) => {
1593
+ const layer = layers.find((l) => l.id === id);
1594
+ return layer ? hexColor(layer.color) : "#888";
1595
+ };
1596
+ var TimelineLayers = class TimelineLayers extends i$2 {
1597
+ constructor(..._args) {
1598
+ super(..._args);
1599
+ this.layers = [];
1600
+ this.caption = "";
1601
+ }
1602
+ static {
1603
+ this.styles = [tokens, i$5`
1604
+ :host {
1605
+ display: flex;
1606
+ gap: var(--lit-devtools-space-2);
1607
+ padding: var(--lit-devtools-space-3) var(--lit-devtools-space-5);
1608
+ border-bottom: 1px solid var(--lit-devtools-border);
1609
+ background: var(--lit-devtools-surface-low);
1610
+ flex-shrink: 0;
1611
+ flex-wrap: wrap;
1612
+ }
1613
+ button {
1614
+ display: flex;
1615
+ align-items: center;
1616
+ gap: var(--lit-devtools-space-2);
1617
+ padding: var(--lit-devtools-space-2) var(--lit-devtools-space-4);
1618
+ border-radius: var(--lit-devtools-radius-pill);
1619
+ background: var(--lit-devtools-surface-elevated);
1620
+ border: 1px solid transparent;
1621
+ color: var(--lit-devtools-text-secondary);
1622
+ font-size: var(--lit-devtools-text-2xs);
1623
+ cursor: pointer;
1624
+ user-select: none;
1625
+ transition: opacity var(--lit-devtools-dur-fast)
1626
+ var(--lit-devtools-ease-standard);
1627
+ }
1628
+ button.on {
1629
+ color: var(--lit-devtools-text);
1630
+ }
1631
+ button:hover {
1632
+ background: var(--lit-devtools-surface-hover);
1633
+ }
1634
+ .caption {
1635
+ align-self: center;
1636
+ color: var(--lit-devtools-text-muted);
1637
+ font-size: var(--lit-devtools-text-2xs);
1638
+ margin-right: var(--lit-devtools-space-2);
1639
+ }
1640
+ .dot {
1641
+ width: 8px;
1642
+ height: 8px;
1643
+ border-radius: 50%;
1644
+ flex-shrink: 0;
1645
+ opacity: 0.4;
1646
+ }
1647
+ button.on .dot {
1648
+ opacity: 1;
1649
+ }
1650
+ `];
1651
+ }
1652
+ _toggle(id) {
1653
+ this.dispatchEvent(new CustomEvent("layer-toggle", {
1654
+ detail: { id },
1655
+ bubbles: true,
1656
+ composed: true
1657
+ }));
1286
1658
  }
1287
- reconnected() {
1288
- this.rt(this.ct);
1659
+ render() {
1660
+ return b`${this.caption ? b`<span class="caption">${this.caption}</span>` : A}${this.layers.map((l) => b`
1661
+ <button
1662
+ class=${l.enabled ? "on" : ""}
1663
+ title=${l.enabled ? `Hide ${l.label}` : `Show ${l.label}`}
1664
+ @click=${() => this._toggle(l.id)}
1665
+ >
1666
+ <span class="dot" style=${"background:" + hexColor(l.color)}></span>
1667
+ ${l.label}
1668
+ </button>
1669
+ `)}`;
1289
1670
  }
1290
- });
1671
+ };
1672
+ __decorate([n$3({ type: Array })], TimelineLayers.prototype, "layers", void 0);
1673
+ __decorate([n$3()], TimelineLayers.prototype, "caption", void 0);
1674
+ TimelineLayers = __decorate([t$2("timeline-layers")], TimelineLayers);
1291
1675
  //#endregion
1292
1676
  //#region node_modules/.pnpm/lit-html@3.3.3/node_modules/lit-html/directives/repeat.js
1293
1677
  /**
@@ -2223,264 +2607,54 @@ var VirtualizeDirective = class extends f {
2223
2607
  }
2224
2608
  update(part, [config]) {
2225
2609
  this._setFunctions(config);
2226
- const itemsChanged = this._items !== config.items;
2227
- this._items = config.items || [];
2228
- if (this._virtualizer) this._updateVirtualizerConfig(part, config);
2229
- else this._initialize(part, config);
2230
- return itemsChanged ? E : this.render();
2231
- }
2232
- async _updateVirtualizerConfig(part, config) {
2233
- if (!await this._virtualizer.updateLayoutConfig(config.layout || {})) {
2234
- const hostElement = part.parentNode;
2235
- this._makeVirtualizer(hostElement, config);
2236
- }
2237
- this._virtualizer.items = this._items;
2238
- }
2239
- _setFunctions(config) {
2240
- const { renderItem, keyFunction } = config;
2241
- if (renderItem) this._renderItem = (item, idx) => renderItem(item, idx + this._first);
2242
- if (keyFunction) this._keyFunction = (item, idx) => keyFunction(item, idx + this._first);
2243
- }
2244
- _makeVirtualizer(hostElement, config) {
2245
- if (this._virtualizer) this._virtualizer.disconnected();
2246
- const { layout, scroller, items } = config;
2247
- this._virtualizer = new Virtualizer({
2248
- hostElement,
2249
- layout,
2250
- scroller
2251
- });
2252
- this._virtualizer.items = items;
2253
- this._virtualizer.connected();
2254
- }
2255
- _initialize(part, config) {
2256
- const hostElement = part.parentNode;
2257
- if (hostElement && hostElement.nodeType === 1) {
2258
- hostElement.addEventListener("rangeChanged", (e) => {
2259
- this._first = e.first;
2260
- this._last = e.last;
2261
- this.setValue(this.render());
2262
- });
2263
- this._makeVirtualizer(hostElement, config);
2264
- }
2265
- }
2266
- disconnected() {
2267
- this._virtualizer?.disconnected();
2268
- }
2269
- reconnected() {
2270
- this._virtualizer?.connected();
2271
- }
2272
- };
2273
- var virtualize = e$1(VirtualizeDirective);
2274
- //#endregion
2275
- //#region src/lib/timeline/derive.ts
2276
- /**
2277
- * @license
2278
- * Copyright 2026 Google LLC
2279
- * SPDX-License-Identifier: BSD-3-Clause
2280
- */
2281
- /** Layer whose events describe Lit update ticks. */
2282
- var LIFECYCLE_LAYER_ID = "lit-lifecycle";
2283
- /** Layers whose events are developer input, for {@link attributeInput}. */
2284
- var INPUT_LAYER_IDS = ["mouse", "keyboard"];
2285
- /** The phase that brackets an entire update tick. */
2286
- var ROOT_PHASE = "performUpdate";
2287
- var START_SUFFIX = ":start";
2288
- var END_SUFFIX = ":end";
2289
- /** Reads `data.changed` defensively — `data` is `unknown` on the wire. */
2290
- var changedOf = (event) => {
2291
- const data = event.data;
2292
- if (data === null || typeof data !== "object") return void 0;
2293
- const changed = data.changed;
2294
- if (!Array.isArray(changed)) return void 0;
2295
- const keys = changed.filter((key) => typeof key === "string");
2296
- return keys.length > 0 ? keys : void 0;
2297
- };
2298
- var pointSpan = (event, index) => ({
2299
- layerId: event.layerId,
2300
- key: `${event.layerId}:point:${index}`,
2301
- name: event.title ?? event.layerId,
2302
- start: event.time,
2303
- changed: changedOf(event),
2304
- subtitle: event.subtitle,
2305
- logType: event.logType,
2306
- meta: event.meta,
2307
- events: [event]
2308
- });
2309
- /**
2310
- * Collapses `:start` / `:end` event pairs into spans, passing anything else
2311
- * through as a point span.
2312
- *
2313
- * Pairing is keyed on **layer + groupId + phase name**, never on `groupId`
2314
- * alone: a whole update tick shares one groupId (`runtime/timeline/
2315
- * lifecycle.ts` builds it as `${elementId}:${tick}`), so matching on the id
2316
- * would marry `willUpdate:start` to `performUpdate:end`. Each phase occurs at
2317
- * most once per tick, which makes the triple unique.
2318
- *
2319
- * Unmatched halves survive rather than disappear — the buffer is a bounded
2320
- * ring, so its oldest end and its newest start routinely have no partner, and
2321
- * an update that is still running has a genuinely open span.
2322
- */
2323
- var toSpans = (events) => {
2324
- const spans = [];
2325
- const open = /* @__PURE__ */ new Map();
2326
- events.forEach((event, index) => {
2327
- const title = event.title ?? event.layerId;
2328
- const isStart = title.endsWith(START_SUFFIX);
2329
- const isEnd = !isStart && title.endsWith(END_SUFFIX);
2330
- if (event.groupId === void 0 || !isStart && !isEnd) {
2331
- spans.push(pointSpan(event, index));
2332
- return;
2333
- }
2334
- const name = title.slice(0, -(isStart ? 6 : 4));
2335
- const key = `${event.layerId}:${event.groupId}:${name}`;
2336
- if (isStart) {
2337
- const span = {
2338
- layerId: event.layerId,
2339
- key,
2340
- name,
2341
- groupId: event.groupId,
2342
- start: event.time,
2343
- changed: changedOf(event),
2344
- subtitle: event.subtitle,
2345
- logType: event.logType,
2346
- meta: event.meta,
2347
- events: [event]
2348
- };
2349
- open.set(key, span);
2350
- spans.push(span);
2351
- return;
2352
- }
2353
- const span = open.get(key);
2354
- if (span === void 0) {
2355
- spans.push(pointSpan(event, index));
2356
- return;
2357
- }
2358
- open.delete(key);
2359
- span.events.push(event);
2360
- if (event.time >= span.start) {
2361
- span.end = event.time;
2362
- span.duration = event.time - span.start;
2363
- }
2364
- span.changed ??= changedOf(event);
2365
- });
2366
- return spans.sort((a, b) => a.start - b.start);
2367
- };
2368
- /**
2369
- * Groups lifecycle spans into update ticks.
2370
- *
2371
- * `duration` is taken from the `performUpdate` span alone, never from a sum of
2372
- * the phases: `willUpdate` / `update` / `updated` all run *inside*
2373
- * `performUpdate`, so summing counts the same milliseconds several times over.
2374
- *
2375
- * `changed` is the union across the tick's phases because only the phases that
2376
- * receive a `PropertyValues` argument carry it — `performUpdate` takes none,
2377
- * so the bracketing span is always silent about the reason.
2378
- */
2379
- var toUpdateCycles = (spans) => {
2380
- const byGroup = /* @__PURE__ */ new Map();
2381
- for (const span of spans) {
2382
- const elementId = span.meta?.elementId;
2383
- if (span.layerId !== LIFECYCLE_LAYER_ID || span.groupId === void 0 || elementId === void 0) continue;
2384
- const key = String(span.groupId);
2385
- let cycle = byGroup.get(key);
2386
- if (cycle === void 0) {
2387
- cycle = {
2388
- key,
2389
- elementId,
2390
- tagName: span.meta?.tagName ?? "unknown",
2391
- source: span.meta?.source,
2392
- start: span.start,
2393
- changed: [],
2394
- phases: []
2395
- };
2396
- byGroup.set(key, cycle);
2397
- }
2398
- cycle.phases.push(span);
2399
- if (span.start < cycle.start) cycle.start = span.start;
2400
- if (span.name === ROOT_PHASE) cycle.duration = span.duration;
2401
- for (const changedKey of span.changed ?? []) if (!cycle.changed.includes(changedKey)) cycle.changed.push(changedKey);
2402
- }
2403
- return [...byGroup.values()].sort((a, b) => a.start - b.start);
2404
- };
2405
- /**
2406
- * Totals update cycles per component tag.
2407
- *
2408
- * Grouped by tag rather than by instance because the question this answers is
2409
- * "which *component* re-renders too much" — a list of 200 rows each updating
2410
- * twice is one component's problem, not 200. The instance ids are kept so the
2411
- * caller can still drill in.
2412
- */
2413
- var rollup = (cycles) => {
2414
- const byTag = /* @__PURE__ */ new Map();
2415
- for (const cycle of cycles) {
2416
- let record = byTag.get(cycle.tagName);
2417
- if (record === void 0) {
2418
- record = {
2419
- entry: {
2420
- tagName: cycle.tagName,
2421
- elementIds: [],
2422
- updates: 0,
2423
- totalMs: 0,
2424
- maxMs: 0,
2425
- reasons: [],
2426
- source: cycle.source
2427
- },
2428
- reasons: /* @__PURE__ */ new Map()
2429
- };
2430
- byTag.set(cycle.tagName, record);
2610
+ const itemsChanged = this._items !== config.items;
2611
+ this._items = config.items || [];
2612
+ if (this._virtualizer) this._updateVirtualizerConfig(part, config);
2613
+ else this._initialize(part, config);
2614
+ return itemsChanged ? E : this.render();
2615
+ }
2616
+ async _updateVirtualizerConfig(part, config) {
2617
+ if (!await this._virtualizer.updateLayoutConfig(config.layout || {})) {
2618
+ const hostElement = part.parentNode;
2619
+ this._makeVirtualizer(hostElement, config);
2431
2620
  }
2432
- const { entry, reasons } = record;
2433
- entry.updates++;
2434
- if (!entry.elementIds.includes(cycle.elementId)) entry.elementIds.push(cycle.elementId);
2435
- entry.source ??= cycle.source;
2436
- if (cycle.duration !== void 0) {
2437
- entry.totalMs += cycle.duration;
2438
- if (cycle.duration > entry.maxMs) entry.maxMs = cycle.duration;
2621
+ this._virtualizer.items = this._items;
2622
+ }
2623
+ _setFunctions(config) {
2624
+ const { renderItem, keyFunction } = config;
2625
+ if (renderItem) this._renderItem = (item, idx) => renderItem(item, idx + this._first);
2626
+ if (keyFunction) this._keyFunction = (item, idx) => keyFunction(item, idx + this._first);
2627
+ }
2628
+ _makeVirtualizer(hostElement, config) {
2629
+ if (this._virtualizer) this._virtualizer.disconnected();
2630
+ const { layout, scroller, items } = config;
2631
+ this._virtualizer = new Virtualizer({
2632
+ hostElement,
2633
+ layout,
2634
+ scroller
2635
+ });
2636
+ this._virtualizer.items = items;
2637
+ this._virtualizer.connected();
2638
+ }
2639
+ _initialize(part, config) {
2640
+ const hostElement = part.parentNode;
2641
+ if (hostElement && hostElement.nodeType === 1) {
2642
+ hostElement.addEventListener("rangeChanged", (e) => {
2643
+ this._first = e.first;
2644
+ this._last = e.last;
2645
+ this.setValue(this.render());
2646
+ });
2647
+ this._makeVirtualizer(hostElement, config);
2439
2648
  }
2440
- for (const key of cycle.changed) reasons.set(key, (reasons.get(key) ?? 0) + 1);
2441
2649
  }
2442
- const entries = [];
2443
- for (const { entry, reasons } of byTag.values()) {
2444
- entry.reasons = [...reasons].map(([key, count]) => ({
2445
- key,
2446
- count
2447
- })).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
2448
- entries.push(entry);
2650
+ disconnected() {
2651
+ this._virtualizer?.disconnected();
2652
+ }
2653
+ reconnected() {
2654
+ this._virtualizer?.connected();
2449
2655
  }
2450
- return entries.sort((a, b) => b.totalMs - a.totalMs || b.updates - a.updates);
2451
- };
2452
- /**
2453
- * Attributes each update cycle to the input event that preceded it, within
2454
- * `windowMs`.
2455
- *
2456
- * This is what the mouse and keyboard layers are for. On their own they
2457
- * duplicate what the browser's own event log already shows; joined to the
2458
- * updates they precede they answer the question the raw layers only imply —
2459
- * an update with a cause is an interaction, and an update without one is a
2460
- * timer, a signal, or a stray `requestUpdate()`.
2461
- *
2462
- * Returns a new array; cycles with no cause are returned as-is.
2463
- */
2464
- var attributeInput = (cycles, events, windowMs = 200) => {
2465
- const inputs = events.filter((event) => INPUT_LAYER_IDS.includes(event.layerId)).sort((a, b) => a.time - b.time);
2466
- if (inputs.length === 0) return [...cycles];
2467
- let cursor = 0;
2468
- return cycles.map((cycle) => {
2469
- while (cursor + 1 < inputs.length && inputs[cursor + 1].time <= cycle.start) cursor++;
2470
- const candidate = inputs[cursor];
2471
- const delta = cycle.start - candidate.time;
2472
- if (delta < 0 || delta > windowMs) return cycle;
2473
- return {
2474
- ...cycle,
2475
- cause: {
2476
- layerId: candidate.layerId,
2477
- type: candidate.title ?? candidate.layerId,
2478
- detail: candidate.subtitle,
2479
- time: candidate.time
2480
- }
2481
- };
2482
- });
2483
2656
  };
2657
+ var virtualize = e$1(VirtualizeDirective);
2484
2658
  //#endregion
2485
2659
  //#region node_modules/.pnpm/devframe@1.0.0_@devframes+agentic@1.0.0_cac@7.0.0_srvx@1.0.5/node_modules/devframe/dist/utils/colors.mjs
2486
2660
  function makeColor(open, close) {
@@ -6835,7 +7009,7 @@ async function getDevframeRpcClient(options = {}) {
6835
7009
  bootstrapAuthSettled = true;
6836
7010
  });
6837
7011
  if (connectionMeta.mcp) __vitePreload(async () => {
6838
- const { setupBrowserAgentRpcBridge } = await import("./browser-agent-rpc-BXhoSh1z-CdtPhBL-.js");
7012
+ const { setupBrowserAgentRpcBridge } = await import("./browser-agent-rpc-BXhoSh1z-DXAVFObe.js");
6839
7013
  return { setupBrowserAgentRpcBridge };
6840
7014
  }, [], import.meta.url).then(({ setupBrowserAgentRpcBridge }) => {
6841
7015
  if (!closed) disposeBrowserAgentBridge = setupBrowserAgentRpcBridge(rpc);
@@ -6881,6 +7055,8 @@ var SOURCE_OVERLAY_EDITORS = [
6881
7055
  ];
6882
7056
  /** localStorage key holding the {@link SettingsOverride}. */
6883
7057
  var SETTINGS_OVERRIDE_LS_KEY = "lit-devtools-overrides";
7058
+ /** localStorage key holding the {@link OverrideBaselines}. */
7059
+ var OVERRIDE_BASELINES_LS_KEY = "lit-devtools-override-baselines";
6884
7060
  //#endregion
6885
7061
  //#region src/lib/devframe/protocol.ts
6886
7062
  /**
@@ -7023,12 +7199,142 @@ var openInEditor = async (file, line) => {
7023
7199
  }
7024
7200
  };
7025
7201
  //#endregion
7202
+ //#region src/panel/timeline-span-detail.ts
7203
+ /**
7204
+ * @license
7205
+ * Copyright 2026 Google LLC
7206
+ * SPDX-License-Identifier: BSD-3-Clause
7207
+ */
7208
+ var TimelineSpanDetail = class TimelineSpanDetail extends i$2 {
7209
+ constructor(..._args) {
7210
+ super(..._args);
7211
+ this.filterable = false;
7212
+ }
7213
+ static {
7214
+ this.styles = [tokens, i$5`
7215
+ :host {
7216
+ display: block;
7217
+ border-top: 1px solid var(--lit-devtools-border);
7218
+ background: var(--lit-devtools-surface-low);
7219
+ padding: var(--lit-devtools-space-5) var(--lit-devtools-space-5);
7220
+ font-size: var(--lit-devtools-text-2xs);
7221
+ font-family: var(--lit-devtools-font-mono);
7222
+ color: var(--lit-devtools-text-secondary);
7223
+ flex-shrink: 0;
7224
+ max-height: 130px;
7225
+ overflow-y: auto;
7226
+ }
7227
+ table {
7228
+ border-collapse: collapse;
7229
+ width: 100%;
7230
+ }
7231
+ td {
7232
+ padding: var(--lit-devtools-space-1) var(--lit-devtools-space-4)
7233
+ var(--lit-devtools-space-1) 0;
7234
+ vertical-align: top;
7235
+ }
7236
+ .key {
7237
+ color: var(--lit-devtools-text-muted);
7238
+ white-space: nowrap;
7239
+ }
7240
+ .val {
7241
+ color: var(--lit-devtools-text);
7242
+ word-break: break-all;
7243
+ }
7244
+ a {
7245
+ color: var(--lit-devtools-accent);
7246
+ text-decoration: none;
7247
+ cursor: pointer;
7248
+ }
7249
+ a:hover {
7250
+ text-decoration: underline;
7251
+ }
7252
+ .filter-link {
7253
+ margin-left: var(--lit-devtools-space-4);
7254
+ font-size: var(--lit-devtools-text-2xs);
7255
+ }
7256
+ `];
7257
+ }
7258
+ _emit(type, id) {
7259
+ this.dispatchEvent(new CustomEvent(type, {
7260
+ detail: { id },
7261
+ bubbles: true,
7262
+ composed: true
7263
+ }));
7264
+ }
7265
+ render() {
7266
+ const row = this.span;
7267
+ if (!row) return A;
7268
+ const { meta } = row;
7269
+ const src = meta?.source;
7270
+ const data = row.events[0]?.data;
7271
+ const id = meta?.elementId;
7272
+ return b`
7273
+ <table>
7274
+ <tr>
7275
+ <td class="key">layer</td>
7276
+ <td class="val">${row.layerId}</td>
7277
+ </tr>
7278
+ <tr>
7279
+ <td class="key">time</td>
7280
+ <td class="val">${row.start.toFixed(3)} ms</td>
7281
+ </tr>
7282
+ ${row.duration !== void 0 ? b`<tr>
7283
+ <td class="key">duration</td>
7284
+ <td class="val">${row.duration.toFixed(3)} ms</td>
7285
+ </tr>` : A}
7286
+ ${row.changed?.length ? b`<tr>
7287
+ <td class="key">changed</td>
7288
+ <td class="val">${row.changed.join(", ")}</td>
7289
+ </tr>` : A}
7290
+ ${meta?.tagName ? b`<tr>
7291
+ <td class="key">element</td>
7292
+ <td class="val">
7293
+ &lt;${meta.tagName}&gt; #${id}
7294
+ ${id != null && this.filterable ? b`<a
7295
+ class="filter-link"
7296
+ @click=${() => this._emit("element-filter", id)}
7297
+ >filter</a
7298
+ >` : A}${id != null ? b`<a
7299
+ class="filter-link"
7300
+ title="Open this element in the Components tab"
7301
+ @click=${() => this._emit("inspect-element", id)}
7302
+ >inspect</a
7303
+ >` : A}
7304
+ </td>
7305
+ </tr>` : A}
7306
+ ${src ? b`<tr>
7307
+ <td class="key">source</td>
7308
+ <td class="val">
7309
+ <a
7310
+ class="src-link"
7311
+ title="Open this file in your editor"
7312
+ @click=${() => openInEditor(src.file, src.line)}
7313
+ >${src.file}:${src.line}</a
7314
+ >
7315
+ </td>
7316
+ </tr>` : A}
7317
+ ${data != null ? b`<tr>
7318
+ <td class="key">data</td>
7319
+ <td class="val">${JSON.stringify(data)}</td>
7320
+ </tr>` : A}
7321
+ </table>
7322
+ `;
7323
+ }
7324
+ };
7325
+ __decorate([n$3({ attribute: false })], TimelineSpanDetail.prototype, "span", void 0);
7326
+ __decorate([n$3({ type: Boolean })], TimelineSpanDetail.prototype, "filterable", void 0);
7327
+ TimelineSpanDetail = __decorate([t$2("timeline-span-detail")], TimelineSpanDetail);
7328
+ //#endregion
7026
7329
  //#region src/panel/timeline-event-list.ts
7027
7330
  /**
7028
7331
  * @license
7029
7332
  * Copyright 2026 Google LLC
7030
7333
  * SPDX-License-Identifier: BSD-3-Clause
7031
7334
  */
7335
+ var RAW_KEY_PREFIX = "raw:";
7336
+ /** Whether `key` names a Raw-mode row rather than a collapsed span. */
7337
+ var isRawKey = (key) => key.startsWith(RAW_KEY_PREFIX);
7032
7338
  /**
7033
7339
  * Adapts one raw event to the row shape for the Raw toggle. Deliberately
7034
7340
  * drops `groupId`: pairing is what the collapsed mode is for, and without it
@@ -7036,7 +7342,7 @@ var openInEditor = async (file, line) => {
7036
7342
  */
7037
7343
  var rawRow = (event, index) => ({
7038
7344
  layerId: event.layerId,
7039
- key: `raw:${index}`,
7345
+ key: `${RAW_KEY_PREFIX}${index}`,
7040
7346
  name: event.title ?? event.layerId,
7041
7347
  start: event.time,
7042
7348
  subtitle: event.subtitle,
@@ -7054,16 +7360,15 @@ var TimelineEventList = class TimelineEventList extends i$2 {
7054
7360
  constructor(..._args) {
7055
7361
  super(..._args);
7056
7362
  this.events = [];
7363
+ this.spans = [];
7057
7364
  this.layers = [];
7058
- this._selectedKey = null;
7059
- this._elementFilter = null;
7060
- this._regex = "";
7365
+ this.selectedKey = null;
7366
+ this.elementFilter = null;
7367
+ this.regex = "";
7061
7368
  this._raw = false;
7062
7369
  this._scrollRef = e();
7063
- this._elementsCache = [];
7064
7370
  this._rowsCache = [];
7065
7371
  this._visibleCache = [];
7066
- this._regexInvalid = false;
7067
7372
  }
7068
7373
  static {
7069
7374
  this.styles = [tokens, i$5`
@@ -7083,31 +7388,6 @@ var TimelineEventList = class TimelineEventList extends i$2 {
7083
7388
  color: var(--lit-devtools-text-muted);
7084
7389
  flex-shrink: 0;
7085
7390
  }
7086
- .filterbar select {
7087
- background: var(--lit-devtools-surface-elevated);
7088
- color: var(--lit-devtools-text);
7089
- border: 1px solid var(--lit-devtools-border-strong);
7090
- border-radius: var(--lit-devtools-radius-sm);
7091
- padding: var(--lit-devtools-space-1) var(--lit-devtools-space-3);
7092
- font-size: var(--lit-devtools-text-2xs);
7093
- font-family: var(--lit-devtools-font-mono);
7094
- }
7095
- .filterbar input.regex {
7096
- background: var(--lit-devtools-surface-elevated);
7097
- color: var(--lit-devtools-text);
7098
- border: 1px solid var(--lit-devtools-border-strong);
7099
- border-radius: var(--lit-devtools-radius-sm);
7100
- padding: var(--lit-devtools-space-1) var(--lit-devtools-space-3);
7101
- font-size: var(--lit-devtools-text-2xs);
7102
- font-family: var(--lit-devtools-font-mono);
7103
- min-width: 140px;
7104
- }
7105
- .filterbar input.regex::placeholder {
7106
- color: var(--lit-devtools-text-muted);
7107
- }
7108
- .filterbar input.regex.invalid {
7109
- border-color: var(--lit-devtools-error);
7110
- }
7111
7391
  .filterbar button {
7112
7392
  background: var(--lit-devtools-surface-elevated);
7113
7393
  color: var(--lit-devtools-text-muted);
@@ -7209,70 +7489,11 @@ var TimelineEventList = class TimelineEventList extends i$2 {
7209
7489
  .dur.open {
7210
7490
  color: var(--lit-devtools-text-muted);
7211
7491
  }
7212
- .detail {
7213
- border-top: 1px solid var(--lit-devtools-border);
7214
- background: var(--lit-devtools-surface-low);
7215
- padding: var(--lit-devtools-space-5) var(--lit-devtools-space-5);
7216
- font-size: var(--lit-devtools-text-2xs);
7217
- font-family: var(--lit-devtools-font-mono);
7218
- color: var(--lit-devtools-text-secondary);
7219
- flex-shrink: 0;
7220
- max-height: 130px;
7221
- overflow-y: auto;
7222
- }
7223
- table {
7224
- border-collapse: collapse;
7225
- width: 100%;
7226
- }
7227
- td {
7228
- padding: var(--lit-devtools-space-1) var(--lit-devtools-space-4)
7229
- var(--lit-devtools-space-1) 0;
7230
- vertical-align: top;
7231
- }
7232
- .key {
7233
- color: var(--lit-devtools-text-muted);
7234
- white-space: nowrap;
7235
- }
7236
- .val {
7237
- color: var(--lit-devtools-text);
7238
- word-break: break-all;
7239
- }
7240
- a {
7241
- color: var(--lit-devtools-accent);
7242
- text-decoration: none;
7243
- }
7244
- a:hover {
7245
- text-decoration: underline;
7246
- }
7247
- .src-link {
7248
- cursor: pointer;
7249
- }
7250
- .filter-link {
7251
- cursor: pointer;
7252
- margin-left: var(--lit-devtools-space-4);
7253
- font-size: var(--lit-devtools-text-2xs);
7254
- }
7255
7492
  `];
7256
7493
  }
7257
7494
  willUpdate(changed) {
7258
- if (changed.has("events") || changed.has("_raw")) {
7259
- this._rowsCache = this._raw ? this.events.map(rawRow) : toSpans(this.events);
7260
- if (this._selectedKey !== null && !this._rowsCache.some((row) => row.key === this._selectedKey)) this._selectedKey = null;
7261
- }
7262
- if (changed.has("events")) {
7263
- this._elementsCache = this._computeElements();
7264
- if (this._elementFilter !== null && !this.events.some((ev) => ev.meta?.elementId === this._elementFilter)) this._elementFilter = null;
7265
- }
7266
- if (changed.has("events") || changed.has("_raw") || changed.has("layers") || changed.has("_elementFilter") || changed.has("_regex")) {
7267
- let re = null;
7268
- this._regexInvalid = false;
7269
- if (this._regex !== "") try {
7270
- re = new RegExp(this._regex, "i");
7271
- } catch {
7272
- this._regexInvalid = true;
7273
- }
7274
- this._visibleCache = this._rowsCache.filter((row) => this._isVisible(row) && (re === null || re.test(this._haystack(row))));
7275
- }
7495
+ if (changed.has("events") || changed.has("spans") || changed.has("_raw")) this._rowsCache = this._raw ? this.events.map(rawRow) : this.spans;
7496
+ if (changed.has("events") || changed.has("spans") || changed.has("_raw") || changed.has("layers") || changed.has("elementFilter") || changed.has("regex")) this._visibleCache = filterSpans(this._rowsCache.filter((row) => this._layerOn(row)), this.elementFilter, compileRegex(this.regex).re);
7276
7497
  }
7277
7498
  updated(changed) {
7278
7499
  if (changed.has("events")) {
@@ -7280,89 +7501,52 @@ var TimelineEventList = class TimelineEventList extends i$2 {
7280
7501
  if (el) el.scrollTop = el.scrollHeight;
7281
7502
  }
7282
7503
  }
7283
- _colorOf(layerId) {
7284
- const l = this.layers.find((l) => l.id === layerId);
7285
- if (!l) return "#888";
7286
- return "#" + l.color.toString(16).padStart(6, "0");
7287
- }
7288
- _isVisible(row) {
7289
- const l = this.layers.find((l) => l.id === row.layerId);
7290
- if (l && !l.enabled) return false;
7291
- if (this._elementFilter !== null && row.meta?.elementId !== this._elementFilter) return false;
7292
- return true;
7293
- }
7294
- /** Distinct elements (by stable id) seen across the recorded events. */
7295
- _computeElements() {
7296
- const seen = /* @__PURE__ */ new Map();
7297
- for (const ev of this.events) {
7298
- const id = ev.meta?.elementId;
7299
- if (id != null && !seen.has(id)) seen.set(id, ev.meta?.tagName ?? "unknown");
7300
- }
7301
- return [...seen].map(([id, tag]) => ({
7302
- id,
7303
- tag
7304
- }));
7305
- }
7306
- _onFilterChange(e) {
7307
- const v = e.target.value;
7308
- this._elementFilter = v === "" ? null : Number(v);
7309
- }
7310
- /** Ask the panel shell to open the Components tab on this element. */
7311
- _inspect(id) {
7312
- this.dispatchEvent(new CustomEvent("inspect-element", {
7313
- detail: { id },
7504
+ _select(key) {
7505
+ this.dispatchEvent(new CustomEvent("span-select", {
7506
+ detail: { key },
7314
7507
  bubbles: true,
7315
7508
  composed: true
7316
7509
  }));
7317
7510
  }
7318
- _onRegexInput(e) {
7319
- this._regex = e.target.value;
7511
+ /**
7512
+ * Scrolls the selected row into view, or to the newest row when nothing is
7513
+ * selected. `timeline-view` calls this when switching back from Tracks: the
7514
+ * list was hidden while the selection moved, so its own auto-scroll could
7515
+ * not follow.
7516
+ */
7517
+ reveal() {
7518
+ const el = this._scrollRef.value;
7519
+ if (!el) return;
7520
+ const index = this.selectedKey === null ? -1 : this._visibleCache.findIndex((row) => row.key === this.selectedKey);
7521
+ if (index === -1) {
7522
+ el.scrollTop = el.scrollHeight;
7523
+ return;
7524
+ }
7525
+ const attempt = (left) => {
7526
+ try {
7527
+ el[virtualizerRef]?.element(index)?.scrollIntoView({ block: "center" });
7528
+ } catch {
7529
+ if (left > 0) requestAnimationFrame(() => attempt(left - 1));
7530
+ }
7531
+ };
7532
+ attempt(30);
7320
7533
  }
7321
- /** Text searched by the regex filter — element tag, name, subtitle and the
7322
- * changed property keys (the reason is worth searching for by name). */
7323
- _haystack(row) {
7324
- return `${row.meta?.tagName ?? ""} ${row.name} ${row.subtitle ?? ""} ${(row.changed ?? []).join(" ")}`;
7534
+ _layerOn(row) {
7535
+ const l = this.layers.find((l) => l.id === row.layerId);
7536
+ return !l || l.enabled;
7325
7537
  }
7326
7538
  render() {
7327
- const elements = this._elementsCache;
7328
7539
  const visible = this._visibleCache;
7329
- const selected = this._selectedKey === null ? void 0 : this._rowsCache.find((row) => row.key === this._selectedKey);
7540
+ const selected = this.selectedKey === null ? void 0 : this._rowsCache.find((row) => row.key === this.selectedKey);
7330
7541
  return b`
7331
7542
  ${this.events.length > 0 ? b`
7332
7543
  <div class="filterbar">
7333
- ${elements.length > 0 ? b`
7334
- <span>Element:</span>
7335
- <select @change=${this._onFilterChange}>
7336
- <option
7337
- value=""
7338
- ?selected=${this._elementFilter === null}
7339
- >
7340
- All elements
7341
- </option>
7342
- ${elements.map((el) => b`
7343
- <option
7344
- value=${el.id}
7345
- ?selected=${this._elementFilter === el.id}
7346
- >
7347
- &lt;${el.tag}&gt; #${el.id}
7348
- </option>
7349
- `)}
7350
- </select>
7351
- ` : A}
7352
- <input
7353
- class="regex ${this._regexInvalid ? "invalid" : ""}"
7354
- type="text"
7355
- spellcheck="false"
7356
- placeholder="filter regex…"
7357
- title="Case-insensitive regex matched against element tag, title and subtitle"
7358
- .value=${this._regex}
7359
- @input=${this._onRegexInput}
7360
- />
7361
7544
  <button
7362
7545
  class=${this._raw ? "on" : ""}
7363
7546
  title="Show one row per recorded event instead of collapsing start/end pairs"
7364
7547
  @click=${() => {
7365
7548
  this._raw = !this._raw;
7549
+ this._select(null);
7366
7550
  }}
7367
7551
  >
7368
7552
  Raw
@@ -7391,22 +7575,23 @@ var TimelineEventList = class TimelineEventList extends i$2 {
7391
7575
  })}
7392
7576
  </div>
7393
7577
  `}
7394
- ${selected ? this._renderDetail(selected) : A}
7578
+ ${selected ? b`<timeline-span-detail
7579
+ filterable
7580
+ .span=${selected}
7581
+ ></timeline-span-detail>` : A}
7395
7582
  `;
7396
7583
  }
7397
7584
  /** One row of the virtualized list (`virtualize`'s `renderItem`). */
7398
7585
  _renderRow(row) {
7399
7586
  return b`
7400
7587
  <div
7401
- class="row ${this._selectedKey === row.key ? "selected" : ""}"
7402
- @click=${() => {
7403
- this._selectedKey = row.key;
7404
- }}
7588
+ class="row ${this.selectedKey === row.key ? "selected" : ""}"
7589
+ @click=${() => this._select(row.key)}
7405
7590
  >
7406
7591
  <span class="time">${row.start.toFixed(1)}ms</span>
7407
7592
  <span
7408
7593
  class="dot"
7409
- style=${"background:" + this._colorOf(row.layerId)}
7594
+ style=${"background:" + layerColor(this.layers, row.layerId)}
7410
7595
  ></span>
7411
7596
  <span class="title">${row.name}</span>
7412
7597
  ${row.changed?.length ? b`<span class="changed">${row.changed.join(", ")}</span>` : A}
@@ -7417,75 +7602,571 @@ var TimelineEventList = class TimelineEventList extends i$2 {
7417
7602
  </div>
7418
7603
  `;
7419
7604
  }
7420
- _renderDetail(row) {
7421
- const { meta } = row;
7422
- const src = meta?.source;
7423
- const data = row.events[0]?.data;
7424
- return b`
7425
- <div class="detail">
7426
- <table>
7427
- <tr>
7428
- <td class="key">layer</td>
7429
- <td class="val">${row.layerId}</td>
7430
- </tr>
7431
- <tr>
7432
- <td class="key">time</td>
7433
- <td class="val">${row.start.toFixed(3)} ms</td>
7434
- </tr>
7435
- ${row.duration !== void 0 ? b`<tr>
7436
- <td class="key">duration</td>
7437
- <td class="val">${row.duration.toFixed(3)} ms</td>
7438
- </tr>` : A}
7439
- ${row.changed?.length ? b`<tr>
7440
- <td class="key">changed</td>
7441
- <td class="val">${row.changed.join(", ")}</td>
7442
- </tr>` : A}
7443
- ${meta?.tagName ? b`<tr>
7444
- <td class="key">element</td>
7445
- <td class="val">
7446
- &lt;${meta.tagName}&gt; #${meta.elementId}
7447
- ${meta.elementId != null ? b`<a
7448
- class="filter-link"
7449
- @click=${() => {
7450
- this._elementFilter = meta.elementId;
7451
- }}
7452
- >filter</a
7453
- ><a
7454
- class="filter-link"
7455
- title="Open this element in the Components tab"
7456
- @click=${() => this._inspect(meta.elementId)}
7457
- >inspect</a
7458
- >` : A}
7459
- </td>
7460
- </tr>` : A}
7461
- ${src ? b`<tr>
7462
- <td class="key">source</td>
7463
- <td class="val">
7464
- <a
7465
- class="src-link"
7466
- title="Open this file in your editor"
7467
- @click=${() => openInEditor(src.file, src.line)}
7468
- >${src.file}:${src.line}</a
7469
- >
7470
- </td>
7471
- </tr>` : A}
7472
- ${data != null ? b`<tr>
7473
- <td class="key">data</td>
7474
- <td class="val">${JSON.stringify(data)}</td>
7475
- </tr>` : A}
7476
- </table>
7477
- </div>
7478
- `;
7479
- }
7480
7605
  };
7481
7606
  __decorate([n$3({ type: Array })], TimelineEventList.prototype, "events", void 0);
7607
+ __decorate([n$3({ type: Array })], TimelineEventList.prototype, "spans", void 0);
7482
7608
  __decorate([n$3({ type: Array })], TimelineEventList.prototype, "layers", void 0);
7483
- __decorate([r$2()], TimelineEventList.prototype, "_selectedKey", void 0);
7484
- __decorate([r$2()], TimelineEventList.prototype, "_elementFilter", void 0);
7485
- __decorate([r$2()], TimelineEventList.prototype, "_regex", void 0);
7609
+ __decorate([n$3({ attribute: false })], TimelineEventList.prototype, "selectedKey", void 0);
7610
+ __decorate([n$3({ attribute: false })], TimelineEventList.prototype, "elementFilter", void 0);
7611
+ __decorate([n$3({ attribute: false })], TimelineEventList.prototype, "regex", void 0);
7486
7612
  __decorate([r$2()], TimelineEventList.prototype, "_raw", void 0);
7487
7613
  TimelineEventList = __decorate([t$2("timeline-event-list")], TimelineEventList);
7488
7614
  //#endregion
7615
+ //#region src/lib/timeline/tracks.ts
7616
+ /**
7617
+ * @license
7618
+ * Copyright 2026 Google LLC
7619
+ * SPDX-License-Identifier: BSD-3-Clause
7620
+ */
7621
+ /**
7622
+ * Where a span stops occupying its row. A point event occupies only its own
7623
+ * instant; a span whose end has not arrived (still running, or its end fell
7624
+ * out of the buffer) occupies the rest of the recording, because that is
7625
+ * where it is drawn — up to the live edge.
7626
+ */
7627
+ var occupiedEnd = (span) => {
7628
+ if (span.end !== void 0) return span.end;
7629
+ return span.groupId === void 0 ? span.start : Infinity;
7630
+ };
7631
+ /**
7632
+ * Groups spans into one track per layer, in `layers` order, then any layer
7633
+ * the spans mention that `layers` does not (a custom layer announced after the
7634
+ * last `get-meta`), in first-seen order. A layer with no spans still gets an
7635
+ * empty track, so a lane does not appear and vanish as events arrive.
7636
+ *
7637
+ * Within a track, spans are packed greedily: each goes into the first row
7638
+ * whose last span has ended by the time it starts. Lit's lifecycle phases
7639
+ * nest, so one update tick lands as `performUpdate` on row 0 and
7640
+ * `willUpdate` / `update` / `updated` side by side on row 1 — the flame shape
7641
+ * falls out of the packing without modelling it.
7642
+ */
7643
+ var buildTracks = (spans, layers) => {
7644
+ const byLayer = /* @__PURE__ */ new Map();
7645
+ for (const layer of layers) byLayer.set(layer.id, []);
7646
+ for (const span of spans) {
7647
+ let list = byLayer.get(span.layerId);
7648
+ if (list === void 0) {
7649
+ list = [];
7650
+ byLayer.set(span.layerId, list);
7651
+ }
7652
+ list.push(span);
7653
+ }
7654
+ return [...byLayer].map(([layerId, list]) => ({
7655
+ layerId,
7656
+ rows: packRows(list)
7657
+ }));
7658
+ };
7659
+ var packRows = (spans) => {
7660
+ const sorted = spans.every((s, i) => i === 0 || spans[i - 1].start <= s.start) ? spans : [...spans].sort((a, b) => a.start - b.start);
7661
+ const rows = [];
7662
+ const rowEnds = [];
7663
+ for (const span of sorted) {
7664
+ let row = rowEnds.findIndex((end) => end <= span.start);
7665
+ if (row === -1) {
7666
+ row = rows.length;
7667
+ rows.push([]);
7668
+ rowEnds.push(-Infinity);
7669
+ }
7670
+ rows[row].push(span);
7671
+ rowEnds[row] = occupiedEnd(span);
7672
+ }
7673
+ return rows;
7674
+ };
7675
+ /**
7676
+ * The recording's time bounds over `spans`: the earliest start, and the
7677
+ * latest instant anything is known to have happened (an end, or the start of
7678
+ * a span with none). The origin is the earliest start rather than zero
7679
+ * because the panel's buffer is capped and rolls its oldest events off; a
7680
+ * long recording would otherwise open on an empty stretch.
7681
+ */
7682
+ var timeBounds = (spans) => {
7683
+ if (spans.length === 0) return {
7684
+ origin: 0,
7685
+ extent: 0
7686
+ };
7687
+ let origin = Infinity;
7688
+ let last = -Infinity;
7689
+ for (const span of spans) {
7690
+ origin = Math.min(origin, span.start);
7691
+ last = Math.max(last, span.end ?? span.start);
7692
+ }
7693
+ return {
7694
+ origin,
7695
+ extent: last - origin
7696
+ };
7697
+ };
7698
+ /** Deepest zoom, in multiples of "fit": about a microsecond per pixel on a
7699
+ * second-long recording, past which there is nothing left to separate. */
7700
+ var MAX_ZOOM = 1e5;
7701
+ /** A recording with a single instant in it still needs a nonzero axis. */
7702
+ var MIN_EXTENT_MS = 1;
7703
+ /**
7704
+ * Builds the scale for a plot `width` pixels wide showing a recording that
7705
+ * begins at `originMs` and runs for `extentMs`.
7706
+ *
7707
+ * `zoom` is a multiple of "fit" (1 shows the whole recording); `pan` is how
7708
+ * many milliseconds past the origin the left edge sits. Both are clamped, so
7709
+ * callers can apply a raw wheel or drag delta and read back where it landed.
7710
+ */
7711
+ var timeScale = (originMs, extentMs, width, zoom = 1, pan = 0) => {
7712
+ const extent = Math.max(extentMs, MIN_EXTENT_MS);
7713
+ const z = clampZoom(zoom);
7714
+ const visibleMs = extent / z;
7715
+ const p = clampPan(pan, extent, z);
7716
+ const pxPerMs = Math.max(width, 1) / visibleMs;
7717
+ const start = originMs + p;
7718
+ return {
7719
+ pxPerMs,
7720
+ start,
7721
+ end: start + visibleMs,
7722
+ toX: (ms) => (ms - start) * pxPerMs,
7723
+ toMs: (x) => start + x / pxPerMs
7724
+ };
7725
+ };
7726
+ var clampZoom = (zoom) => Math.min(Math.max(Number.isNaN(zoom) ? 1 : zoom, 1), MAX_ZOOM);
7727
+ /** Keeps the visible window inside the recording. */
7728
+ var clampPan = (pan, extentMs, zoom) => {
7729
+ const extent = Math.max(extentMs, MIN_EXTENT_MS);
7730
+ const max = extent - extent / clampZoom(zoom);
7731
+ return Math.min(Math.max(Number.isNaN(pan) ? 0 : pan, 0), max);
7732
+ };
7733
+ /** The pan that puts the live edge (the end of the recording) at the right
7734
+ * of the plot, for following a recording in progress. */
7735
+ var livePan = (extentMs, zoom) => clampPan(Infinity, extentMs, zoom);
7736
+ /**
7737
+ * Zooms by `factor` while keeping the millisecond under pixel `x` where it
7738
+ * is, which is what a wheel over a timeline is expected to do.
7739
+ */
7740
+ var zoomAt = (originMs, extentMs, width, zoom, pan, x, factor) => {
7741
+ const anchor = timeScale(originMs, extentMs, width, zoom, pan).toMs(x);
7742
+ const nextZoom = clampZoom(clampZoom(zoom) * factor);
7743
+ const after = timeScale(originMs, extentMs, width, nextZoom, 0);
7744
+ return {
7745
+ zoom: nextZoom,
7746
+ pan: clampPan(anchor - originMs - x / after.pxPerMs, extentMs, nextZoom)
7747
+ };
7748
+ };
7749
+ /**
7750
+ * Index range `[from, to)` of the spans in `row` that intersect the window
7751
+ * `[start, end]`, found by binary search so a zoomed-in lane renders a
7752
+ * handful of marks rather than walking thousands.
7753
+ *
7754
+ * Only the span just before the first one starting inside the window can
7755
+ * reach into it, because a row's spans never overlap.
7756
+ */
7757
+ var visibleRange = (row, start, end) => {
7758
+ const firstAtOrAfter = (t, inclusive) => {
7759
+ let lo = 0;
7760
+ let hi = row.length;
7761
+ while (lo < hi) {
7762
+ const mid = lo + hi >> 1;
7763
+ const s = row[mid].start;
7764
+ if (inclusive ? s < t : s <= t) lo = mid + 1;
7765
+ else hi = mid;
7766
+ }
7767
+ return lo;
7768
+ };
7769
+ let from = firstAtOrAfter(start, true);
7770
+ if (from > 0 && occupiedEnd(row[from - 1]) >= start) from--;
7771
+ const to = firstAtOrAfter(end, false);
7772
+ return [from, Math.max(from, to)];
7773
+ };
7774
+ /**
7775
+ * A round tick interval (1, 2 or 5 times a power of ten, in milliseconds) at
7776
+ * least `minMs` long, for axis labels that land on readable numbers.
7777
+ */
7778
+ var niceStep = (minMs) => {
7779
+ if (!(minMs > 0) || !Number.isFinite(minMs)) return 1;
7780
+ const pow = 10 ** Math.floor(Math.log10(minMs));
7781
+ for (const m of [
7782
+ 1,
7783
+ 2,
7784
+ 5,
7785
+ 10
7786
+ ]) if (m * pow >= minMs) return m * pow;
7787
+ return 10 * pow;
7788
+ };
7789
+ //#endregion
7790
+ //#region src/panel/timeline-tracks.ts
7791
+ /**
7792
+ * @license
7793
+ * Copyright 2026 Google LLC
7794
+ * SPDX-License-Identifier: BSD-3-Clause
7795
+ */
7796
+ /** Height of one packed row inside a lane. */
7797
+ var ROW_PX = 14;
7798
+ /** Marks narrower than this draw as a fixed-width tick, so sub-millisecond
7799
+ * Lit phases stay visible at any zoom. */
7800
+ var MIN_MARK_PX = 2;
7801
+ /** Rough spacing between axis labels. */
7802
+ var TICK_PX = 90;
7803
+ /** A pointer that moves less than this between down and up was a click. */
7804
+ var DRAG_SLOP_PX = 3;
7805
+ var formatAxis = (ms, step) => step >= 1e3 ? `${(ms / 1e3).toFixed(step >= 1e4 ? 0 : 1)}s` : `${ms.toFixed(step >= 1 ? 0 : step >= .1 ? 1 : 2)}ms`;
7806
+ var describe = (span) => {
7807
+ const parts = [span.name];
7808
+ if (span.subtitle) parts.push(span.subtitle);
7809
+ parts.push(`@ ${span.start.toFixed(2)}ms`);
7810
+ if (span.duration !== void 0) parts.push(`${span.duration.toFixed(3)}ms`);
7811
+ else if (span.groupId !== void 0) parts.push("still open");
7812
+ return parts.join(" · ");
7813
+ };
7814
+ var TimelineTracks = class TimelineTracks extends i$2 {
7815
+ constructor(..._args) {
7816
+ super(..._args);
7817
+ this.spans = [];
7818
+ this.layers = [];
7819
+ this.selectedKey = null;
7820
+ this.visibleTracks = [];
7821
+ this.recording = false;
7822
+ this._zoom = 1;
7823
+ this._pan = 0;
7824
+ this._follow = true;
7825
+ this._width = 0;
7826
+ this._ticksRef = e();
7827
+ this._resize = null;
7828
+ this._observed = null;
7829
+ this._tracks = [];
7830
+ this._visibleSpans = [];
7831
+ this._bounds = {
7832
+ origin: 0,
7833
+ extent: 0
7834
+ };
7835
+ this._drag = null;
7836
+ this._suppressClick = false;
7837
+ }
7838
+ static {
7839
+ this.styles = [tokens, i$5`
7840
+ :host {
7841
+ display: flex;
7842
+ flex-direction: column;
7843
+ flex: 1;
7844
+ overflow: hidden;
7845
+ font-size: var(--lit-devtools-text-2xs);
7846
+ font-family: var(--lit-devtools-font-mono);
7847
+ --gutter: 120px;
7848
+ }
7849
+ .axis,
7850
+ .lane {
7851
+ display: flex;
7852
+ }
7853
+ .axis {
7854
+ flex-shrink: 0;
7855
+ height: 20px;
7856
+ border-bottom: 1px solid var(--lit-devtools-border);
7857
+ color: var(--lit-devtools-text-muted);
7858
+ }
7859
+ .gutter {
7860
+ flex: 0 0 var(--gutter);
7861
+ box-sizing: border-box;
7862
+ padding: 0 var(--lit-devtools-space-4);
7863
+ display: flex;
7864
+ align-items: center;
7865
+ gap: var(--lit-devtools-space-2);
7866
+ overflow: hidden;
7867
+ white-space: nowrap;
7868
+ border-right: 1px solid var(--lit-devtools-border);
7869
+ }
7870
+ .ticks {
7871
+ position: relative;
7872
+ flex: 1;
7873
+ overflow: hidden;
7874
+ }
7875
+ .tick-label {
7876
+ position: absolute;
7877
+ top: 3px;
7878
+ padding-left: var(--lit-devtools-space-2);
7879
+ border-left: 1px solid var(--lit-devtools-border-strong);
7880
+ white-space: nowrap;
7881
+ }
7882
+ .lanes {
7883
+ flex: 1;
7884
+ overflow-y: auto;
7885
+ cursor: grab;
7886
+ user-select: none;
7887
+ touch-action: none;
7888
+ }
7889
+ .lanes.dragging {
7890
+ cursor: grabbing;
7891
+ }
7892
+ .lane {
7893
+ border-bottom: 1px solid var(--lit-devtools-border);
7894
+ }
7895
+ .lane .gutter {
7896
+ color: var(--lit-devtools-text-secondary);
7897
+ cursor: default;
7898
+ }
7899
+ .dot {
7900
+ width: 8px;
7901
+ height: 8px;
7902
+ border-radius: 50%;
7903
+ flex-shrink: 0;
7904
+ }
7905
+ .plot {
7906
+ flex: 1;
7907
+ min-width: 0;
7908
+ max-height: calc(8 * ${ROW_PX}px);
7909
+ overflow-x: hidden;
7910
+ overflow-y: auto;
7911
+ }
7912
+ .rows {
7913
+ position: relative;
7914
+ }
7915
+ .mark {
7916
+ position: absolute;
7917
+ box-sizing: border-box;
7918
+ height: ${11}px;
7919
+ margin-top: 1px;
7920
+ border-radius: 2px;
7921
+ opacity: 0.85;
7922
+ overflow: hidden;
7923
+ white-space: nowrap;
7924
+ color: var(--lit-devtools-surface-low);
7925
+ font-size: 9px;
7926
+ line-height: ${11}px;
7927
+ padding: 0 2px;
7928
+ cursor: pointer;
7929
+ }
7930
+ .mark.tick {
7931
+ padding: 0;
7932
+ }
7933
+ .mark.open {
7934
+ opacity: 0.45;
7935
+ }
7936
+ .mark:hover {
7937
+ opacity: 1;
7938
+ }
7939
+ .mark.selected {
7940
+ opacity: 1;
7941
+ outline: 2px solid var(--lit-devtools-text);
7942
+ outline-offset: 0;
7943
+ z-index: 1;
7944
+ }
7945
+ .mark:focus-visible {
7946
+ outline: 2px solid var(--lit-devtools-accent-ring);
7947
+ z-index: 1;
7948
+ }
7949
+ .empty {
7950
+ display: flex;
7951
+ flex-direction: column;
7952
+ align-items: center;
7953
+ justify-content: center;
7954
+ flex: 1;
7955
+ gap: var(--lit-devtools-space-4);
7956
+ color: var(--lit-devtools-text-muted);
7957
+ font-family: var(--lit-devtools-font-sans);
7958
+ font-size: var(--lit-devtools-text-xs);
7959
+ }
7960
+ `];
7961
+ }
7962
+ disconnectedCallback() {
7963
+ super.disconnectedCallback();
7964
+ this._resize?.disconnect();
7965
+ this._resize = null;
7966
+ this._observed = null;
7967
+ }
7968
+ updated() {
7969
+ const el = this._ticksRef.value ?? null;
7970
+ if (el === this._observed) return;
7971
+ this._resize?.disconnect();
7972
+ this._observed = el;
7973
+ if (!el) return;
7974
+ this._resize ??= new ResizeObserver(() => {
7975
+ this._width = this._observed?.clientWidth ?? 0;
7976
+ });
7977
+ this._resize.observe(el);
7978
+ }
7979
+ willUpdate(changed) {
7980
+ if (changed.has("spans") || changed.has("layers") || changed.has("visibleTracks")) {
7981
+ const shown = new Set(this.visibleTracks);
7982
+ this._visibleSpans = this.spans.filter((s) => shown.has(s.layerId));
7983
+ this._tracks = buildTracks(this._visibleSpans, this.layers).filter((t) => shown.has(t.layerId));
7984
+ this._bounds = timeBounds(this._visibleSpans);
7985
+ }
7986
+ }
7987
+ _scale() {
7988
+ const { origin, extent } = this._bounds;
7989
+ const pan = this._follow && this.recording ? livePan(extent, this._zoom) : this._pan;
7990
+ return timeScale(origin, extent, this._width, this._zoom, pan);
7991
+ }
7992
+ /** Commit a zoom/pan, and keep following only if it still ends at the live
7993
+ * edge. */
7994
+ _setView(zoom, pan) {
7995
+ const { extent } = this._bounds;
7996
+ this._zoom = clampZoom(zoom);
7997
+ this._pan = clampPan(pan, extent, this._zoom);
7998
+ this._follow = this._pan >= livePan(extent, this._zoom) - 1e-9;
7999
+ }
8000
+ _currentPan() {
8001
+ return this._scale().start - this._bounds.origin;
8002
+ }
8003
+ _onWheel(e) {
8004
+ if (this._width === 0 || this._visibleSpans.length === 0) return;
8005
+ const plot = this._ticksRef.value;
8006
+ if (!plot) return;
8007
+ const x = e.clientX - plot.getBoundingClientRect().left;
8008
+ if (x < 0) return;
8009
+ e.preventDefault();
8010
+ const { origin, extent } = this._bounds;
8011
+ const pan = this._currentPan();
8012
+ if (Math.abs(e.deltaX) > Math.abs(e.deltaY) || e.shiftKey) {
8013
+ const delta = e.shiftKey ? e.deltaY : e.deltaX;
8014
+ const scale = this._scale();
8015
+ this._setView(this._zoom, pan + delta / scale.pxPerMs);
8016
+ return;
8017
+ }
8018
+ const factor = Math.exp(-e.deltaY * .002);
8019
+ const next = zoomAt(origin, extent, this._width, this._zoom, pan, x, factor);
8020
+ this._setView(next.zoom, next.pan);
8021
+ }
8022
+ _onPointerDown(e) {
8023
+ if (e.button !== 0) return;
8024
+ this._drag = {
8025
+ x: e.clientX,
8026
+ pan: this._currentPan(),
8027
+ moved: false
8028
+ };
8029
+ }
8030
+ _onPointerMove(e) {
8031
+ const drag = this._drag;
8032
+ if (!drag) return;
8033
+ const dx = e.clientX - drag.x;
8034
+ if (!drag.moved && Math.abs(dx) < DRAG_SLOP_PX) return;
8035
+ if (!drag.moved) {
8036
+ drag.moved = true;
8037
+ e.currentTarget.setPointerCapture(e.pointerId);
8038
+ this.requestUpdate();
8039
+ }
8040
+ this._setView(this._zoom, drag.pan - dx / this._scale().pxPerMs);
8041
+ }
8042
+ _onPointerUp() {
8043
+ if (this._drag?.moved) {
8044
+ this._suppressClick = true;
8045
+ setTimeout(() => this._suppressClick = false);
8046
+ this.requestUpdate();
8047
+ }
8048
+ this._drag = null;
8049
+ }
8050
+ _refit() {
8051
+ this._zoom = 1;
8052
+ this._pan = 0;
8053
+ this._follow = true;
8054
+ }
8055
+ _select(key) {
8056
+ if (this._suppressClick) return;
8057
+ this.dispatchEvent(new CustomEvent("span-select", {
8058
+ detail: { key },
8059
+ bubbles: true,
8060
+ composed: true
8061
+ }));
8062
+ }
8063
+ render() {
8064
+ const selected = this.selectedSpan;
8065
+ const detail = selected ? b`<timeline-span-detail
8066
+ filterable
8067
+ .span=${selected}
8068
+ ></timeline-span-detail>` : A;
8069
+ if (this._tracks.length === 0) return b`<div class="empty">
8070
+ ${this.visibleTracks.length === 0 ? "No tracks shown. Pick some above." : "No events recorded."}
8071
+ </div>
8072
+ ${detail}`;
8073
+ const scale = this._scale();
8074
+ return b`
8075
+ <div class="axis">
8076
+ <div class="gutter"></div>
8077
+ <div class="ticks" ${n(this._ticksRef)}>
8078
+ ${this._width > 0 ? this._renderTicks(scale) : A}
8079
+ </div>
8080
+ </div>
8081
+ <div
8082
+ class="lanes ${this._drag?.moved ? "dragging" : ""}"
8083
+ title="Wheel to zoom, drag to pan, double-click to fit"
8084
+ @wheel=${this._onWheel}
8085
+ @pointerdown=${this._onPointerDown}
8086
+ @pointermove=${this._onPointerMove}
8087
+ @pointerup=${this._onPointerUp}
8088
+ @pointercancel=${this._onPointerUp}
8089
+ @dblclick=${this._refit}
8090
+ >
8091
+ ${this._tracks.map((track) => this._renderLane(track, scale))}
8092
+ </div>
8093
+ ${detail}
8094
+ `;
8095
+ }
8096
+ _renderTicks(scale) {
8097
+ const step = niceStep(TICK_PX / scale.pxPerMs);
8098
+ const labels = [];
8099
+ for (let t = Math.ceil(scale.start / step) * step; t <= scale.end && labels.length < 200; t += step) labels.push(b`<span class="tick-label" style="left:${scale.toX(t)}px"
8100
+ >${formatAxis(t, step)}</span
8101
+ >`);
8102
+ return labels;
8103
+ }
8104
+ _renderLane(track, scale) {
8105
+ const layer = this.layers.find((l) => l.id === track.layerId);
8106
+ const color = layerColor(this.layers, track.layerId);
8107
+ const rows = Math.max(track.rows.length, 1);
8108
+ return b`
8109
+ <div class="lane">
8110
+ <div class="gutter" title=${layer?.label ?? track.layerId}>
8111
+ <span class="dot" style="background:${color}"></span>
8112
+ ${layer?.label ?? track.layerId}
8113
+ </div>
8114
+ <div class="plot">
8115
+ <div class="rows" style="height:${rows * ROW_PX}px">
8116
+ ${this._width > 0 ? track.rows.map((row, i) => this._renderRow(row, i, scale, color)) : A}
8117
+ </div>
8118
+ </div>
8119
+ </div>
8120
+ `;
8121
+ }
8122
+ _renderRow(row, rowIndex, scale, color) {
8123
+ const [from, to] = visibleRange(row, scale.start, scale.end);
8124
+ const liveEdge = this._bounds.origin + this._bounds.extent;
8125
+ const marks = [];
8126
+ let lastTickPx = -Infinity;
8127
+ for (let i = from; i < to; i++) {
8128
+ const span = row[i];
8129
+ const open = span.end === void 0 && span.groupId !== void 0;
8130
+ const x0 = scale.toX(span.start);
8131
+ const x1 = scale.toX(span.end ?? (open ? liveEdge : span.start));
8132
+ const isTick = x1 - x0 < MIN_MARK_PX;
8133
+ const selected = span.key === this.selectedKey;
8134
+ if (isTick && !selected && Math.round(x0) === lastTickPx) continue;
8135
+ if (isTick) lastTickPx = Math.round(x0);
8136
+ const left = Math.max(x0, -1);
8137
+ const width = isTick ? MIN_MARK_PX : Math.min(x1, this._width + 1) - left;
8138
+ marks.push(b`<div
8139
+ class="mark ${isTick ? "tick" : ""} ${open ? "open" : ""} ${selected ? "selected" : ""}"
8140
+ role="button"
8141
+ tabindex="0"
8142
+ title=${describe(span)}
8143
+ style="left:${left}px;width:${width}px;top:${rowIndex * ROW_PX}px;background:${color}"
8144
+ @click=${() => this._select(span.key)}
8145
+ @keydown=${(e) => {
8146
+ if (e.key === "Enter" || e.key === " ") {
8147
+ e.preventDefault();
8148
+ this._select(span.key);
8149
+ }
8150
+ }}
8151
+ >
8152
+ ${isTick || width < 40 ? A : span.name}
8153
+ </div>`);
8154
+ }
8155
+ return marks;
8156
+ }
8157
+ };
8158
+ __decorate([n$3({ type: Array })], TimelineTracks.prototype, "spans", void 0);
8159
+ __decorate([n$3({ type: Array })], TimelineTracks.prototype, "layers", void 0);
8160
+ __decorate([n$3({ attribute: false })], TimelineTracks.prototype, "selectedKey", void 0);
8161
+ __decorate([n$3({ attribute: false })], TimelineTracks.prototype, "selectedSpan", void 0);
8162
+ __decorate([n$3({ type: Array })], TimelineTracks.prototype, "visibleTracks", void 0);
8163
+ __decorate([n$3({ type: Boolean })], TimelineTracks.prototype, "recording", void 0);
8164
+ __decorate([r$2()], TimelineTracks.prototype, "_zoom", void 0);
8165
+ __decorate([r$2()], TimelineTracks.prototype, "_pan", void 0);
8166
+ __decorate([r$2()], TimelineTracks.prototype, "_follow", void 0);
8167
+ __decorate([r$2()], TimelineTracks.prototype, "_width", void 0);
8168
+ TimelineTracks = __decorate([t$2("timeline-tracks")], TimelineTracks);
8169
+ //#endregion
7489
8170
  //#region src/panel/timeline-store.ts
7490
8171
  /**
7491
8172
  * @license
@@ -7514,6 +8195,47 @@ TimelineEventList = __decorate([t$2("timeline-event-list")], TimelineEventList);
7514
8195
  * recent events; older ones scroll off.
7515
8196
  */
7516
8197
  var MAX_EVENTS = 5e3;
8198
+ /**
8199
+ * sessionStorage key for the id of the newest event Clear (or a fresh
8200
+ * recording) threw away. A reloaded panel is refilled from the node's
8201
+ * `timeline-history`, which Clear does not touch (agents still read the same
8202
+ * buffer through `recent-events`), so without this a reload would resurrect
8203
+ * everything the developer cleared. sessionStorage, not a module variable:
8204
+ * the point is to survive the reload. Ids are `${epoch}-${seq}` with a seq
8205
+ * that never resets across Clear (see `devframe/definition.ts`).
8206
+ */
8207
+ var CLEARED_THROUGH_KEY = "lit-devtools:timeline-cleared-through";
8208
+ var parseEventId = (id) => {
8209
+ if (id === void 0) return void 0;
8210
+ const cut = id.lastIndexOf("-");
8211
+ const seq = Number(id.slice(cut + 1));
8212
+ return cut > 0 && Number.isInteger(seq) ? {
8213
+ epoch: id.slice(0, cut),
8214
+ seq
8215
+ } : void 0;
8216
+ };
8217
+ var readClearedThrough = () => {
8218
+ try {
8219
+ return parseEventId(sessionStorage.getItem(CLEARED_THROUGH_KEY) ?? void 0);
8220
+ } catch {
8221
+ return;
8222
+ }
8223
+ };
8224
+ var rememberClearedThrough = (id) => {
8225
+ try {
8226
+ sessionStorage.setItem(CLEARED_THROUGH_KEY, id);
8227
+ } catch {}
8228
+ };
8229
+ /** Drops events at or before the cleared-through mark. A mark from another
8230
+ * epoch belongs to an earlier dev server and says nothing about these ids. */
8231
+ var withoutCleared = (batch) => {
8232
+ const mark = readClearedThrough();
8233
+ if (mark === void 0) return batch;
8234
+ return batch.filter((event) => {
8235
+ const id = parseEventId(event.id);
8236
+ return id === void 0 || id.epoch !== mark.epoch || id.seq > mark.seq;
8237
+ });
8238
+ };
7517
8239
  var events = [];
7518
8240
  var error = null;
7519
8241
  var started = false;
@@ -7528,6 +8250,8 @@ var getTimelineEvents = () => events;
7528
8250
  var getTimelineError = () => error;
7529
8251
  var clearTimelineEvents = () => {
7530
8252
  if (events.length === 0) return;
8253
+ const newest = events[events.length - 1].id;
8254
+ if (newest !== void 0) rememberClearedThrough(newest);
7531
8255
  events = [];
7532
8256
  notify$1();
7533
8257
  };
@@ -7553,8 +8277,16 @@ var start = async () => {
7553
8277
  return;
7554
8278
  }
7555
8279
  const reader = rpc.rpc.streaming.subscribe(meta.stream.channel, meta.stream.id, { highWaterMark: 4096 });
8280
+ const seeded = withoutCleared(await rpc.rpc.call("timeline-history").catch(() => [])).slice(-5e3);
8281
+ const seededIds = new Set(seeded.map((event) => event.id));
8282
+ if (seeded.length > 0) {
8283
+ events = seeded;
8284
+ notify$1();
8285
+ }
7556
8286
  for await (const batch of reader) {
7557
- const next = [...events, ...batch];
8287
+ const fresh = withoutCleared(batch).filter((event) => event.id === void 0 || !seededIds.has(event.id));
8288
+ if (fresh.length === 0) continue;
8289
+ const next = [...events, ...fresh];
7558
8290
  events = next.length > MAX_EVENTS ? next.slice(-5e3) : next;
7559
8291
  notify$1();
7560
8292
  }
@@ -7570,15 +8302,63 @@ var start = async () => {
7570
8302
  * Copyright 2026 Google LLC
7571
8303
  * SPDX-License-Identifier: BSD-3-Clause
7572
8304
  */
8305
+ /** localStorage key remembering List vs Tracks. */
8306
+ var MODE_LS_KEY = "lit-devtools-timeline-mode";
8307
+ /** localStorage key remembering which tracks the user hid. Stored as the
8308
+ * hidden set, not the shown one, so a layer seen for the first time (a
8309
+ * custom layer, a new built-in) gets a track by default. */
8310
+ var HIDDEN_TRACKS_LS_KEY = "lit-devtools-timeline-hidden-tracks";
8311
+ var MODE_TABS = [{
8312
+ id: "list",
8313
+ label: "List"
8314
+ }, {
8315
+ id: "tracks",
8316
+ label: "Tracks"
8317
+ }];
8318
+ var readMode = () => {
8319
+ try {
8320
+ return localStorage.getItem(MODE_LS_KEY) === "tracks" ? "tracks" : "list";
8321
+ } catch {
8322
+ return "list";
8323
+ }
8324
+ };
8325
+ var readHiddenTracks = () => {
8326
+ try {
8327
+ const parsed = JSON.parse(localStorage.getItem(HIDDEN_TRACKS_LS_KEY) ?? "[]");
8328
+ return new Set(Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : []);
8329
+ } catch {
8330
+ return /* @__PURE__ */ new Set();
8331
+ }
8332
+ };
8333
+ var store = (key, value) => {
8334
+ try {
8335
+ localStorage.setItem(key, value);
8336
+ } catch {}
8337
+ };
7573
8338
  var TimelineView = class TimelineView extends i$2 {
7574
8339
  constructor(..._args) {
7575
8340
  super(..._args);
7576
8341
  this._recording = false;
8342
+ this._sessionSeen = false;
7577
8343
  this._exporting = false;
7578
8344
  this._exportNote = null;
7579
8345
  this._events = [];
7580
8346
  this._layers = [];
7581
8347
  this._error = null;
8348
+ this._mode = readMode();
8349
+ this._hiddenTracks = readHiddenTracks();
8350
+ this._selectedKey = null;
8351
+ this._elementFilter = null;
8352
+ this._regex = "";
8353
+ this._spans = [];
8354
+ this._spansOf = null;
8355
+ this._elements = [];
8356
+ this._filteredSpans = [];
8357
+ this._pendingEventId = null;
8358
+ this._revealSelection = false;
8359
+ this._pendingSettled = false;
8360
+ this._listRef = e();
8361
+ this._visibleTracks = [];
7582
8362
  this._baseLayers = [];
7583
8363
  this._rpc = null;
7584
8364
  this._sessionOff = null;
@@ -7633,10 +8413,47 @@ var TimelineView = class TimelineView extends i$2 {
7633
8413
  background: var(--lit-devtools-error-soft);
7634
8414
  color: var(--lit-devtools-error);
7635
8415
  }
7636
- timeline-event-list {
8416
+ .toolbar segmented-tabs {
8417
+ align-self: stretch;
8418
+ margin: calc(-1 * var(--lit-devtools-space-3)) 0;
8419
+ }
8420
+ .filterbar {
8421
+ display: flex;
8422
+ align-items: center;
8423
+ gap: var(--lit-devtools-space-3);
8424
+ padding: var(--lit-devtools-space-2) var(--lit-devtools-space-5);
8425
+ border-bottom: 1px solid var(--lit-devtools-border);
8426
+ font-size: var(--lit-devtools-text-2xs);
8427
+ color: var(--lit-devtools-text-muted);
8428
+ flex-shrink: 0;
8429
+ }
8430
+ .filterbar select,
8431
+ .filterbar input.regex {
8432
+ background: var(--lit-devtools-surface-elevated);
8433
+ color: var(--lit-devtools-text);
8434
+ border: 1px solid var(--lit-devtools-border-strong);
8435
+ border-radius: var(--lit-devtools-radius-sm);
8436
+ padding: var(--lit-devtools-space-1) var(--lit-devtools-space-3);
8437
+ font-size: var(--lit-devtools-text-2xs);
8438
+ font-family: var(--lit-devtools-font-mono);
8439
+ }
8440
+ .filterbar input.regex {
8441
+ min-width: 140px;
8442
+ }
8443
+ .filterbar input.regex::placeholder {
8444
+ color: var(--lit-devtools-text-muted);
8445
+ }
8446
+ .filterbar input.regex.invalid {
8447
+ border-color: var(--lit-devtools-error);
8448
+ }
8449
+ timeline-event-list,
8450
+ timeline-tracks {
7637
8451
  flex: 1;
7638
8452
  overflow: hidden;
7639
8453
  }
8454
+ [hidden] {
8455
+ display: none !important;
8456
+ }
7640
8457
  .error {
7641
8458
  padding: var(--lit-devtools-space-5);
7642
8459
  color: var(--lit-devtools-text-secondary);
@@ -7659,6 +8476,60 @@ var TimelineView = class TimelineView extends i$2 {
7659
8476
  this._storeOff?.();
7660
8477
  this._storeOff = null;
7661
8478
  }
8479
+ willUpdate(changed) {
8480
+ if (changed.has("_layers") || changed.has("_hiddenTracks")) this._visibleTracks = this._layers.filter((l) => l.enabled && !this._hiddenTracks.has(l.id)).map((l) => l.id);
8481
+ if (this._events !== this._spansOf) {
8482
+ this._spansOf = this._events;
8483
+ this._spans = toSpans(this._events);
8484
+ this._elements = listElements(this._events);
8485
+ if (this._elementFilter !== null && !this._elements.some((el) => el.id === this._elementFilter)) this._elementFilter = null;
8486
+ const key = this._selectedKey;
8487
+ if (key !== null && !isRawKey(key) && !this._spans.some((s) => s.key === key)) this._selectedKey = null;
8488
+ this._applyPendingEvent();
8489
+ }
8490
+ if (changed.has("_events") || changed.has("_elementFilter") || changed.has("_regex")) this._filteredSpans = filterSpans(this._spans, this._elementFilter, compileRegex(this._regex).re);
8491
+ }
8492
+ /**
8493
+ * Select the span a deep link named. Its start event is the natural target
8494
+ * (`span.events[0]`), but any of the span's events matches, so a link to
8495
+ * either half of a pair lands on the same row.
8496
+ *
8497
+ * An id the buffer does not hold — evicted, or from another session — ends
8498
+ * with no selection and no error, once there are events to have looked in.
8499
+ */
8500
+ selectEvent(id) {
8501
+ this._pendingEventId = id;
8502
+ this._applyPendingEvent();
8503
+ this.requestUpdate();
8504
+ }
8505
+ _applyPendingEvent() {
8506
+ const id = this._pendingEventId;
8507
+ if (id === null || this._spans.length === 0) return;
8508
+ const span = this._spans.find((s) => s.events.some((e) => e.id === id));
8509
+ this._pendingEventId = null;
8510
+ this._pendingSettled = true;
8511
+ this._selectedKey = span?.key ?? null;
8512
+ if (span !== void 0) this._revealSelection = true;
8513
+ }
8514
+ /** Id of the selected span's start event, for the shell's URL sync. While a
8515
+ * linked id is still waiting on the store it is reported as-is, so the
8516
+ * address bar does not drop it before the events land. */
8517
+ get selectedEventId() {
8518
+ if (this._pendingEventId !== null) return this._pendingEventId;
8519
+ const key = this._selectedKey;
8520
+ if (key === null) return null;
8521
+ return this._spans.find((s) => s.key === key)?.events[0]?.id ?? null;
8522
+ }
8523
+ updated(changed) {
8524
+ if (this._revealSelection) {
8525
+ this._revealSelection = false;
8526
+ this.updateComplete.then(() => this._listRef.value?.updateComplete).then(() => this._listRef.value?.reveal());
8527
+ }
8528
+ if (changed.has("_selectedKey") || this._pendingSettled) {
8529
+ this._pendingSettled = false;
8530
+ this.dispatchEvent(new CustomEvent("selection-change"));
8531
+ }
8532
+ }
7662
8533
  _readStore() {
7663
8534
  if (!this._active) return;
7664
8535
  this._events = getTimelineEvents();
@@ -7690,7 +8561,8 @@ var TimelineView = class TimelineView extends i$2 {
7690
8561
  * to the recording flag and merged layer list. */
7691
8562
  _applySession(state) {
7692
8563
  const { recordingState } = state.layers;
7693
- if (recordingState && !this._recording) clearTimelineEvents();
8564
+ if (this._sessionSeen && recordingState && !this._recording) clearTimelineEvents();
8565
+ this._sessionSeen = true;
7694
8566
  this._recording = recordingState;
7695
8567
  this._layers = this._mergeLayers(state.layers, state.customLayers);
7696
8568
  }
@@ -7745,10 +8617,46 @@ var TimelineView = class TimelineView extends i$2 {
7745
8617
  console.warn("[lit-devtools] toggle-layer failed", err);
7746
8618
  });
7747
8619
  }
8620
+ _onModeChange(e) {
8621
+ e.stopPropagation();
8622
+ this._mode = e.detail.value === "tracks" ? "tracks" : "list";
8623
+ store(MODE_LS_KEY, this._mode);
8624
+ if (this._mode === "list") this.updateComplete.then(() => this._listRef.value?.reveal());
8625
+ }
8626
+ _onTrackToggle(e) {
8627
+ e.stopPropagation();
8628
+ const hidden = new Set(this._hiddenTracks);
8629
+ if (!hidden.delete(e.detail.id)) hidden.add(e.detail.id);
8630
+ this._hiddenTracks = hidden;
8631
+ store(HIDDEN_TRACKS_LS_KEY, JSON.stringify([...hidden]));
8632
+ }
8633
+ _onElementSelect(e) {
8634
+ const v = e.target.value;
8635
+ this._elementFilter = v === "" ? null : Number(v);
8636
+ }
8637
+ _onRegexInput(e) {
8638
+ this._regex = e.target.value;
8639
+ }
8640
+ /** The detail pane's **filter** link. */
8641
+ _onElementFilter(e) {
8642
+ this._elementFilter = e.detail.id;
8643
+ }
8644
+ _onSpanSelect(e) {
8645
+ this._pendingEventId = null;
8646
+ this._selectedKey = e.detail.key;
8647
+ }
7748
8648
  render() {
7749
8649
  if (this._error !== null) return b`<div class="error">${this._error}</div>`;
8650
+ const tracks = this._mode === "tracks";
8651
+ const captured = this._layers.filter((l) => l.enabled);
7750
8652
  return b`
7751
8653
  <div class="toolbar">
8654
+ <segmented-tabs
8655
+ size="sm"
8656
+ .items=${MODE_TABS}
8657
+ .value=${this._mode}
8658
+ @change=${this._onModeChange}
8659
+ ></segmented-tabs>
7752
8660
  ${this._exportNote === null ? A : b`<span class="export-note">${this._exportNote}</span>`}
7753
8661
  <span class="spacer"></span>
7754
8662
  <button
@@ -7770,10 +8678,67 @@ var TimelineView = class TimelineView extends i$2 {
7770
8678
  .layers=${this._layers}
7771
8679
  @layer-toggle=${this._onLayerToggle}
7772
8680
  ></timeline-layers>
8681
+ ${tracks ? b`<timeline-layers
8682
+ caption="Tracks"
8683
+ .layers=${captured.map((l) => ({
8684
+ ...l,
8685
+ enabled: !this._hiddenTracks.has(l.id)
8686
+ }))}
8687
+ @layer-toggle=${this._onTrackToggle}
8688
+ ></timeline-layers>` : A}
8689
+ ${this._events.length > 0 ? b`<div class="filterbar">
8690
+ ${this._elements.length > 0 ? b`
8691
+ <span>Element:</span>
8692
+ <select @change=${this._onElementSelect}>
8693
+ <option
8694
+ value=""
8695
+ ?selected=${this._elementFilter === null}
8696
+ >
8697
+ All elements
8698
+ </option>
8699
+ ${this._elements.map((el) => b`
8700
+ <option
8701
+ value=${el.id}
8702
+ ?selected=${this._elementFilter === el.id}
8703
+ >
8704
+ &lt;${el.tag}&gt; #${el.id}
8705
+ </option>
8706
+ `)}
8707
+ </select>
8708
+ ` : A}
8709
+ <input
8710
+ class="regex ${compileRegex(this._regex).invalid ? "invalid" : ""}"
8711
+ type="text"
8712
+ spellcheck="false"
8713
+ placeholder="filter regex…"
8714
+ title="Case-insensitive regex matched against element tag, title and subtitle"
8715
+ .value=${this._regex}
8716
+ @input=${this._onRegexInput}
8717
+ />
8718
+ </div>` : A}
7773
8719
  <timeline-event-list
8720
+ ${n(this._listRef)}
8721
+ ?hidden=${tracks}
7774
8722
  .events=${this._events}
8723
+ .spans=${this._spans}
7775
8724
  .layers=${this._layers}
8725
+ .selectedKey=${this._selectedKey}
8726
+ .elementFilter=${this._elementFilter}
8727
+ .regex=${this._regex}
8728
+ @span-select=${this._onSpanSelect}
8729
+ @element-filter=${this._onElementFilter}
7776
8730
  ></timeline-event-list>
8731
+ <timeline-tracks
8732
+ ?hidden=${!tracks}
8733
+ .spans=${this._filteredSpans}
8734
+ .layers=${this._layers}
8735
+ .visibleTracks=${this._visibleTracks}
8736
+ .selectedKey=${this._selectedKey}
8737
+ .selectedSpan=${this._selectedKey === null ? void 0 : this._spans.find((s) => s.key === this._selectedKey)}
8738
+ ?recording=${this._recording}
8739
+ @span-select=${this._onSpanSelect}
8740
+ @element-filter=${this._onElementFilter}
8741
+ ></timeline-tracks>
7777
8742
  `;
7778
8743
  }
7779
8744
  };
@@ -7783,6 +8748,11 @@ __decorate([r$2()], TimelineView.prototype, "_exportNote", void 0);
7783
8748
  __decorate([r$2()], TimelineView.prototype, "_events", void 0);
7784
8749
  __decorate([r$2()], TimelineView.prototype, "_layers", void 0);
7785
8750
  __decorate([r$2()], TimelineView.prototype, "_error", void 0);
8751
+ __decorate([r$2()], TimelineView.prototype, "_mode", void 0);
8752
+ __decorate([r$2()], TimelineView.prototype, "_hiddenTracks", void 0);
8753
+ __decorate([r$2()], TimelineView.prototype, "_selectedKey", void 0);
8754
+ __decorate([r$2()], TimelineView.prototype, "_elementFilter", void 0);
8755
+ __decorate([r$2()], TimelineView.prototype, "_regex", void 0);
7786
8756
  TimelineView = __decorate([t$2("timeline-view")], TimelineView);
7787
8757
  //#endregion
7788
8758
  //#region src/types/hmr-incompatibility.ts
@@ -8597,6 +9567,29 @@ var writeLocal = (override) => {
8597
9567
  else localStorage.setItem(SETTINGS_OVERRIDE_LS_KEY, JSON.stringify(override));
8598
9568
  } catch {}
8599
9569
  };
9570
+ /**
9571
+ * The config values the current overrides were made against; `{}` when unset
9572
+ * or unparsable. Deliberately separate from the override itself, so the
9573
+ * runtime's apply and the live push never carry it.
9574
+ */
9575
+ var readBaselines = () => {
9576
+ try {
9577
+ const raw = localStorage.getItem(OVERRIDE_BASELINES_LS_KEY);
9578
+ if (raw !== null) return JSON.parse(raw);
9579
+ } catch {}
9580
+ return {};
9581
+ };
9582
+ var writeLocalBaselines = (baselines) => {
9583
+ try {
9584
+ if (baselines === null || Object.keys(baselines).length === 0) localStorage.removeItem(OVERRIDE_BASELINES_LS_KEY);
9585
+ else localStorage.setItem(OVERRIDE_BASELINES_LS_KEY, JSON.stringify(baselines));
9586
+ } catch {}
9587
+ };
9588
+ /** Write the durable baselines (or delete them when empty), best-effort. */
9589
+ var persistBaselines = (baselines) => {
9590
+ const empty = baselines === void 0 || Object.keys(baselines).length === 0;
9591
+ litRpc().then((rpc) => empty ? rpc.settings.global.delete("overrideBaselines") : rpc.settings.global.set("overrideBaselines", baselines)).catch(() => {});
9592
+ };
8600
9593
  /** Send an override to the app runtime over RPC, best-effort. */
8601
9594
  var pushLive = (override) => {
8602
9595
  litRpc().then((rpc) => rpc.rpc.call("set-settings-override", override)).catch(() => {});
@@ -8616,10 +9609,18 @@ var onOverrideChange = (cb) => {
8616
9609
  listeners.delete(cb);
8617
9610
  };
8618
9611
  };
8619
- /** A change made in this panel: all three copies, then listeners. */
8620
- var commitOverride = (next) => {
9612
+ /**
9613
+ * A change made in this panel: all three copies, then listeners. `baselines`,
9614
+ * when given, replaces the recorded baselines (local and durable; never sent
9615
+ * to the page).
9616
+ */
9617
+ var commitOverride = (next, baselines) => {
8621
9618
  writeLocal(next);
8622
9619
  persist(next);
9620
+ if (baselines !== void 0) {
9621
+ writeLocalBaselines(baselines);
9622
+ persistBaselines(baselines);
9623
+ }
8623
9624
  pushLive(next);
8624
9625
  notify(next);
8625
9626
  };
@@ -8634,8 +9635,9 @@ var patchOverride = (patch) => {
8634
9635
  * A value that arrived *from* the durable store: mirror it locally and to the
8635
9636
  * page, but don't write it back — that would be a pointless round trip.
8636
9637
  */
8637
- var adoptOverride = (next) => {
9638
+ var adoptOverride = (next, baselines) => {
8638
9639
  writeLocal(next);
9640
+ if (baselines !== void 0) writeLocalBaselines(baselines);
8639
9641
  pushLive(next);
8640
9642
  notify(next);
8641
9643
  };
@@ -8647,9 +9649,46 @@ var adoptOverride = (next) => {
8647
9649
  var resetOverride = (envValues) => {
8648
9650
  writeLocal(null);
8649
9651
  persist(void 0);
9652
+ writeLocalBaselines(null);
9653
+ persistBaselines(void 0);
8650
9654
  if (envValues !== void 0) pushLive(envValues);
8651
9655
  notify({});
8652
9656
  };
9657
+ /**
9658
+ * Drop one key from the override, leaving the rest in place. `baseline` is
9659
+ * that key's resolved config value; it goes to the live runtime alongside the
9660
+ * remaining override (a missing field would leave the current live value in
9661
+ * place) but isn't stored, so the key reads as un-overridden again.
9662
+ */
9663
+ var dropOverrideKey = (key, baseline) => {
9664
+ const next = { ...readOverride() };
9665
+ delete next[key];
9666
+ const empty = Object.keys(next).length === 0;
9667
+ writeLocal(empty ? null : next);
9668
+ persist(empty ? void 0 : next);
9669
+ const baselines = readBaselines();
9670
+ delete baselines[key];
9671
+ writeLocalBaselines(baselines);
9672
+ persistBaselines(baselines);
9673
+ pushLive({
9674
+ ...next,
9675
+ [key]: baseline
9676
+ });
9677
+ notify(next);
9678
+ };
9679
+ /**
9680
+ * Re-stamp one key's baseline to the config value it has now ("Keep"), so the
9681
+ * changed-config hint goes away while the override stays.
9682
+ */
9683
+ var keepBaseline = (key, current) => {
9684
+ const next = {
9685
+ ...readBaselines(),
9686
+ [key]: current
9687
+ };
9688
+ writeLocalBaselines(next);
9689
+ persistBaselines(next);
9690
+ notify(readOverride());
9691
+ };
8653
9692
  //#endregion
8654
9693
  //#region src/panel/components-view.ts
8655
9694
  /**
@@ -9707,6 +10746,8 @@ var DEEP_LINK_TABS = [
9707
10746
  "settings"
9708
10747
  ];
9709
10748
  var isTab = (value) => typeof value === "string" && DEEP_LINK_TABS.includes(value);
10749
+ /** True when a link asks for anything at all. */
10750
+ var hasLink = (link) => link.tab !== void 0 || link.componentId !== void 0 || link.eventId !== void 0;
9710
10751
  /** Parse a `DeepLink` out of hash params, ignoring anything unrecognised. */
9711
10752
  var fromParams = (params) => {
9712
10753
  const link = {};
@@ -9714,6 +10755,8 @@ var fromParams = (params) => {
9714
10755
  if (isTab(tab)) link.tab = tab;
9715
10756
  const id = Number(params.get("component"));
9716
10757
  if (params.get("component") !== null && Number.isInteger(id)) link.componentId = id;
10758
+ const eventId = params.get("event");
10759
+ if (eventId !== null && eventId !== "") link.eventId = eventId;
9717
10760
  return link;
9718
10761
  };
9719
10762
  /** Read the link encoded in the current URL hash, if any. */
@@ -9733,6 +10776,8 @@ var writeHashLink = (link) => {
9733
10776
  else params.set("tab", link.tab);
9734
10777
  if (link.componentId === void 0) params.delete("component");
9735
10778
  else params.set("component", String(link.componentId));
10779
+ if (link.eventId === void 0) params.delete("event");
10780
+ else params.set("event", link.eventId);
9736
10781
  const next = params.toString();
9737
10782
  if (next === location.hash.replace(/^#/, "")) return;
9738
10783
  history.replaceState(history.state, "", next === "" ? location.pathname : `#${next}`);
@@ -9747,7 +10792,7 @@ var writeHashLink = (link) => {
9747
10792
  */
9748
10793
  var onDeepLink = (apply) => {
9749
10794
  const hashLink = readHashLink();
9750
- if (hashLink.tab !== void 0 || hashLink.componentId !== void 0) apply(hashLink);
10795
+ if (hasLink(hashLink)) apply(hashLink);
9751
10796
  window.addEventListener("hashchange", () => apply(readHashLink()));
9752
10797
  (async () => {
9753
10798
  try {
@@ -9762,7 +10807,9 @@ var onDeepLink = (apply) => {
9762
10807
  if (isTab(tab)) link.tab = tab;
9763
10808
  const id = params["componentId"];
9764
10809
  if (typeof id === "number" && Number.isInteger(id)) link.componentId = id;
9765
- if (link.tab !== void 0 || link.componentId !== void 0) apply(link);
10810
+ const eventId = params["eventId"];
10811
+ if (typeof eventId === "string" && eventId !== "") link.eventId = eventId;
10812
+ if (hasLink(link)) apply(link);
9766
10813
  };
9767
10814
  handle(state.value());
9768
10815
  state.on("updated", handle);
@@ -9770,6 +10817,27 @@ var onDeepLink = (apply) => {
9770
10817
  })();
9771
10818
  };
9772
10819
  //#endregion
10820
+ //#region src/lib/override-baselines.ts
10821
+ /**
10822
+ * @license
10823
+ * Copyright 2026 Google LLC
10824
+ * SPDX-License-Identifier: BSD-3-Clause
10825
+ */
10826
+ /**
10827
+ * Whether the config value an override was made against has since changed.
10828
+ *
10829
+ * `recorded` is `undefined` for a legacy override that predates baseline
10830
+ * tracking, which never counts as a change (no false positives). Values are
10831
+ * compared structurally. The source-overlay editor reports `'custom'` for an
10832
+ * `EditorConfig` object, which can't be compared meaningfully, so any side
10833
+ * being `'custom'` skips the check.
10834
+ */
10835
+ var baselineChanged = (key, recorded, current) => {
10836
+ if (recorded === void 0 || current === void 0) return false;
10837
+ if (key === "sourceOverlayEditor" && (recorded === "custom" || current === "custom")) return false;
10838
+ return JSON.stringify(recorded) !== JSON.stringify(current);
10839
+ };
10840
+ //#endregion
9773
10841
  //#region src/panel/devtools-settings.ts
9774
10842
  /**
9775
10843
  * @license
@@ -9781,6 +10849,7 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
9781
10849
  super(..._args);
9782
10850
  this._settings = null;
9783
10851
  this._override = {};
10852
+ this._recorded = {};
9784
10853
  this._loaded = false;
9785
10854
  this._colorScheme = "auto";
9786
10855
  this._unsubscribeOverride = null;
@@ -9880,6 +10949,52 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
9880
10949
  margin-left: var(--lit-devtools-space-3);
9881
10950
  vertical-align: middle;
9882
10951
  }
10952
+ .src {
10953
+ margin-left: var(--lit-devtools-space-3);
10954
+ vertical-align: middle;
10955
+ }
10956
+ .row-reset {
10957
+ appearance: none;
10958
+ border: 1px solid var(--lit-devtools-border-strong);
10959
+ background: var(--lit-devtools-surface-elevated);
10960
+ color: var(--lit-devtools-text-muted);
10961
+ border-radius: var(--lit-devtools-radius-sm);
10962
+ font: inherit;
10963
+ font-size: var(--lit-devtools-text-2xs);
10964
+ line-height: 1;
10965
+ padding: 1px var(--lit-devtools-space-2);
10966
+ margin-left: var(--lit-devtools-space-3);
10967
+ vertical-align: middle;
10968
+ cursor: pointer;
10969
+ }
10970
+ .row-reset:hover {
10971
+ background: var(--lit-devtools-surface-hover);
10972
+ color: var(--lit-devtools-text);
10973
+ }
10974
+ .nudge {
10975
+ display: flex;
10976
+ align-items: center;
10977
+ flex-wrap: wrap;
10978
+ gap: var(--lit-devtools-space-3);
10979
+ margin-top: var(--lit-devtools-space-2);
10980
+ color: var(--lit-devtools-warning);
10981
+ font-family: inherit;
10982
+ font-size: var(--lit-devtools-text-2xs);
10983
+ }
10984
+ .nudge button {
10985
+ appearance: none;
10986
+ border: 1px solid var(--lit-devtools-border-strong);
10987
+ background: var(--lit-devtools-surface-elevated);
10988
+ color: var(--lit-devtools-text);
10989
+ border-radius: var(--lit-devtools-radius-sm);
10990
+ font: inherit;
10991
+ line-height: 1;
10992
+ padding: 1px var(--lit-devtools-space-3);
10993
+ cursor: pointer;
10994
+ }
10995
+ .nudge button:hover {
10996
+ background: var(--lit-devtools-surface-hover);
10997
+ }
9883
10998
  label.toggle {
9884
10999
  display: inline-flex;
9885
11000
  align-items: center;
@@ -9920,8 +11035,10 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
9920
11035
  connectedCallback() {
9921
11036
  super.connectedCallback();
9922
11037
  this._override = readOverride();
11038
+ this._recorded = readBaselines();
9923
11039
  this._unsubscribeOverride = onOverrideChange((o) => {
9924
11040
  this._override = o;
11041
+ this._recorded = readBaselines();
9925
11042
  });
9926
11043
  this._colorScheme = readColorSchemePreference();
9927
11044
  this._fetch();
@@ -9961,16 +11078,16 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
9961
11078
  }
9962
11079
  /**
9963
11080
  * Apply a settings snapshot. Both branches no-op when the value already
9964
- * matches, which is what stops `_adopt` -> `_push` -> store write ->
11081
+ * matches, which is what stops `_adopt` -> `commitOverride` -> store write ->
9965
11082
  * `onChange` -> `_adopt` from looping.
9966
11083
  */
9967
11084
  _adopt(all) {
9968
- const { appearance, override } = all;
11085
+ const { appearance, override, overrideBaselines } = all;
9969
11086
  if (appearance !== void 0 && appearance !== this._colorScheme) {
9970
11087
  this._colorScheme = appearance;
9971
11088
  setColorSchemePreference(appearance);
9972
11089
  }
9973
- if (override !== void 0 && JSON.stringify(override) !== JSON.stringify(this._override)) adoptOverride(override);
11090
+ if (override !== void 0 && (JSON.stringify(override) !== JSON.stringify(this._override) || JSON.stringify(overrideBaselines ?? {}) !== JSON.stringify(readBaselines()))) adoptOverride(override, overrideBaselines ?? {});
9974
11091
  }
9975
11092
  /** Write one preference through to the durable store, best-effort. */
9976
11093
  _persist(key, value) {
@@ -9991,43 +11108,92 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
9991
11108
  this._loaded = true;
9992
11109
  }
9993
11110
  }
9994
- /** Persist the merged override and push it to the app runtime. */
9995
- _push(override) {
9996
- commitOverride(override);
9997
- }
9998
11111
  _set(key, value) {
9999
- this._push({
11112
+ const s = this._settings;
11113
+ const baselines = readBaselines();
11114
+ if (s !== null && key in this._baselines(s)) baselines[key] = this._baselines(s)[key];
11115
+ commitOverride({
10000
11116
  ...this._override,
10001
11117
  [key]: value
10002
- });
11118
+ }, baselines);
10003
11119
  }
10004
- /** Clear overrides and revert the runtime to the resolved env values. */
10005
- _reset() {
10006
- const s = this._settings;
10007
- resetOverride(s === null ? void 0 : {
11120
+ /** The resolved config value of every overridable setting. */
11121
+ _baselines(s) {
11122
+ return {
10008
11123
  hmrReconnect: s.hmr.reconnect,
10009
11124
  hmrOnIncompatible: s.hmr.onIncompatible,
10010
11125
  hmrIndicatorVisible: s.hmr.indicatorEnabled,
10011
11126
  hmrIndicatorCount: s.hmr.indicatorCount,
10012
- sourceOverlayEditor: s.sourceOverlay.editor,
11127
+ sourceOverlayEditor: s.sourceOverlay.editor
11128
+ };
11129
+ }
11130
+ /** Clear overrides and revert the runtime to the resolved env values. */
11131
+ _reset() {
11132
+ const s = this._settings;
11133
+ resetOverride(s === null ? void 0 : {
11134
+ ...this._baselines(s),
10013
11135
  flashUpdates: false,
10014
- flashUpdatesRamp: false
11136
+ flashUpdatesRamp: false,
11137
+ chromeTracks: false
10015
11138
  });
10016
11139
  }
11140
+ /** Drop one overridden setting, reverting it (live) to its baseline. */
11141
+ _resetKey(key) {
11142
+ const s = this._settings;
11143
+ if (s === null) return;
11144
+ dropOverrideKey(key, this._baselines(s)[key]);
11145
+ }
10017
11146
  _pill(on) {
10018
11147
  return b`<span class="pill ${on ? "on" : ""}"
10019
11148
  >${on ? "enabled" : "disabled"}</span
10020
11149
  >`;
10021
11150
  }
10022
- _ovr(overridden) {
10023
- return overridden ? b`<span class="ovr">(overridden)</span>` : A;
11151
+ /**
11152
+ * Origin badge behind a value, from the server-side provenance:
11153
+ * "(env)", "(option)" or "(default)". Nothing when the server sent none.
11154
+ */
11155
+ _source(key) {
11156
+ const src = this._settings?.sources?.[key];
11157
+ return src ? b`<span class="env src">(${src})</span>` : A;
11158
+ }
11159
+ /**
11160
+ * Badge for an overridable row. Without an override it's the plain origin
11161
+ * badge; with one it's "(overridden)", the baseline it replaced
11162
+ * ("env: Zed") and a reset for just this row. `baseline` is the config
11163
+ * value as the row displays it, `fmt` renders a recorded raw value the same
11164
+ * way. When the config value moved since the override was made, a second
11165
+ * line says so and offers Reset or Keep.
11166
+ */
11167
+ _ovrSource(key, baseline, fmt = String) {
11168
+ if (this._override[key] === void 0) return this._source(key);
11169
+ const src = this._settings?.sources?.[key] ?? "config";
11170
+ const s = this._settings;
11171
+ const current = s === null ? void 0 : this._baselines(s)[key];
11172
+ return b`<span class="ovr">(overridden)</span>
11173
+ <span class="env">${src}: ${baseline}</span>
11174
+ <button
11175
+ class="row-reset"
11176
+ title="Reset to the ${src} value"
11177
+ aria-label="Reset to the ${src} value"
11178
+ @click=${() => this._resetKey(key)}
11179
+ >
11180
+ ×
11181
+ </button>
11182
+ ${baselineChanged(key, this._recorded[key], current) ? b`<div class="nudge" data-nudge=${key}>
11183
+ <span
11184
+ >Config changed since you overrode this: was
11185
+ ${fmt(this._recorded[key])}, now ${baseline}</span
11186
+ >
11187
+ <button @click=${() => this._resetKey(key)}>Reset</button>
11188
+ <button @click=${() => keepBaseline(key, current)}>Keep</button>
11189
+ </div>` : A}`;
10024
11190
  }
10025
- _readonlyRow(key, value, env) {
11191
+ _readonlyRow(key, value, env, source) {
10026
11192
  return b`
10027
11193
  <tr>
10028
11194
  <td class="key">${key}</td>
10029
11195
  <td class="val">
10030
- ${String(value)}
11196
+ ${String(value)} ${source ? this._source(source) : A}
10031
11197
  ${env ? b`<span class="env">${env}</span>` : A}
10032
11198
  </td>
10033
11199
  </tr>
@@ -10083,7 +11249,7 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
10083
11249
  />
10084
11250
  ${reconnect ? "on" : "off"}
10085
11251
  </label>
10086
- ${this._ovr(o.hmrReconnect !== void 0)}
11252
+ ${this._ovrSource("hmrReconnect", s.hmr.reconnect ? "on" : "off", (v) => v ? "on" : "off")}
10087
11253
  </td>
10088
11254
  </tr>
10089
11255
  <tr>
@@ -10099,7 +11265,7 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
10099
11265
  warn
10100
11266
  </option>
10101
11267
  </select>
10102
- ${this._ovr(o.hmrOnIncompatible !== void 0)}
11268
+ ${this._ovrSource("hmrOnIncompatible", s.hmr.onIncompatible)}
10103
11269
  </td>
10104
11270
  </tr>
10105
11271
  <tr class=${s.hmr.indicatorEnabled ? "" : "row-disabled"}>
@@ -10114,7 +11280,7 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
10114
11280
  />
10115
11281
  ${s.hmr.indicatorEnabled ? indicatorVisible ? "shown" : "hidden" : "off (config)"}
10116
11282
  </label>
10117
- ${this._ovr(o.hmrIndicatorVisible !== void 0)}
11283
+ ${this._ovrSource("hmrIndicatorVisible", s.hmr.indicatorEnabled ? "shown" : "off (config)", (v) => v ? "shown" : "off (config)")}
10118
11284
  </td>
10119
11285
  </tr>
10120
11286
  <tr class=${s.hmr.indicatorEnabled ? "" : "row-disabled"}>
@@ -10129,10 +11295,41 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
10129
11295
  />
10130
11296
  ${indicatorCount ? "shown" : "hidden"}
10131
11297
  </label>
10132
- ${this._ovr(o.hmrIndicatorCount !== void 0)}
11298
+ ${this._ovrSource("hmrIndicatorCount", s.hmr.indicatorCount ? "shown" : "hidden", (v) => v ? "shown" : "hidden")}
10133
11299
  </td>
10134
11300
  </tr>
10135
11301
  </table>
11302
+ `;
11303
+ }
11304
+ /**
11305
+ * The editor select. A custom `EditorConfig` object (reported as
11306
+ * `'custom'`) isn't one of the built-in choices, so the select is disabled
11307
+ * and just shows "Custom".
11308
+ */
11309
+ _renderEditorRow(s) {
11310
+ const baseline = s.sourceOverlay.editor;
11311
+ const custom = baseline === "custom";
11312
+ const current = this._override.sourceOverlayEditor ?? baseline;
11313
+ const editorLabel = (value) => SOURCE_OVERLAY_EDITORS.find((ed) => ed.value === value)?.label ?? (value === "custom" ? "Custom" : value);
11314
+ const label = editorLabel(baseline);
11315
+ return b`
11316
+ <tr>
11317
+ <td class="key">editor</td>
11318
+ <td class="val">
11319
+ <select
11320
+ ?disabled=${custom}
11321
+ @change=${(e) => this._set("sourceOverlayEditor", e.target.value)}
11322
+ >
11323
+ ${custom ? b`<option value="custom" selected>Custom</option>` : SOURCE_OVERLAY_EDITORS.map((ed) => b`<option
11324
+ value=${ed.value}
11325
+ ?selected=${ed.value === current}
11326
+ >
11327
+ ${ed.label}
11328
+ </option>`)}
11329
+ </select>
11330
+ ${this._ovrSource("sourceOverlayEditor", label, (v) => editorLabel(String(v)))}
11331
+ </td>
11332
+ </tr>
10136
11333
  `;
10137
11334
  }
10138
11335
  /**
@@ -10174,6 +11371,30 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
10174
11371
  </td>
10175
11372
  </tr>
10176
11373
  </table>
11374
+ `;
11375
+ }
11376
+ /**
11377
+ * Timeline preferences. Pure preference, no config-time baseline, so an
11378
+ * unset value means off.
11379
+ */
11380
+ _renderTimelinePrefs() {
11381
+ const chrome = this._override.chromeTracks ?? false;
11382
+ return b`
11383
+ <table>
11384
+ <tr>
11385
+ <td class="key">chrome performance tracks</td>
11386
+ <td class="val">
11387
+ <label class="toggle">
11388
+ <input
11389
+ type="checkbox"
11390
+ .checked=${chrome}
11391
+ @change=${(e) => this._set("chromeTracks", e.target.checked)}
11392
+ />
11393
+ ${chrome ? "on" : "off"}
11394
+ </label>
11395
+ </td>
11396
+ </tr>
11397
+ </table>
10177
11398
  `;
10178
11399
  }
10179
11400
  render() {
@@ -10203,24 +11424,9 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
10203
11424
  <section>
10204
11425
  <h3>Source Overlay ${this._pill(s.sourceOverlay.enabled)}</h3>
10205
11426
  ${s.sourceOverlay.enabled ? b`<table>
10206
- ${this._readonlyRow("hotkey", `Ctrl+Shift+${s.sourceOverlay.key.toUpperCase()}`, "LIT_PLUGIN_SOURCE_OVERLAY_KEY")}
10207
- <tr>
10208
- <td class="key">editor</td>
10209
- <td class="val">
10210
- <select
10211
- @change=${(e) => this._set("sourceOverlayEditor", e.target.value)}
10212
- >
10213
- ${SOURCE_OVERLAY_EDITORS.map((ed) => b`<option
10214
- value=${ed.value}
10215
- ?selected=${ed.value === (this._override.sourceOverlayEditor ?? s.sourceOverlay.editor)}
10216
- >
10217
- ${ed.label}
10218
- </option>`)}
10219
- </select>
10220
- ${this._ovr(this._override.sourceOverlayEditor !== void 0)}
10221
- </td>
10222
- </tr>
10223
- ${this._readonlyRow("throttle (ms)", s.sourceOverlay.throttleMs, "LIT_PLUGIN_SOURCE_OVERLAY_THROTTLE_MS")}
11427
+ ${this._readonlyRow("hotkey", `Ctrl+Shift+${s.sourceOverlay.key.toUpperCase()}`, "LIT_PLUGIN_SOURCE_OVERLAY_KEY", "sourceOverlayKey")}
11428
+ ${this._renderEditorRow(s)}
11429
+ ${this._readonlyRow("throttle (ms)", s.sourceOverlay.throttleMs, "LIT_PLUGIN_SOURCE_OVERLAY_THROTTLE_MS", "sourceOverlayThrottleMs")}
10224
11430
  </table>` : b`<p class="empty">
10225
11431
  Enable with <code>sourceOverlay: true</code> or
10226
11432
  <code>LIT_PLUGIN_SOURCE_OVERLAY=true</code>.
@@ -10241,131 +11447,18 @@ var DevtoolsSettings = class DevtoolsSettings extends i$2 {
10241
11447
  <p class="empty">
10242
11448
  Layers and recording are controlled in the Timeline tab.
10243
11449
  </p>
11450
+ ${s.timeline ? this._renderTimelinePrefs() : A}
10244
11451
  </section>
10245
11452
  `;
10246
11453
  }
10247
11454
  };
10248
11455
  __decorate([r$2()], DevtoolsSettings.prototype, "_settings", void 0);
10249
11456
  __decorate([r$2()], DevtoolsSettings.prototype, "_override", void 0);
11457
+ __decorate([r$2()], DevtoolsSettings.prototype, "_recorded", void 0);
10250
11458
  __decorate([r$2()], DevtoolsSettings.prototype, "_loaded", void 0);
10251
11459
  __decorate([r$2()], DevtoolsSettings.prototype, "_colorScheme", void 0);
10252
11460
  DevtoolsSettings = __decorate([t$2("devtools-settings")], DevtoolsSettings);
10253
11461
  //#endregion
10254
- //#region src/lib/segmented-tabs.ts
10255
- /**
10256
- * @license
10257
- * Copyright 2026 Google LLC
10258
- * SPDX-License-Identifier: BSD-3-Clause
10259
- */
10260
- var SegmentedTabs = class SegmentedTabs extends i$2 {
10261
- static {
10262
- this.styles = [tokens, i$5`
10263
- :host {
10264
- display: flex;
10265
- gap: var(--lit-devtools-space-1);
10266
- align-self: stretch;
10267
- }
10268
- button {
10269
- appearance: none;
10270
- display: flex;
10271
- align-items: center;
10272
- gap: var(--lit-devtools-space-2);
10273
- border: 0;
10274
- background: none;
10275
- font: inherit;
10276
- color: var(--lit-devtools-text-muted);
10277
- padding: 0 var(--lit-devtools-space-5);
10278
- cursor: pointer;
10279
- border-bottom: 2px solid transparent;
10280
- white-space: nowrap;
10281
- transition:
10282
- color var(--lit-devtools-dur-fast) var(--lit-devtools-ease-standard),
10283
- border-color var(--lit-devtools-dur-fast)
10284
- var(--lit-devtools-ease-standard),
10285
- background var(--lit-devtools-dur-fast)
10286
- var(--lit-devtools-ease-standard);
10287
- }
10288
- button:hover {
10289
- color: var(--lit-devtools-text);
10290
- background: var(--lit-devtools-surface-hover);
10291
- }
10292
- button.active {
10293
- color: var(--lit-devtools-accent);
10294
- border-bottom-color: var(--lit-devtools-accent);
10295
- }
10296
- button:focus-visible {
10297
- outline: 2px solid var(--lit-devtools-accent-ring);
10298
- outline-offset: -2px;
10299
- }
10300
- button svg {
10301
- width: 18px;
10302
- height: 18px;
10303
- flex-shrink: 0;
10304
- }
10305
- .badge {
10306
- flex-shrink: 0;
10307
- min-width: 16px;
10308
- padding: 0 var(--lit-devtools-space-1);
10309
- border-radius: var(--lit-devtools-radius-pill);
10310
- background: var(--lit-devtools-error-soft);
10311
- color: var(--lit-devtools-error);
10312
- font-size: var(--lit-devtools-text-xs);
10313
- line-height: 16px;
10314
- text-align: center;
10315
- }
10316
- :host([size='sm']) button {
10317
- font-size: var(--lit-devtools-text-xs);
10318
- padding: 0 var(--lit-devtools-space-4);
10319
- }
10320
- :host([size='md']) button,
10321
- :host(:not([size])) button {
10322
- font-size: var(--lit-devtools-text-sm);
10323
- padding: 0 var(--lit-devtools-space-5);
10324
- }
10325
- `];
10326
- }
10327
- constructor() {
10328
- super();
10329
- this.items = [];
10330
- this.value = "";
10331
- this.size = "md";
10332
- }
10333
- _select(id) {
10334
- this.value = id;
10335
- this.dispatchEvent(new CustomEvent("change", {
10336
- detail: { value: id },
10337
- bubbles: true,
10338
- composed: true
10339
- }));
10340
- }
10341
- render() {
10342
- return b`
10343
- ${this.items.map((item) => b`
10344
- <button
10345
- role="tab"
10346
- aria-selected=${item.id === this.value}
10347
- class=${item.id === this.value ? "active" : ""}
10348
- @click=${() => this._select(item.id)}
10349
- >
10350
- ${item.icon ? b`<svg
10351
- viewBox="0 0 256 256"
10352
- fill="currentColor"
10353
- aria-hidden="true"
10354
- >
10355
- <path d=${item.icon} />
10356
- </svg>` : A}
10357
- ${item.label}
10358
- ${item.badge ? b`<span class="badge">${item.badge}</span>` : A}
10359
- </button>
10360
- `)}
10361
- `;
10362
- }
10363
- };
10364
- __decorate([n$3({ type: Array })], SegmentedTabs.prototype, "items", void 0);
10365
- __decorate([n$3()], SegmentedTabs.prototype, "value", void 0);
10366
- __decorate([n$3()], SegmentedTabs.prototype, "size", void 0);
10367
- SegmentedTabs = __decorate([t$2("segmented-tabs")], SegmentedTabs);
10368
- //#endregion
10369
11462
  //#region src/lib/icons.ts
10370
11463
  /**
10371
11464
  * @license
@@ -10510,6 +11603,15 @@ var LitDevtoolsPanel = class LitDevtoolsPanel extends i$2 {
10510
11603
  firstUpdated() {
10511
11604
  onDeepLink((link) => {
10512
11605
  if (link.tab !== void 0) this._tab = link.tab;
11606
+ if (link.componentId === void 0 && link.eventId !== void 0) {
11607
+ this._tab = "timeline";
11608
+ const eventId = link.eventId;
11609
+ this.updateComplete.then(() => {
11610
+ this._timelineView?.selectEvent(eventId);
11611
+ this._syncHash();
11612
+ });
11613
+ return;
11614
+ }
10513
11615
  if (link.componentId === void 0) {
10514
11616
  this._syncHash();
10515
11617
  return;
@@ -10532,9 +11634,11 @@ var LitDevtoolsPanel = class LitDevtoolsPanel extends i$2 {
10532
11634
  _syncHash() {
10533
11635
  if (window.top !== window.self) return;
10534
11636
  const selectedId = this._tab === "updates" ? this._updatesView?.selectedId : this._componentsView?.selectedId;
11637
+ const eventId = this._tab === "timeline" ? this._timelineView?.selectedEventId : null;
10535
11638
  writeHashLink({
10536
11639
  tab: this._tab,
10537
- ...selectedId === null || selectedId === void 0 ? {} : { componentId: selectedId }
11640
+ ...selectedId === null || selectedId === void 0 ? {} : { componentId: selectedId },
11641
+ ...eventId === null || eventId === void 0 ? {} : { eventId }
10538
11642
  });
10539
11643
  }
10540
11644
  /**
@@ -10576,7 +11680,10 @@ var LitDevtoolsPanel = class LitDevtoolsPanel extends i$2 {
10576
11680
  ></segmented-tabs>
10577
11681
  </header>
10578
11682
  <div class="view" @inspect-element=${this._onInspectElement}>
10579
- <timeline-view ?hidden=${this._tab !== "timeline"}></timeline-view>
11683
+ <timeline-view
11684
+ ?hidden=${this._tab !== "timeline"}
11685
+ @selection-change=${this._syncHash}
11686
+ ></timeline-view>
10580
11687
  <!-- Kept mounted (like the timeline) so an overlay inspect-pick can
10581
11688
  arrive and switch us here even while another tab is in front. -->
10582
11689
  <components-view
@@ -10600,6 +11707,7 @@ __decorate([r$2()], LitDevtoolsPanel.prototype, "_tab", void 0);
10600
11707
  __decorate([r$2()], LitDevtoolsPanel.prototype, "_hmrCount", void 0);
10601
11708
  __decorate([e$3("components-view")], LitDevtoolsPanel.prototype, "_componentsView", void 0);
10602
11709
  __decorate([e$3("updates-view")], LitDevtoolsPanel.prototype, "_updatesView", void 0);
11710
+ __decorate([e$3("timeline-view")], LitDevtoolsPanel.prototype, "_timelineView", void 0);
10603
11711
  LitDevtoolsPanel = __decorate([t$2("lit-devtools-panel")], LitDevtoolsPanel);
10604
11712
  //#endregion
10605
11713
  //#region src/panel/main.ts
@@ -10611,4 +11719,4 @@ LitDevtoolsPanel = __decorate([t$2("lit-devtools-panel")], LitDevtoolsPanel);
10611
11719
  //#endregion
10612
11720
  export { onBrowserAgentToolsChanged as n, nanoid as r, listBrowserAgentTools as t };
10613
11721
 
10614
- //# sourceMappingURL=index-DVe6M4HV.js.map
11722
+ //# sourceMappingURL=index-DVr5EnyB.js.map