uicore-ts 1.1.310 → 1.1.312

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.
@@ -0,0 +1,2535 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var UILayoutDebugger_exports = {};
20
+ __export(UILayoutDebugger_exports, {
21
+ UILayoutDebugger: () => UILayoutDebugger
22
+ });
23
+ module.exports = __toCommonJS(UILayoutDebugger_exports);
24
+ const _UILayoutDebugger = class {
25
+ static get isEnabled() {
26
+ return _UILayoutDebugger._isEnabled;
27
+ }
28
+ static get breakpointsEnabled() {
29
+ return _UILayoutDebugger._breakpointsEnabled;
30
+ }
31
+ static enable() {
32
+ _UILayoutDebugger._isEnabled = true;
33
+ _UILayoutDebugger._ensureOverlay();
34
+ _UILayoutDebugger._renderOverlay();
35
+ console.log(
36
+ "%c[UILayoutDebugger] ENABLED \u2014 recording layout traces and showing overlay.",
37
+ "color: #4CAF50; font-weight: bold"
38
+ );
39
+ }
40
+ static disable() {
41
+ _UILayoutDebugger._isEnabled = false;
42
+ _UILayoutDebugger._breakpointsEnabled = false;
43
+ _UILayoutDebugger._removeOverlay();
44
+ console.log(
45
+ "%c[UILayoutDebugger] DISABLED.",
46
+ "color: #9E9E9E; font-weight: bold"
47
+ );
48
+ }
49
+ static enableBreakpoints() {
50
+ if (!_UILayoutDebugger._isEnabled) {
51
+ _UILayoutDebugger.enable();
52
+ }
53
+ _UILayoutDebugger._breakpointsEnabled = true;
54
+ console.log(
55
+ "%c[UILayoutDebugger] Breakpoint mode ON. Search for 'breakpointOnThisLine' in Sources to set your breakpoint.",
56
+ "color: #FF9800; font-weight: bold"
57
+ );
58
+ }
59
+ static disableBreakpoints() {
60
+ _UILayoutDebugger._breakpointsEnabled = false;
61
+ console.log(
62
+ "%c[UILayoutDebugger] Breakpoint mode OFF.",
63
+ "color: #9E9E9E"
64
+ );
65
+ }
66
+ static stepForward() {
67
+ if (!_UILayoutDebugger._isEnabled) {
68
+ return;
69
+ }
70
+ const trace = _UILayoutDebugger._currentReplayTrace;
71
+ if (!trace) {
72
+ return;
73
+ }
74
+ const next = _UILayoutDebugger._replayStepIndex + 1;
75
+ _UILayoutDebugger.goToStep(Math.min(next, trace.steps.length - 1));
76
+ }
77
+ static stepBack() {
78
+ if (!_UILayoutDebugger._isEnabled) {
79
+ return;
80
+ }
81
+ _UILayoutDebugger.goToStep(Math.max(_UILayoutDebugger._replayStepIndex - 1, -1));
82
+ }
83
+ static goToStep(stepIndex) {
84
+ if (!_UILayoutDebugger._isEnabled) {
85
+ return;
86
+ }
87
+ _UILayoutDebugger._replayStepIndex = stepIndex;
88
+ _UILayoutDebugger._renderOverlay();
89
+ }
90
+ static goToCompareStep(stepIndex) {
91
+ if (!_UILayoutDebugger._isEnabled) {
92
+ return;
93
+ }
94
+ _UILayoutDebugger._compareStepIndex = stepIndex;
95
+ _UILayoutDebugger._renderOverlay();
96
+ }
97
+ static showTrace(traceIndex) {
98
+ if (!_UILayoutDebugger._isEnabled) {
99
+ return;
100
+ }
101
+ const clamped = Math.max(0, Math.min(traceIndex, _UILayoutDebugger._traces.length - 1));
102
+ _UILayoutDebugger._replayTraceIndex = clamped;
103
+ _UILayoutDebugger._replayStepIndex = -1;
104
+ _UILayoutDebugger._singleExpandState = /* @__PURE__ */ new Map();
105
+ _UILayoutDebugger._renderOverlay();
106
+ }
107
+ static showCompareTrace(traceIndex) {
108
+ if (!_UILayoutDebugger._isEnabled) {
109
+ return;
110
+ }
111
+ const clamped = Math.max(0, Math.min(traceIndex, _UILayoutDebugger._traces.length - 1));
112
+ _UILayoutDebugger._compareTraceIndex = clamped;
113
+ _UILayoutDebugger._compareStepIndex = -1;
114
+ _UILayoutDebugger._renderOverlay();
115
+ }
116
+ static get _currentReplayTrace() {
117
+ var _a;
118
+ return (_a = _UILayoutDebugger._traces[_UILayoutDebugger._replayTraceIndex]) != null ? _a : null;
119
+ }
120
+ static get _currentCompareTrace() {
121
+ var _a;
122
+ return (_a = _UILayoutDebugger._traces[_UILayoutDebugger._compareTraceIndex]) != null ? _a : null;
123
+ }
124
+ static willBeginLayoutPass(viewsToLayout) {
125
+ if (!_UILayoutDebugger._isEnabled) {
126
+ return;
127
+ }
128
+ _UILayoutDebugger._currentTrace = {
129
+ passIndex: _UILayoutDebugger._passIndex++,
130
+ steps: [],
131
+ roots: [],
132
+ cacheChanges: [],
133
+ totalIterations: 0
134
+ };
135
+ _UILayoutDebugger._currentIteration = 0;
136
+ _UILayoutDebugger._layoutCountsThisPass = /* @__PURE__ */ new Map();
137
+ _UILayoutDebugger._liveViewRegistry = /* @__PURE__ */ new Map();
138
+ _UILayoutDebugger._pendingStep = null;
139
+ _UILayoutDebugger._pendingSubviewsBefore = /* @__PURE__ */ new Map();
140
+ }
141
+ static willBeginIteration(iteration) {
142
+ if (!_UILayoutDebugger._isEnabled) {
143
+ return;
144
+ }
145
+ _UILayoutDebugger._currentIteration = iteration;
146
+ }
147
+ static viewDidCallSetNeedsLayout(view) {
148
+ var _a, _b;
149
+ if (!_UILayoutDebugger._isEnabled) {
150
+ return;
151
+ }
152
+ const viewIdx = (_a = view == null ? void 0 : view._UIViewIndex) != null ? _a : -1;
153
+ if (viewIdx < 0) {
154
+ return;
155
+ }
156
+ if (_UILayoutDebugger._triggerMap.has(viewIdx)) {
157
+ return;
158
+ }
159
+ const rawStack = (_b = new Error().stack) != null ? _b : "";
160
+ const cleanStack = _UILayoutDebugger._cleanStack(rawStack);
161
+ const callerFunction = _UILayoutDebugger._extractCallerFunctionName(cleanStack);
162
+ _UILayoutDebugger._triggerMap.set(viewIdx, { callerFunction, cleanStack });
163
+ }
164
+ static didSetCachedIntrinsicSize(view, cacheKey, value) {
165
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
166
+ if (!_UILayoutDebugger._isEnabled) {
167
+ return;
168
+ }
169
+ const trace = _UILayoutDebugger._currentTrace;
170
+ if (!trace) {
171
+ return;
172
+ }
173
+ const viewIdx = (_a = view == null ? void 0 : view._UIViewIndex) != null ? _a : -1;
174
+ if (viewIdx < 0) {
175
+ return;
176
+ }
177
+ const rawStack = (_b = new Error().stack) != null ? _b : "";
178
+ const cleanStack = _UILayoutDebugger._cleanStack(rawStack);
179
+ const callerFunction = _UILayoutDebugger._extractCallerFunctionName(cleanStack);
180
+ const event = {
181
+ eventIndex: trace.cacheChanges.length,
182
+ stepIndex: (_d = (_c = _UILayoutDebugger._pendingStep) == null ? void 0 : _c.stepIndex) != null ? _d : -1,
183
+ iteration: _UILayoutDebugger._currentIteration,
184
+ viewIndex: viewIdx,
185
+ className: (_f = (_e = view == null ? void 0 : view.constructor) == null ? void 0 : _e.name) != null ? _f : "UnknownView",
186
+ elementID: (_g = view == null ? void 0 : view.elementID) != null ? _g : String(viewIdx),
187
+ cacheKey,
188
+ newValue: { width: (_h = value == null ? void 0 : value.width) != null ? _h : 0, height: (_i = value == null ? void 0 : value.height) != null ? _i : 0 },
189
+ callerFunction,
190
+ cleanStack
191
+ };
192
+ trace.cacheChanges.push(event);
193
+ }
194
+ static willLayoutView(view) {
195
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
196
+ if (!_UILayoutDebugger._isEnabled) {
197
+ return;
198
+ }
199
+ const stepIndex = (_b = (_a = _UILayoutDebugger._currentTrace) == null ? void 0 : _a.steps.length) != null ? _b : 0;
200
+ const viewIdx = (_c = view == null ? void 0 : view._UIViewIndex) != null ? _c : -1;
201
+ if (viewIdx >= 0) {
202
+ _UILayoutDebugger._liveViewRegistry.set(viewIdx, view);
203
+ }
204
+ _UILayoutDebugger._pendingStep = {
205
+ stepIndex,
206
+ iteration: _UILayoutDebugger._currentIteration,
207
+ viewIndex: viewIdx,
208
+ className: (_e = (_d = view == null ? void 0 : view.constructor) == null ? void 0 : _d.name) != null ? _e : "UnknownView",
209
+ elementID: (_f = view == null ? void 0 : view.elementID) != null ? _f : String(viewIdx),
210
+ frameBefore: _UILayoutDebugger._captureFrame(view),
211
+ frameAfter: null,
212
+ cacheBefore: _UILayoutDebugger._captureCache(view),
213
+ cacheAfter: null,
214
+ subviewRecords: [],
215
+ trigger: (_g = _UILayoutDebugger._triggerMap.get(viewIdx)) != null ? _g : null
216
+ };
217
+ _UILayoutDebugger._triggerMap.delete(viewIdx);
218
+ _UILayoutDebugger._pendingSubviewsBefore = /* @__PURE__ */ new Map();
219
+ const subviews = (_h = view == null ? void 0 : view.subviews) != null ? _h : [];
220
+ for (let i = 0; i < subviews.length; i++) {
221
+ const sv = subviews[i];
222
+ const idx = (_i = sv == null ? void 0 : sv._UIViewIndex) != null ? _i : -i;
223
+ _UILayoutDebugger._pendingSubviewsBefore.set(idx, _UILayoutDebugger._captureFrame(sv));
224
+ }
225
+ }
226
+ static didLayoutView(view) {
227
+ var _a, _b, _c;
228
+ if (!_UILayoutDebugger._isEnabled) {
229
+ return;
230
+ }
231
+ const step = _UILayoutDebugger._pendingStep;
232
+ if (!step) {
233
+ return;
234
+ }
235
+ step.frameAfter = _UILayoutDebugger._captureFrame(view);
236
+ step.cacheAfter = _UILayoutDebugger._captureCache(view);
237
+ const viewIdx = (_a = view == null ? void 0 : view._UIViewIndex) != null ? _a : -1;
238
+ const prev = (_b = _UILayoutDebugger._layoutCountsThisPass.get(viewIdx)) != null ? _b : 0;
239
+ _UILayoutDebugger._layoutCountsThisPass.set(viewIdx, prev + 1);
240
+ (_c = _UILayoutDebugger._currentTrace) == null ? void 0 : _c.steps.push(step);
241
+ _UILayoutDebugger._pendingStep = null;
242
+ }
243
+ static didFinishLayoutPass(iterationCount) {
244
+ var _a;
245
+ if (!_UILayoutDebugger._isEnabled) {
246
+ return;
247
+ }
248
+ const trace = _UILayoutDebugger._currentTrace;
249
+ if (!trace) {
250
+ return;
251
+ }
252
+ trace.totalIterations = iterationCount;
253
+ const anyView = _UILayoutDebugger._liveViewRegistry.values().next().value;
254
+ const rootView = anyView == null ? void 0 : anyView.rootView;
255
+ if (rootView) {
256
+ _UILayoutDebugger._lastKnownRootView = rootView;
257
+ const visited = /* @__PURE__ */ new Set();
258
+ const rootIdx = (_a = rootView._UIViewIndex) != null ? _a : -1;
259
+ if (rootIdx >= 0) {
260
+ visited.add(rootIdx);
261
+ }
262
+ trace.roots = [_UILayoutDebugger._buildTreeSnapshot(rootView, 0, visited)];
263
+ }
264
+ _UILayoutDebugger._traces.unshift(trace);
265
+ if (_UILayoutDebugger._traces.length > _UILayoutDebugger.maxStoredTraces) {
266
+ _UILayoutDebugger._traces.length = _UILayoutDebugger.maxStoredTraces;
267
+ }
268
+ _UILayoutDebugger._replayTraceIndex = 0;
269
+ _UILayoutDebugger._replayStepIndex = -1;
270
+ _UILayoutDebugger._currentTrace = null;
271
+ _UILayoutDebugger._liveViewRegistry.clear();
272
+ _UILayoutDebugger._renderOverlay();
273
+ }
274
+ static clearTraces() {
275
+ _UILayoutDebugger._traces = [];
276
+ _UILayoutDebugger._passIndex = 0;
277
+ _UILayoutDebugger._replayTraceIndex = 0;
278
+ _UILayoutDebugger._compareTraceIndex = 1;
279
+ _UILayoutDebugger._replayStepIndex = -1;
280
+ _UILayoutDebugger._compareStepIndex = -1;
281
+ _UILayoutDebugger._renderOverlay();
282
+ }
283
+ static captureBaseline() {
284
+ if (!_UILayoutDebugger._isEnabled) {
285
+ return;
286
+ }
287
+ const snap = _UILayoutDebugger._captureStateSnapshot("Baseline");
288
+ if (!snap) {
289
+ console.warn("[UILayoutDebugger] captureBaseline: no root view found yet \u2014 trigger a layout pass first.");
290
+ return;
291
+ }
292
+ _UILayoutDebugger._baseline = snap;
293
+ _UILayoutDebugger._diffSnapshot = null;
294
+ _UILayoutDebugger._diffMode = false;
295
+ _UILayoutDebugger._renderOverlay();
296
+ console.log(
297
+ `%c[UILayoutDebugger] Baseline captured \u2014 ${snap.views.size} views.`,
298
+ "color: #88ddff; font-weight: bold"
299
+ );
300
+ }
301
+ static captureAndDiff() {
302
+ if (!_UILayoutDebugger._isEnabled) {
303
+ return;
304
+ }
305
+ if (!_UILayoutDebugger._baseline) {
306
+ console.warn("[UILayoutDebugger] captureAndDiff: no baseline set. Call captureBaseline() first.");
307
+ return;
308
+ }
309
+ const snap = _UILayoutDebugger._captureStateSnapshot("Current");
310
+ if (!snap) {
311
+ console.warn("[UILayoutDebugger] captureAndDiff: could not find root view.");
312
+ return;
313
+ }
314
+ _UILayoutDebugger._diffSnapshot = snap;
315
+ _UILayoutDebugger._diffMode = true;
316
+ _UILayoutDebugger._renderOverlay();
317
+ }
318
+ static clearDiff() {
319
+ _UILayoutDebugger._baseline = null;
320
+ _UILayoutDebugger._diffSnapshot = null;
321
+ _UILayoutDebugger._diffMode = false;
322
+ _UILayoutDebugger._renderOverlay();
323
+ }
324
+ static captureStaleLayoutReport() {
325
+ var _a, _b, _c;
326
+ if (!_UILayoutDebugger._isEnabled) {
327
+ return;
328
+ }
329
+ const rootView = _UILayoutDebugger._lastKnownRootView;
330
+ if (!rootView) {
331
+ console.warn(
332
+ "[UILayoutDebugger] captureStaleLayoutReport: no root view found yet \u2014 trigger a layout pass first."
333
+ );
334
+ return;
335
+ }
336
+ const before = _UILayoutDebugger._captureStateSnapshotFromRoot(rootView, "Before (potentially stale)");
337
+ (_a = rootView.performForcedSubtreeLayout) == null ? void 0 : _a.call(rootView);
338
+ const after = _UILayoutDebugger._captureStateSnapshotFromRoot(rootView, "After (forced cold remeasure)");
339
+ const diffs = _UILayoutDebugger._diffSnapshots(before, after).filter((d) => d.kind !== "unchanged");
340
+ const forcedPassCacheChanges = /* @__PURE__ */ new Map();
341
+ const forcedTrace = (_b = _UILayoutDebugger._traces[0]) != null ? _b : null;
342
+ const forcedPassIndex = (_c = forcedTrace == null ? void 0 : forcedTrace.passIndex) != null ? _c : -1;
343
+ if (forcedTrace) {
344
+ for (const ev of forcedTrace.cacheChanges) {
345
+ let bucket = forcedPassCacheChanges.get(ev.viewIndex);
346
+ if (!bucket) {
347
+ bucket = [];
348
+ forcedPassCacheChanges.set(ev.viewIndex, bucket);
349
+ }
350
+ bucket.push(ev);
351
+ }
352
+ }
353
+ _UILayoutDebugger._staleReportResult = { before, after, diffs, forcedPassCacheChanges, passIndex: forcedPassIndex };
354
+ _UILayoutDebugger._staleReportMode = true;
355
+ _UILayoutDebugger._renderOverlay();
356
+ const correctedCount = diffs.length;
357
+ console.log(
358
+ `%c[UILayoutDebugger] Stale layout report: ${correctedCount} view(s) had stale state corrected by forced layout.`,
359
+ "color: #ffaa55; font-weight: bold"
360
+ );
361
+ }
362
+ static clearStaleReport() {
363
+ _UILayoutDebugger._staleReportResult = null;
364
+ _UILayoutDebugger._staleReportMode = false;
365
+ _UILayoutDebugger._renderOverlay();
366
+ }
367
+ static toggleLiveInspector() {
368
+ _UILayoutDebugger._liveInspectorMode = !_UILayoutDebugger._liveInspectorMode;
369
+ _UILayoutDebugger._renderOverlay();
370
+ }
371
+ static _captureStateSnapshot(label) {
372
+ const rootView = _UILayoutDebugger._lastKnownRootView;
373
+ if (!rootView) {
374
+ return null;
375
+ }
376
+ return _UILayoutDebugger._captureStateSnapshotFromRoot(rootView, label);
377
+ }
378
+ static _captureStateSnapshotFromRoot(rootView, label) {
379
+ var _a, _b, _c, _d;
380
+ const views = /* @__PURE__ */ new Map();
381
+ _UILayoutDebugger._walkViewTree(rootView, views, /* @__PURE__ */ new Set());
382
+ const tm = window.UITextMeasurement;
383
+ const textMeasurement = {
384
+ preparedCacheSize: (_b = (_a = tm == null ? void 0 : tm._preparedCache) == null ? void 0 : _a.size) != null ? _b : -1,
385
+ styleCacheSize: (_d = (_c = tm == null ? void 0 : tm.globalStyleCache) == null ? void 0 : _c.size) != null ? _d : -1
386
+ };
387
+ return { label, takenAt: Date.now(), views, textMeasurement };
388
+ }
389
+ static _walkViewTree(view, out, visited) {
390
+ var _a, _b, _c, _d, _e;
391
+ const idx = (_a = view == null ? void 0 : view._UIViewIndex) != null ? _a : -1;
392
+ if (idx < 0 || visited.has(idx)) {
393
+ return;
394
+ }
395
+ visited.add(idx);
396
+ out.set(idx, {
397
+ viewIndex: idx,
398
+ className: (_c = (_b = view == null ? void 0 : view.constructor) == null ? void 0 : _b.name) != null ? _c : "UnknownView",
399
+ elementID: (_d = view == null ? void 0 : view.elementID) != null ? _d : String(idx),
400
+ frame: _UILayoutDebugger._captureFrame(view),
401
+ cache: _UILayoutDebugger._captureCache(view)
402
+ });
403
+ const subviews = (_e = view == null ? void 0 : view.subviews) != null ? _e : [];
404
+ for (const sv of subviews) {
405
+ _UILayoutDebugger._walkViewTree(sv, out, visited);
406
+ }
407
+ }
408
+ static _diffSnapshots(baseline, current) {
409
+ var _a, _b;
410
+ const diffs = [];
411
+ const allKeys = /* @__PURE__ */ new Set([...baseline.views.keys(), ...current.views.keys()]);
412
+ for (const idx of allKeys) {
413
+ const b = (_a = baseline.views.get(idx)) != null ? _a : null;
414
+ const c = (_b = current.views.get(idx)) != null ? _b : null;
415
+ if (!b) {
416
+ diffs.push({
417
+ kind: "appeared",
418
+ viewIndex: idx,
419
+ className: c.className,
420
+ elementID: c.elementID,
421
+ baselineFrame: null,
422
+ currentFrame: c.frame,
423
+ baselineCache: null,
424
+ currentCache: c.cache
425
+ });
426
+ continue;
427
+ }
428
+ if (!c) {
429
+ diffs.push({
430
+ kind: "disappeared",
431
+ viewIndex: idx,
432
+ className: b.className,
433
+ elementID: b.elementID,
434
+ baselineFrame: b.frame,
435
+ currentFrame: null,
436
+ baselineCache: b.cache,
437
+ currentCache: null
438
+ });
439
+ continue;
440
+ }
441
+ const frameChanged = _UILayoutDebugger._framesEqual(b.frame, c.frame) === false;
442
+ const cacheChanged = _UILayoutDebugger._cachesEqual(b.cache, c.cache) === false;
443
+ const kind = frameChanged && cacheChanged ? "both" : frameChanged ? "frame" : cacheChanged ? "cache" : "unchanged";
444
+ diffs.push({
445
+ kind,
446
+ viewIndex: idx,
447
+ className: c.className,
448
+ elementID: c.elementID,
449
+ baselineFrame: b.frame,
450
+ currentFrame: c.frame,
451
+ baselineCache: b.cache,
452
+ currentCache: c.cache
453
+ });
454
+ }
455
+ const order = {
456
+ appeared: 0,
457
+ disappeared: 1,
458
+ both: 2,
459
+ frame: 3,
460
+ cache: 4,
461
+ unchanged: 5
462
+ };
463
+ diffs.sort((a, b) => order[a.kind] - order[b.kind]);
464
+ return diffs;
465
+ }
466
+ static _framesEqual(a, b) {
467
+ if (!a && !b) {
468
+ return true;
469
+ }
470
+ if (!a || !b) {
471
+ return false;
472
+ }
473
+ if (_UILayoutDebugger._boundsBasedDiff) {
474
+ return a.width === b.width && a.height === b.height;
475
+ }
476
+ return a.left === b.left && a.top === b.top && a.width === b.width && a.height === b.height;
477
+ }
478
+ static _cachesEqual(a, b) {
479
+ if (!a && !b) {
480
+ return true;
481
+ }
482
+ if (!a || !b) {
483
+ return false;
484
+ }
485
+ if (a.entryCount !== b.entryCount) {
486
+ return false;
487
+ }
488
+ for (const key of Object.keys(a.entries)) {
489
+ const ae = a.entries[key];
490
+ const be = b.entries[key];
491
+ if (!be || ae.width !== be.width || ae.height !== be.height) {
492
+ return false;
493
+ }
494
+ }
495
+ if (a.hasFrameCache !== b.hasFrameCache) {
496
+ return false;
497
+ }
498
+ if (a.hasFrameCache && b.hasFrameCache) {
499
+ const af = a.frameCache, bf = b.frameCache;
500
+ if (!af || !bf || af.top !== bf.top || af.left !== bf.left || af.width !== bf.width || af.height !== bf.height) {
501
+ return false;
502
+ }
503
+ }
504
+ if (a.hasVirtualFrameCache !== b.hasVirtualFrameCache) {
505
+ return false;
506
+ }
507
+ if (a.hasVirtualFrameCache && b.hasVirtualFrameCache) {
508
+ const av = a.virtualFrameCache, bv = b.virtualFrameCache;
509
+ if (!av || !bv || av.top !== bv.top || av.left !== bv.left || av.width !== bv.width || av.height !== bv.height) {
510
+ return false;
511
+ }
512
+ }
513
+ return true;
514
+ }
515
+ static willSetSubviewFrames(_view) {
516
+ }
517
+ static didSetSubviewFrames(view) {
518
+ var _a, _b, _c, _d, _e, _f;
519
+ if (!_UILayoutDebugger._isEnabled) {
520
+ return;
521
+ }
522
+ const step = _UILayoutDebugger._pendingStep;
523
+ if (!step) {
524
+ return;
525
+ }
526
+ const subviews = (_a = view == null ? void 0 : view.subviews) != null ? _a : [];
527
+ for (let i = 0; i < subviews.length; i++) {
528
+ const sv = subviews[i];
529
+ const idx = (_b = sv == null ? void 0 : sv._UIViewIndex) != null ? _b : -i;
530
+ const record = {
531
+ viewIndex: idx,
532
+ className: (_d = (_c = sv == null ? void 0 : sv.constructor) == null ? void 0 : _c.name) != null ? _d : "UnknownView",
533
+ elementID: (_e = sv == null ? void 0 : sv.elementID) != null ? _e : String(idx),
534
+ frameBefore: (_f = _UILayoutDebugger._pendingSubviewsBefore.get(idx)) != null ? _f : null,
535
+ frameAfter: _UILayoutDebugger._captureFrame(sv)
536
+ };
537
+ step.subviewRecords.push(record);
538
+ }
539
+ }
540
+ static _shouldHitBreakpoint(_view) {
541
+ return _UILayoutDebugger._isEnabled && _UILayoutDebugger._breakpointsEnabled;
542
+ }
543
+ static _cleanStack(rawStack) {
544
+ const lines = rawStack.split("\n");
545
+ let firstAppFrameIndex = 1;
546
+ for (let i = 1; i < lines.length; i++) {
547
+ const trimmed = lines[i].trim();
548
+ const atMatch = trimmed.match(/^at\s+([\w.<>$\s]+?)\s*(?:\(|$)/);
549
+ const frameName = atMatch ? atMatch[1].trim() : trimmed;
550
+ const methodName = frameName.includes(".") ? frameName.slice(frameName.lastIndexOf(".") + 1) : frameName;
551
+ const isNoise = _UILayoutDebugger._noiseFramePrefixes.some((prefix) => {
552
+ return frameName === prefix || methodName === prefix || frameName.endsWith("." + prefix);
553
+ });
554
+ if (!isNoise) {
555
+ firstAppFrameIndex = i;
556
+ break;
557
+ }
558
+ }
559
+ return lines.slice(firstAppFrameIndex).join("\n");
560
+ }
561
+ static _extractCallerFunctionName(cleanStack) {
562
+ var _a, _b;
563
+ const firstLine = (_b = (_a = cleanStack.split("\n")[0]) == null ? void 0 : _a.trim()) != null ? _b : "";
564
+ const atMatch = firstLine.match(/^at\s+([\w.<>$\s]+?)\s*(?:\(|$)/);
565
+ if (atMatch) {
566
+ return atMatch[1].trim();
567
+ }
568
+ return firstLine.substring(0, 80) || "(unknown)";
569
+ }
570
+ static _captureFrame(view) {
571
+ var _a, _b, _c, _d, _e, _f;
572
+ const f = view == null ? void 0 : view._frame;
573
+ if (!f) {
574
+ return null;
575
+ }
576
+ return {
577
+ top: (_b = (_a = f.top) != null ? _a : f.y) != null ? _b : 0,
578
+ left: (_d = (_c = f.left) != null ? _c : f.x) != null ? _d : 0,
579
+ width: (_e = f.width) != null ? _e : 0,
580
+ height: (_f = f.height) != null ? _f : 0
581
+ };
582
+ }
583
+ static _captureCache(view) {
584
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
585
+ if (!view) {
586
+ return null;
587
+ }
588
+ const sharedKey = view.sharedIntrinsicSizeCacheIdentifier;
589
+ const isShared = !!sharedKey;
590
+ let rawEntries;
591
+ if (isShared) {
592
+ rawEntries = (_f = (_e = (_d = (_a = view.constructor) == null ? void 0 : _a._sharedIntrinsicSizeCaches) != null ? _d : (_c = (_b = view.__proto__) == null ? void 0 : _b.constructor) == null ? void 0 : _c._sharedIntrinsicSizeCaches) == null ? void 0 : _e.get(sharedKey)) != null ? _f : {};
593
+ } else {
594
+ rawEntries = (_g = view._intrinsicSizesCache) != null ? _g : {};
595
+ }
596
+ const entries = {};
597
+ for (const key of Object.keys(rawEntries)) {
598
+ const r = rawEntries[key];
599
+ entries[key] = { width: (_h = r == null ? void 0 : r.width) != null ? _h : 0, height: (_i = r == null ? void 0 : r.height) != null ? _i : 0 };
600
+ }
601
+ const rawFrameCache = view._frameCache;
602
+ const rawVirtualFrameCache = view._frameCacheForVirtualLayouting;
603
+ const frameCache = rawFrameCache ? { top: (_k = (_j = rawFrameCache.top) != null ? _j : rawFrameCache.y) != null ? _k : 0, left: (_m = (_l = rawFrameCache.left) != null ? _l : rawFrameCache.x) != null ? _m : 0, width: (_n = rawFrameCache.width) != null ? _n : 0, height: (_o = rawFrameCache.height) != null ? _o : 0 } : null;
604
+ const virtualFrameCache = rawVirtualFrameCache ? { top: (_q = (_p = rawVirtualFrameCache.top) != null ? _p : rawVirtualFrameCache.y) != null ? _q : 0, left: (_s = (_r = rawVirtualFrameCache.left) != null ? _r : rawVirtualFrameCache.x) != null ? _s : 0, width: (_t = rawVirtualFrameCache.width) != null ? _t : 0, height: (_u = rawVirtualFrameCache.height) != null ? _u : 0 } : null;
605
+ return {
606
+ entryCount: Object.keys(entries).length,
607
+ entries,
608
+ isShared,
609
+ sharedKey,
610
+ hasFrameCache: rawFrameCache !== void 0,
611
+ frameCache,
612
+ hasVirtualFrameCache: rawVirtualFrameCache !== void 0,
613
+ virtualFrameCache
614
+ };
615
+ }
616
+ static _buildTreeSnapshot(view, depth, visited = /* @__PURE__ */ new Set()) {
617
+ var _a, _b, _c, _d, _e, _f, _g;
618
+ const idx = (_a = view == null ? void 0 : view._UIViewIndex) != null ? _a : -1;
619
+ const node = {
620
+ viewIndex: idx,
621
+ className: (_c = (_b = view == null ? void 0 : view.constructor) == null ? void 0 : _b.name) != null ? _c : "UnknownView",
622
+ elementID: (_d = view == null ? void 0 : view.elementID) != null ? _d : String(idx),
623
+ depth,
624
+ frame: _UILayoutDebugger._captureFrame(view),
625
+ layoutCount: (_e = _UILayoutDebugger._layoutCountsThisPass.get(idx)) != null ? _e : 0,
626
+ cacheAfterPass: _UILayoutDebugger._captureCache(view),
627
+ children: []
628
+ };
629
+ const subviews = (_f = view == null ? void 0 : view.subviews) != null ? _f : [];
630
+ for (let i = 0; i < subviews.length; i++) {
631
+ const sv = subviews[i];
632
+ const svIdx = (_g = sv == null ? void 0 : sv._UIViewIndex) != null ? _g : -1;
633
+ if (svIdx < 0 || visited.has(svIdx)) {
634
+ continue;
635
+ }
636
+ visited.add(svIdx);
637
+ node.children.push(_UILayoutDebugger._buildTreeSnapshot(sv, depth + 1, visited));
638
+ }
639
+ return node;
640
+ }
641
+ static _findCausingTrace(viewIndex, baselineTakenAt) {
642
+ for (let ti = _UILayoutDebugger._traces.length - 1; ti >= 0; ti--) {
643
+ const trace = _UILayoutDebugger._traces[ti];
644
+ const si = trace.steps.findIndex((s) => s.viewIndex === viewIndex);
645
+ if (si >= 0) {
646
+ return { traceIndex: ti, stepIndex: si, passIndex: trace.passIndex };
647
+ }
648
+ }
649
+ return null;
650
+ }
651
+ static _ensureOverlay() {
652
+ if (_UILayoutDebugger._overlayRoot) {
653
+ return;
654
+ }
655
+ const root = document.createElement("div");
656
+ root.id = "__UILayoutDebugger_overlay";
657
+ root.style.cssText = [
658
+ "position: fixed",
659
+ "top: 8px",
660
+ "right: 8px",
661
+ "max-height: calc(100vh - 16px)",
662
+ "background: rgba(15, 15, 20, 0.96)",
663
+ "color: #e8e8e8",
664
+ "font: 11px/1.4 'SF Mono', 'Menlo', 'Consolas', monospace",
665
+ "border-radius: 8px",
666
+ "border: 1px solid rgba(255,255,255,0.12)",
667
+ "box-shadow: 0 8px 32px rgba(0,0,0,0.6)",
668
+ "z-index: 2147483647",
669
+ "display: flex",
670
+ "flex-direction: column",
671
+ "overflow: hidden",
672
+ "user-select: none"
673
+ ].join("; ");
674
+ document.body.appendChild(root);
675
+ _UILayoutDebugger._overlayRoot = root;
676
+ _UILayoutDebugger._makeDraggable(root);
677
+ }
678
+ static _removeOverlay() {
679
+ var _a;
680
+ (_a = _UILayoutDebugger._overlayRoot) == null ? void 0 : _a.remove();
681
+ _UILayoutDebugger._overlayRoot = null;
682
+ }
683
+ static _renderOverlay() {
684
+ const root = _UILayoutDebugger._overlayRoot;
685
+ if (!root) {
686
+ return;
687
+ }
688
+ const cmp = _UILayoutDebugger._compareMode;
689
+ const diff = _UILayoutDebugger._diffMode && !!_UILayoutDebugger._baseline && !!_UILayoutDebugger._diffSnapshot;
690
+ const live = _UILayoutDebugger._liveInspectorMode && !!_UILayoutDebugger._lastKnownRootView;
691
+ const stale = _UILayoutDebugger._staleReportMode && !!_UILayoutDebugger._staleReportResult;
692
+ const passWidth = cmp ? 1140 : 570;
693
+ const extraWidth = (diff ? 320 : 0) + (live ? 320 : 0) + (stale ? 360 : 0);
694
+ root.style.width = passWidth + extraWidth + "px";
695
+ root.innerHTML = "";
696
+ const headerRow1 = _UILayoutDebugger._el("div", [
697
+ "padding: 8px 10px 5px",
698
+ "background: rgba(255,255,255,0.05)",
699
+ "display: flex",
700
+ "align-items: center",
701
+ "gap: 6px",
702
+ "cursor: move",
703
+ "flex-shrink: 0"
704
+ ]);
705
+ headerRow1.dataset.dragHandle = "1";
706
+ const title = _UILayoutDebugger._el("span", ["flex: 1", "font-weight: bold", "font-size: 11px", "color: #c8d8ff"]);
707
+ title.textContent = "\u2699 UILayoutDebugger";
708
+ const helpBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
709
+ _UILayoutDebugger._helpMode ? "#ffcc88" : "#9090a8"
710
+ ));
711
+ helpBtn.textContent = "\u24D8";
712
+ helpBtn.title = "Show help";
713
+ helpBtn.onclick = () => {
714
+ _UILayoutDebugger._helpMode = !_UILayoutDebugger._helpMode;
715
+ _UILayoutDebugger._renderOverlay();
716
+ };
717
+ const bpBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
718
+ _UILayoutDebugger._breakpointsEnabled ? "#ffaa33" : "#9090a8"
719
+ ));
720
+ bpBtn.textContent = _UILayoutDebugger._breakpointsEnabled ? "\u23F8 BP ON" : "\u23F8 BP OFF";
721
+ bpBtn.title = "Toggle breakpoint step-through";
722
+ bpBtn.onclick = () => {
723
+ _UILayoutDebugger._breakpointsEnabled ? _UILayoutDebugger.disableBreakpoints() : _UILayoutDebugger.enableBreakpoints();
724
+ _UILayoutDebugger._renderOverlay();
725
+ };
726
+ const cmpBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(cmp ? "#7bc8ff" : "#9090a8"));
727
+ cmpBtn.textContent = cmp ? "\u29C9 Compare ON" : "\u29C9 Compare";
728
+ cmpBtn.title = "Toggle side-by-side pass comparison";
729
+ cmpBtn.onclick = () => {
730
+ _UILayoutDebugger._compareMode = !_UILayoutDebugger._compareMode;
731
+ if (_UILayoutDebugger._compareMode) {
732
+ _UILayoutDebugger._compareTraceIndex = Math.min(1, _UILayoutDebugger._traces.length - 1);
733
+ _UILayoutDebugger._compareStepIndex = -1;
734
+ _UILayoutDebugger._sharedExpandState = /* @__PURE__ */ new Map();
735
+ }
736
+ _UILayoutDebugger._renderOverlay();
737
+ };
738
+ const filterLabels = {
739
+ all: "\u2298 All",
740
+ changed: "\u2298 Changed",
741
+ unchanged: "\u2298 Unchanged"
742
+ };
743
+ const filterColors = {
744
+ all: "#9090a8",
745
+ changed: "#7bc8ff",
746
+ unchanged: "#ffcc88"
747
+ };
748
+ const filterCycle = {
749
+ all: "changed",
750
+ changed: "unchanged",
751
+ unchanged: "all"
752
+ };
753
+ const filterBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
754
+ filterColors[_UILayoutDebugger._frameFilter]
755
+ ));
756
+ filterBtn.textContent = filterLabels[_UILayoutDebugger._frameFilter];
757
+ filterBtn.title = "Cycle: show all steps \u2192 only changed frames \u2192 only unchanged frames";
758
+ filterBtn.onclick = () => {
759
+ _UILayoutDebugger._frameFilter = filterCycle[_UILayoutDebugger._frameFilter];
760
+ _UILayoutDebugger._replayStepIndex = -1;
761
+ _UILayoutDebugger._compareStepIndex = -1;
762
+ _UILayoutDebugger._renderOverlay();
763
+ };
764
+ const clearBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#9090a8"));
765
+ clearBtn.textContent = "\u232B Clear";
766
+ clearBtn.title = "Clear all recorded traces and restart recording";
767
+ clearBtn.onclick = () => _UILayoutDebugger.clearTraces();
768
+ const toggleBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#9090a8"));
769
+ toggleBtn.textContent = _UILayoutDebugger._overlayVisible ? "\u25BE" : "\u25B8";
770
+ toggleBtn.title = "Collapse / expand";
771
+ toggleBtn.onclick = () => {
772
+ _UILayoutDebugger._overlayVisible = !_UILayoutDebugger._overlayVisible;
773
+ _UILayoutDebugger._renderOverlay();
774
+ };
775
+ const closeBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#dd5555"));
776
+ closeBtn.textContent = "\u2715";
777
+ closeBtn.title = "Disable UILayoutDebugger";
778
+ closeBtn.onclick = () => _UILayoutDebugger.disable();
779
+ headerRow1.append(title, helpBtn, bpBtn, cmpBtn, filterBtn, clearBtn, toggleBtn, closeBtn);
780
+ const headerRow2 = _UILayoutDebugger._el("div", [
781
+ "padding: 4px 10px 6px",
782
+ "background: rgba(255,255,255,0.05)",
783
+ "border-bottom: 1px solid rgba(255,255,255,0.10)",
784
+ "display: flex",
785
+ "align-items: center",
786
+ "gap: 6px",
787
+ "flex-shrink: 0",
788
+ "flex-wrap: wrap"
789
+ ]);
790
+ headerRow2.dataset.dragHandle = "1";
791
+ const hasBaseline = !!_UILayoutDebugger._baseline;
792
+ const baselineBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
793
+ _UILayoutDebugger._diffMode ? "#88ddff" : hasBaseline ? "#ffcc88" : "#9090a8"
794
+ ));
795
+ baselineBtn.textContent = _UILayoutDebugger._diffMode ? "\u2295 Diff ON" : hasBaseline ? "\u{1F4CD} Baseline set" : "\u{1F4CD} Baseline";
796
+ baselineBtn.title = hasBaseline ? "Baseline captured \u2014 click to recapture, or use \u2295 to diff against it" : "Capture current view tree state as baseline";
797
+ baselineBtn.onclick = () => {
798
+ if (_UILayoutDebugger._diffMode) {
799
+ _UILayoutDebugger.clearDiff();
800
+ } else {
801
+ _UILayoutDebugger.captureBaseline();
802
+ }
803
+ };
804
+ const diffBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
805
+ hasBaseline ? "#88ff99" : "#9090a8"
806
+ ));
807
+ diffBtn.textContent = "\u2295 Diff";
808
+ diffBtn.title = "Capture current state and diff against baseline";
809
+ diffBtn.disabled = !hasBaseline;
810
+ diffBtn.onclick = () => _UILayoutDebugger.captureAndDiff();
811
+ const liveBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
812
+ live ? "#88ff99" : "#9090a8"
813
+ ));
814
+ liveBtn.textContent = live ? "\u{1F441} Live ON" : "\u{1F441} Live";
815
+ liveBtn.title = "Show live view tree with current frames and cache state";
816
+ liveBtn.onclick = () => _UILayoutDebugger.toggleLiveInspector();
817
+ const hasStaleResult = !!_UILayoutDebugger._staleReportResult;
818
+ const staleBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
819
+ stale ? "#ffaa55" : hasStaleResult ? "#a06030" : "#9090a8"
820
+ ));
821
+ staleBtn.textContent = stale ? "\u2622 Stale ON" : "\u2622 Stale";
822
+ staleBtn.title = hasStaleResult ? "Stale layout report available \u2014 click to toggle the panel, or re-run to refresh" : "Run a forced full-tree remeasure and show which views had stale/missing invalidations";
823
+ staleBtn.onclick = () => {
824
+ if (stale) {
825
+ _UILayoutDebugger._staleReportMode = false;
826
+ _UILayoutDebugger._renderOverlay();
827
+ } else if (hasStaleResult) {
828
+ _UILayoutDebugger._staleReportMode = true;
829
+ _UILayoutDebugger._renderOverlay();
830
+ } else {
831
+ _UILayoutDebugger.captureStaleLayoutReport();
832
+ }
833
+ };
834
+ staleBtn.oncontextmenu = (e) => {
835
+ e.preventDefault();
836
+ _UILayoutDebugger.captureStaleLayoutReport();
837
+ };
838
+ const boundsToggleBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle(
839
+ _UILayoutDebugger._boundsBasedDiff ? "#c8d8ff" : "#9090a8"
840
+ ));
841
+ boundsToggleBtn.textContent = _UILayoutDebugger._boundsBasedDiff ? "\u2B1A Bounds" : "\u2B1A Frame";
842
+ boundsToggleBtn.title = _UILayoutDebugger._boundsBasedDiff ? "Diffing by bounds (size only \u2014 origin ignored). Click to switch to frame diffing (position + size)." : "Diffing by frame (position + size). Click to switch to bounds diffing (size only \u2014 position changes are ignored).";
843
+ boundsToggleBtn.onclick = () => {
844
+ _UILayoutDebugger._boundsBasedDiff = !_UILayoutDebugger._boundsBasedDiff;
845
+ _UILayoutDebugger._renderOverlay();
846
+ };
847
+ headerRow2.append(baselineBtn, diffBtn, liveBtn, staleBtn, boundsToggleBtn);
848
+ const header = _UILayoutDebugger._el("div", ["flex-shrink: 0"]);
849
+ header.append(headerRow1, headerRow2);
850
+ root.appendChild(header);
851
+ if (!_UILayoutDebugger._overlayVisible) {
852
+ return;
853
+ }
854
+ if (_UILayoutDebugger._helpMode) {
855
+ root.appendChild(_UILayoutDebugger._renderHelpPanel());
856
+ return;
857
+ }
858
+ if (_UILayoutDebugger._baseline && !diff) {
859
+ const msg = _UILayoutDebugger._el("div", [
860
+ "padding: 8px 12px",
861
+ "color: #ffcc88",
862
+ "font-size: 10px",
863
+ "border-bottom: 1px solid rgba(255,255,255,0.08)",
864
+ "flex-shrink: 0"
865
+ ]);
866
+ const ts = new Date(_UILayoutDebugger._baseline.takenAt);
867
+ msg.textContent = `\u{1F4CD} Baseline set at ${ts.toLocaleTimeString()} \u2014 ${_UILayoutDebugger._baseline.views.size} views. Hit \u2295 Diff to compare.`;
868
+ root.appendChild(msg);
869
+ }
870
+ if (_UILayoutDebugger._traces.length === 0 && !diff) {
871
+ const msg = _UILayoutDebugger._el("div", ["padding: 10px 12px", "color: #9090a8", "font-size: 10px"]);
872
+ msg.textContent = "No layout pass recorded yet. Trigger a layout to begin.";
873
+ root.appendChild(msg);
874
+ return;
875
+ }
876
+ const body = _UILayoutDebugger._el("div", [
877
+ "display: flex",
878
+ "flex: 1",
879
+ "overflow: hidden",
880
+ "min-height: 0"
881
+ ]);
882
+ root.appendChild(body);
883
+ if (_UILayoutDebugger._traces.length > 0) {
884
+ const passSection = _UILayoutDebugger._el("div", [
885
+ "display: flex",
886
+ "flex: 1",
887
+ "overflow: hidden",
888
+ "min-height: 0"
889
+ ]);
890
+ if (cmp) {
891
+ let leftTreeEl = null;
892
+ let rightTreeEl = null;
893
+ const leftCol = _UILayoutDebugger._renderPassColumn(
894
+ _UILayoutDebugger._replayTraceIndex,
895
+ _UILayoutDebugger._replayStepIndex,
896
+ (si) => {
897
+ _UILayoutDebugger._replayStepIndex = si;
898
+ _UILayoutDebugger._renderOverlay();
899
+ },
900
+ (ti) => {
901
+ _UILayoutDebugger._replayTraceIndex = ti;
902
+ _UILayoutDebugger._replayStepIndex = -1;
903
+ _UILayoutDebugger._renderOverlay();
904
+ },
905
+ _UILayoutDebugger._sharedExpandState,
906
+ (el) => {
907
+ leftTreeEl = el;
908
+ },
909
+ () => rightTreeEl
910
+ );
911
+ const colDivider = _UILayoutDebugger._el("div", [
912
+ "width: 1px",
913
+ "background: rgba(255,255,255,0.10)",
914
+ "flex-shrink: 0"
915
+ ]);
916
+ const rightCol = _UILayoutDebugger._renderPassColumn(
917
+ _UILayoutDebugger._compareTraceIndex,
918
+ _UILayoutDebugger._compareStepIndex,
919
+ (si) => {
920
+ _UILayoutDebugger._compareStepIndex = si;
921
+ _UILayoutDebugger._renderOverlay();
922
+ },
923
+ (ti) => {
924
+ _UILayoutDebugger._compareTraceIndex = ti;
925
+ _UILayoutDebugger._compareStepIndex = -1;
926
+ _UILayoutDebugger._renderOverlay();
927
+ },
928
+ _UILayoutDebugger._sharedExpandState,
929
+ (el) => {
930
+ rightTreeEl = el;
931
+ },
932
+ () => leftTreeEl
933
+ );
934
+ passSection.append(leftCol, colDivider, rightCol);
935
+ } else {
936
+ const col = _UILayoutDebugger._renderPassColumn(
937
+ _UILayoutDebugger._replayTraceIndex,
938
+ _UILayoutDebugger._replayStepIndex,
939
+ (si) => {
940
+ _UILayoutDebugger._replayStepIndex = si;
941
+ _UILayoutDebugger._renderOverlay();
942
+ },
943
+ (ti) => {
944
+ _UILayoutDebugger._replayTraceIndex = ti;
945
+ _UILayoutDebugger._replayStepIndex = -1;
946
+ _UILayoutDebugger._renderOverlay();
947
+ },
948
+ _UILayoutDebugger._singleExpandState,
949
+ () => {
950
+ },
951
+ () => null
952
+ );
953
+ col.style.flex = "1";
954
+ passSection.appendChild(col);
955
+ }
956
+ body.appendChild(passSection);
957
+ }
958
+ if (diff) {
959
+ const divider = _UILayoutDebugger._el("div", [
960
+ "width: 1px",
961
+ "background: rgba(255,255,255,0.10)",
962
+ "flex-shrink: 0"
963
+ ]);
964
+ const diffPanel = _UILayoutDebugger._renderDiffPanel(
965
+ _UILayoutDebugger._baseline,
966
+ _UILayoutDebugger._diffSnapshot,
967
+ (viewIndex) => {
968
+ for (let ti = 0; ti < _UILayoutDebugger._traces.length; ti++) {
969
+ const trace = _UILayoutDebugger._traces[ti];
970
+ const si = trace.steps.findIndex((s) => s.viewIndex === viewIndex);
971
+ if (si >= 0) {
972
+ _UILayoutDebugger._replayTraceIndex = ti;
973
+ _UILayoutDebugger._replayStepIndex = si;
974
+ _UILayoutDebugger._renderOverlay();
975
+ return;
976
+ }
977
+ }
978
+ },
979
+ _UILayoutDebugger._baseline.takenAt
980
+ );
981
+ diffPanel.style.width = "320px";
982
+ diffPanel.style.flexShrink = "0";
983
+ body.append(divider, diffPanel);
984
+ }
985
+ if (live) {
986
+ const divider = _UILayoutDebugger._el("div", [
987
+ "width: 1px",
988
+ "background: rgba(255,255,255,0.10)",
989
+ "flex-shrink: 0"
990
+ ]);
991
+ const livePanel = _UILayoutDebugger._renderLiveInspectorPanel();
992
+ livePanel.style.width = "320px";
993
+ livePanel.style.flexShrink = "0";
994
+ body.append(divider, livePanel);
995
+ }
996
+ if (stale) {
997
+ const divider = _UILayoutDebugger._el("div", [
998
+ "width: 1px",
999
+ "background: rgba(255,255,255,0.10)",
1000
+ "flex-shrink: 0"
1001
+ ]);
1002
+ const stalePanel = _UILayoutDebugger._renderStaleReportPanel(_UILayoutDebugger._staleReportResult);
1003
+ stalePanel.style.width = "360px";
1004
+ stalePanel.style.flexShrink = "0";
1005
+ body.append(divider, stalePanel);
1006
+ }
1007
+ }
1008
+ static _renderPassColumn(traceIndex, stepIndex, onStepChange, onTraceChange, expandState, registerTree, getPeerTree) {
1009
+ var _a, _b, _c, _d;
1010
+ const col = _UILayoutDebugger._el("div", [
1011
+ "display: flex",
1012
+ "flex-direction: column",
1013
+ "flex: 1",
1014
+ "min-width: 0",
1015
+ "overflow: hidden"
1016
+ ]);
1017
+ const trace = (_a = _UILayoutDebugger._traces[traceIndex]) != null ? _a : null;
1018
+ const pickerRow = _UILayoutDebugger._el("div", [
1019
+ "padding: 5px 10px",
1020
+ "border-bottom: 1px solid rgba(255,255,255,0.08)",
1021
+ "display: flex",
1022
+ "align-items: center",
1023
+ "gap: 6px",
1024
+ "flex-shrink: 0",
1025
+ "font-size: 10px"
1026
+ ]);
1027
+ const pickerLabel = _UILayoutDebugger._el("span", ["color: #a0a0b8", "flex-shrink: 0"]);
1028
+ pickerLabel.textContent = "Pass:";
1029
+ const sel = document.createElement("select");
1030
+ sel.style.cssText = [
1031
+ "flex: 1",
1032
+ "background: #1e1e2e",
1033
+ "color: #d8d8f0",
1034
+ "border: 1px solid #444",
1035
+ "border-radius: 3px",
1036
+ "font: inherit",
1037
+ "padding: 1px 4px"
1038
+ ].join("; ");
1039
+ for (let i = 0; i < _UILayoutDebugger._traces.length; i++) {
1040
+ const t = _UILayoutDebugger._traces[i];
1041
+ const opt = document.createElement("option");
1042
+ opt.value = String(i);
1043
+ opt.textContent = `#${t.passIndex} (${t.steps.length} steps, ${t.totalIterations} iter)`;
1044
+ if (i === traceIndex) {
1045
+ opt.selected = true;
1046
+ }
1047
+ sel.appendChild(opt);
1048
+ }
1049
+ sel.onchange = () => onTraceChange(parseInt(sel.value, 10));
1050
+ pickerRow.append(pickerLabel, sel);
1051
+ col.appendChild(pickerRow);
1052
+ if (!trace) {
1053
+ return col;
1054
+ }
1055
+ const frameFilter = _UILayoutDebugger._frameFilter;
1056
+ const visibleSteps = frameFilter === "all" ? trace.steps : trace.steps.filter((s) => {
1057
+ const f = s.frameBefore;
1058
+ const g = s.frameAfter;
1059
+ const changed = !f || !g || f.left !== g.left || f.top !== g.top || f.width !== g.width || f.height !== g.height;
1060
+ return frameFilter === "changed" ? changed : !changed;
1061
+ });
1062
+ const clampedStep = Math.max(-1, Math.min(stepIndex, visibleSteps.length - 1));
1063
+ const activeStep = (_b = visibleSteps[clampedStep]) != null ? _b : null;
1064
+ const activeViewIndex = (_c = activeStep == null ? void 0 : activeStep.viewIndex) != null ? _c : -1;
1065
+ const countMap = /* @__PURE__ */ new Map();
1066
+ const stepMap = /* @__PURE__ */ new Map();
1067
+ for (const step of trace.steps) {
1068
+ countMap.set(step.viewIndex, ((_d = countMap.get(step.viewIndex)) != null ? _d : 0) + 1);
1069
+ stepMap.set(step.viewIndex, step);
1070
+ }
1071
+ const stepBar = _UILayoutDebugger._el("div", [
1072
+ "padding: 5px 10px",
1073
+ "border-bottom: 1px solid rgba(255,255,255,0.08)",
1074
+ "display: flex",
1075
+ "align-items: center",
1076
+ "gap: 6px",
1077
+ "flex-shrink: 0"
1078
+ ]);
1079
+ const backBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#59599b"));
1080
+ backBtn.textContent = "\u25C0";
1081
+ backBtn.title = "Step back";
1082
+ backBtn.disabled = clampedStep <= -1;
1083
+ backBtn.onclick = () => onStepChange(Math.max(clampedStep - 1, -1));
1084
+ const fwdBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#59599b"));
1085
+ fwdBtn.textContent = "\u25B6";
1086
+ fwdBtn.title = "Step forward";
1087
+ fwdBtn.disabled = clampedStep >= visibleSteps.length - 1;
1088
+ fwdBtn.onclick = () => onStepChange(Math.min(clampedStep + 1, visibleSteps.length - 1));
1089
+ const slider = document.createElement("input");
1090
+ slider.type = "range";
1091
+ slider.min = "-1";
1092
+ slider.max = String(visibleSteps.length - 1);
1093
+ slider.value = String(clampedStep);
1094
+ slider.style.cssText = "flex: 1; cursor: pointer; accent-color: #59599b;";
1095
+ slider.oninput = () => onStepChange(parseInt(slider.value, 10));
1096
+ const stepLabel = _UILayoutDebugger._el("span", ["color: #b0b0c8", "font-size: 10px", "white-space: nowrap"]);
1097
+ const totalLabel = frameFilter === "all" ? String(visibleSteps.length) : `${visibleSteps.length}/${trace.steps.length}`;
1098
+ stepLabel.textContent = clampedStep < 0 ? `\u2014 / ${totalLabel}` : `${clampedStep + 1} / ${totalLabel}`;
1099
+ stepBar.append(backBtn, slider, fwdBtn, stepLabel);
1100
+ col.appendChild(stepBar);
1101
+ if (activeStep) {
1102
+ col.appendChild(_UILayoutDebugger._renderStepDetail(activeStep));
1103
+ }
1104
+ const treeContainer = _UILayoutDebugger._el("div", [
1105
+ "overflow-y: scroll",
1106
+ "flex: 1",
1107
+ "padding: 4px 0",
1108
+ "min-height: 0",
1109
+ "position: relative"
1110
+ ]);
1111
+ registerTree(treeContainer);
1112
+ treeContainer.addEventListener("scroll", () => {
1113
+ const peer = getPeerTree();
1114
+ if (peer && peer.scrollTop !== treeContainer.scrollTop) {
1115
+ peer.scrollTop = treeContainer.scrollTop;
1116
+ }
1117
+ });
1118
+ if (trace.roots.length > 0) {
1119
+ let activeRow = null;
1120
+ for (const treeRoot of trace.roots) {
1121
+ const result = _UILayoutDebugger._renderTreeNode(
1122
+ treeRoot,
1123
+ treeContainer,
1124
+ countMap,
1125
+ activeViewIndex,
1126
+ expandState,
1127
+ stepMap
1128
+ );
1129
+ if (result) {
1130
+ activeRow = result;
1131
+ }
1132
+ }
1133
+ if (activeRow) {
1134
+ const rowRef = activeRow;
1135
+ const containerRef = treeContainer;
1136
+ setTimeout(() => {
1137
+ const rowTop = rowRef.offsetTop;
1138
+ const rowBottom = rowTop + rowRef.offsetHeight;
1139
+ const visTop = containerRef.scrollTop;
1140
+ const visBottom = visTop + containerRef.clientHeight;
1141
+ if (rowTop < visTop || rowBottom > visBottom) {
1142
+ containerRef.scrollTop = rowTop - containerRef.clientHeight / 2;
1143
+ }
1144
+ }, 0);
1145
+ }
1146
+ } else {
1147
+ const msg = _UILayoutDebugger._el("div", ["padding: 8px 10px", "color: #6a6a80"]);
1148
+ msg.textContent = "No steps recorded in this pass.";
1149
+ treeContainer.appendChild(msg);
1150
+ }
1151
+ col.appendChild(treeContainer);
1152
+ if (trace.cacheChanges.length > 0) {
1153
+ let cacheListExpanded = false;
1154
+ const cacheHeader = _UILayoutDebugger._el("div", [
1155
+ "padding: 5px 10px",
1156
+ "border-top: 1px solid rgba(255,255,255,0.08)",
1157
+ "display: flex",
1158
+ "align-items: center",
1159
+ "gap: 5px",
1160
+ "flex-shrink: 0",
1161
+ "cursor: pointer",
1162
+ "font-size: 10px"
1163
+ ]);
1164
+ const cacheChevron = _UILayoutDebugger._el("span", ["color: #7070a0", "font-size: 8px", "width: 10px"]);
1165
+ cacheChevron.textContent = "\u25B8";
1166
+ const cacheTitle = _UILayoutDebugger._el("span", ["color: #a0a0b8"]);
1167
+ cacheTitle.textContent = `Cache writes (${trace.cacheChanges.length})`;
1168
+ cacheHeader.append(cacheChevron, cacheTitle);
1169
+ col.appendChild(cacheHeader);
1170
+ const cacheList = _UILayoutDebugger._el("div", [
1171
+ "display: none",
1172
+ "overflow-y: auto",
1173
+ "max-height: 180px",
1174
+ "flex-shrink: 0",
1175
+ "font-size: 10px",
1176
+ "padding: 2px 0"
1177
+ ]);
1178
+ for (const ev of trace.cacheChanges) {
1179
+ const evRow = _UILayoutDebugger._el("div", [
1180
+ "padding: 2px 10px 2px 14px",
1181
+ "display: flex",
1182
+ "flex-direction: column",
1183
+ "gap: 1px",
1184
+ "border-bottom: 1px solid rgba(255,255,255,0.04)"
1185
+ ]);
1186
+ const topLine = _UILayoutDebugger._el("div", ["display: flex", "gap: 5px", "align-items: baseline"]);
1187
+ const evIdx = _UILayoutDebugger._el("span", ["color: #5a5a70", "flex-shrink: 0"]);
1188
+ evIdx.textContent = `#${ev.eventIndex}`;
1189
+ const evClass = _UILayoutDebugger._el("span", ["color: #ffcc88", "font-weight: bold"]);
1190
+ evClass.textContent = ev.className;
1191
+ const evEid = _UILayoutDebugger._el("span", ["color: #6a6a80"]);
1192
+ evEid.textContent = `#${ev.elementID}`;
1193
+ const evKey = _UILayoutDebugger._el("span", ["color: #9090a8"]);
1194
+ const match = ev.cacheKey.match(/h_(\d+(?:\.\d+)?)__w_(\d+(?:\.\d+)?)/);
1195
+ evKey.textContent = match ? match[1] !== "0" && match[2] !== "0" ? `h\u2264${match[1]} w\u2264${match[2]}` : match[2] !== "0" ? `w\u2264${match[2]}` : `h\u2264${match[1]}` : ev.cacheKey;
1196
+ const evVal = _UILayoutDebugger._el("span", ["color: #88ddff", "margin-left: auto"]);
1197
+ evVal.textContent = `${ev.newValue.width.toFixed(0)}\xD7${ev.newValue.height.toFixed(0)}`;
1198
+ topLine.append(evIdx, evClass, evEid, evKey, evVal);
1199
+ const callerLine = _UILayoutDebugger._el("div", [
1200
+ "display: flex",
1201
+ "gap: 5px",
1202
+ "align-items: baseline"
1203
+ ]);
1204
+ const stepRef = _UILayoutDebugger._el("span", ["color: #5a5a70", "flex-shrink: 0"]);
1205
+ stepRef.textContent = ev.stepIndex >= 0 ? `step ${ev.stepIndex}` : "between steps";
1206
+ const callerFn = _UILayoutDebugger._el("span", ["color: #7878a0", "cursor: pointer"]);
1207
+ callerFn.textContent = ev.callerFunction + "()";
1208
+ callerFn.title = ev.cleanStack;
1209
+ let stackExpanded = false;
1210
+ const stackEl = _UILayoutDebugger._el("div", [
1211
+ "display: none",
1212
+ "margin-top: 2px",
1213
+ "padding: 3px 6px",
1214
+ "background: rgba(255,255,255,0.04)",
1215
+ "border-radius: 3px",
1216
+ "color: #6060808",
1217
+ "font-size: 9px",
1218
+ "white-space: pre",
1219
+ "overflow-x: auto"
1220
+ ]);
1221
+ stackEl.textContent = ev.cleanStack;
1222
+ callerFn.onclick = () => {
1223
+ stackExpanded = !stackExpanded;
1224
+ stackEl.style.display = stackExpanded ? "block" : "none";
1225
+ };
1226
+ callerLine.append(stepRef, callerFn);
1227
+ evRow.append(topLine, callerLine, stackEl);
1228
+ cacheList.appendChild(evRow);
1229
+ }
1230
+ cacheHeader.onclick = () => {
1231
+ cacheListExpanded = !cacheListExpanded;
1232
+ cacheList.style.display = cacheListExpanded ? "block" : "none";
1233
+ cacheChevron.textContent = cacheListExpanded ? "\u25BE" : "\u25B8";
1234
+ };
1235
+ col.appendChild(cacheList);
1236
+ }
1237
+ return col;
1238
+ }
1239
+ static _renderStaleReportPanel(result) {
1240
+ const panel = _UILayoutDebugger._el("div", [
1241
+ "display: flex",
1242
+ "flex-direction: column",
1243
+ "flex: 1",
1244
+ "min-height: 0",
1245
+ "overflow: hidden"
1246
+ ]);
1247
+ const bar = _UILayoutDebugger._el("div", [
1248
+ "padding: 5px 10px",
1249
+ "border-bottom: 1px solid rgba(255,255,255,0.08)",
1250
+ "display: flex",
1251
+ "align-items: center",
1252
+ "gap: 6px",
1253
+ "flex-shrink: 0",
1254
+ "font-size: 10px"
1255
+ ]);
1256
+ const barTitle = _UILayoutDebugger._el("span", ["color: #ffaa55", "flex: 1", "font-weight: bold"]);
1257
+ barTitle.textContent = "\u2622 Stale Layout Report";
1258
+ const rerunBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#ffaa55"));
1259
+ rerunBtn.textContent = "\u21BA Re-run";
1260
+ rerunBtn.title = "Re-run performForcedSubtreeLayout and refresh the report";
1261
+ rerunBtn.onclick = () => _UILayoutDebugger.captureStaleLayoutReport();
1262
+ const closeBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#9090a8"));
1263
+ closeBtn.textContent = "\u2715";
1264
+ closeBtn.title = "Close stale report panel";
1265
+ closeBtn.onclick = () => {
1266
+ _UILayoutDebugger._staleReportMode = false;
1267
+ _UILayoutDebugger._renderOverlay();
1268
+ };
1269
+ bar.append(barTitle, rerunBtn, closeBtn);
1270
+ panel.appendChild(bar);
1271
+ const { diffs, forcedPassCacheChanges, passIndex } = result;
1272
+ const counts = { frame: 0, cache: 0, both: 0, appeared: 0, disappeared: 0 };
1273
+ for (const d of diffs) {
1274
+ if (d.kind === "frame") {
1275
+ counts.frame++;
1276
+ } else if (d.kind === "cache") {
1277
+ counts.cache++;
1278
+ } else if (d.kind === "both") {
1279
+ counts.both++;
1280
+ } else if (d.kind === "appeared") {
1281
+ counts.appeared++;
1282
+ } else if (d.kind === "disappeared") {
1283
+ counts.disappeared++;
1284
+ }
1285
+ }
1286
+ const totalCorrected = diffs.length;
1287
+ const summaryBar = _UILayoutDebugger._el("div", [
1288
+ "padding: 5px 10px",
1289
+ "border-bottom: 1px solid rgba(255,255,255,0.08)",
1290
+ "display: flex",
1291
+ "gap: 8px",
1292
+ "align-items: center",
1293
+ "flex-shrink: 0",
1294
+ "font-size: 10px",
1295
+ "flex-wrap: wrap"
1296
+ ]);
1297
+ if (totalCorrected === 0) {
1298
+ const clean = _UILayoutDebugger._el("span", ["color: #88ff99", "font-weight: bold"]);
1299
+ clean.textContent = "\u2713 No stale state detected \u2014 all views were already correct.";
1300
+ summaryBar.appendChild(clean);
1301
+ } else {
1302
+ const total = _UILayoutDebugger._el("span", ["color: #ffaa55", "font-weight: bold"]);
1303
+ total.textContent = `${totalCorrected} stale view${totalCorrected !== 1 ? "s" : ""} corrected`;
1304
+ summaryBar.appendChild(total);
1305
+ const summaryItems = [
1306
+ ["frame+cache", counts.both, "#ffaa55"],
1307
+ ["frame", counts.frame, "#7bc8ff"],
1308
+ ["cache", counts.cache, "#ffcc88"],
1309
+ ["appeared", counts.appeared, "#88ff99"],
1310
+ ["disappeared", counts.disappeared, "#ff8888"]
1311
+ ];
1312
+ for (const [label, count, color] of summaryItems) {
1313
+ if (count === 0) {
1314
+ continue;
1315
+ }
1316
+ const chip = _UILayoutDebugger._el("span", [`color: ${color}`]);
1317
+ chip.textContent = `${count} ${label}`;
1318
+ summaryBar.appendChild(chip);
1319
+ }
1320
+ }
1321
+ const passTag = _UILayoutDebugger._el("span", ["color: #5a5a70", "margin-left: auto", "white-space: nowrap", "flex-shrink: 0"]);
1322
+ passTag.textContent = passIndex >= 0 ? `forced pass #${passIndex}` : "";
1323
+ summaryBar.appendChild(passTag);
1324
+ panel.appendChild(summaryBar);
1325
+ const tmBefore = result.before.textMeasurement;
1326
+ const tmAfter = result.after.textMeasurement;
1327
+ const tmPreparedCleared = tmBefore.preparedCacheSize > 0 && tmAfter.preparedCacheSize === 0;
1328
+ const tmStyleCleared = tmBefore.styleCacheSize > 0 && tmAfter.styleCacheSize === 0;
1329
+ const tmKnown = tmBefore.preparedCacheSize >= 0;
1330
+ if (tmKnown) {
1331
+ const tmBar = _UILayoutDebugger._el("div", [
1332
+ "padding: 4px 10px",
1333
+ "border-bottom: 1px solid rgba(255,255,255,0.08)",
1334
+ "display: flex",
1335
+ "gap: 10px",
1336
+ "flex-shrink: 0",
1337
+ "font-size: 9px",
1338
+ "color: #7060a0",
1339
+ "flex-wrap: wrap"
1340
+ ]);
1341
+ const tmLabel = _UILayoutDebugger._el("span", ["color: #7060a0", "font-weight: bold", "flex-shrink: 0"]);
1342
+ tmLabel.textContent = "UITextMeasurement (global):";
1343
+ tmBar.appendChild(tmLabel);
1344
+ const prepChip = _UILayoutDebugger._el("span", [
1345
+ "color: " + (tmPreparedCleared ? "#ffaa55" : "#5a5a70")
1346
+ ]);
1347
+ prepChip.textContent = `preparedCache ${tmBefore.preparedCacheSize} \u2192 ${tmAfter.preparedCacheSize}` + (tmPreparedCleared ? " \u2713 cleared" : "");
1348
+ tmBar.appendChild(prepChip);
1349
+ const styleChip = _UILayoutDebugger._el("span", [
1350
+ "color: " + (tmStyleCleared ? "#ffaa55" : "#5a5a70")
1351
+ ]);
1352
+ styleChip.textContent = `styleCache ${tmBefore.styleCacheSize} \u2192 ${tmAfter.styleCacheSize}` + (tmStyleCleared ? " \u2713 cleared" : "");
1353
+ tmBar.appendChild(styleChip);
1354
+ panel.appendChild(tmBar);
1355
+ }
1356
+ let staleFilter = totalCorrected > 0 ? "all" : "all";
1357
+ const tabBar = _UILayoutDebugger._el("div", [
1358
+ "display: flex",
1359
+ "border-bottom: 1px solid rgba(255,255,255,0.08)",
1360
+ "flex-shrink: 0",
1361
+ "font-size: 10px"
1362
+ ]);
1363
+ const list = _UILayoutDebugger._el("div", [
1364
+ "overflow-y: auto",
1365
+ "flex: 1",
1366
+ "padding: 4px 0"
1367
+ ]);
1368
+ const renderList = (filter) => {
1369
+ var _a;
1370
+ list.innerHTML = "";
1371
+ if (diffs.length === 0) {
1372
+ const msg = _UILayoutDebugger._el("div", ["padding: 10px 12px", "color: #88ff99", "font-size: 10px"]);
1373
+ msg.textContent = "\u2713 Nothing was corrected \u2014 no missing invalidations detected.";
1374
+ list.appendChild(msg);
1375
+ return;
1376
+ }
1377
+ const visible = filter === "all" ? diffs : filter === "frame" ? diffs.filter((d) => d.kind === "frame" || d.kind === "both") : diffs.filter((d) => d.kind === "cache" || d.kind === "both");
1378
+ if (visible.length === 0) {
1379
+ const msg = _UILayoutDebugger._el("div", ["padding: 8px 12px", "color: #5a5a70", "font-size: 10px"]);
1380
+ msg.textContent = "No items.";
1381
+ list.appendChild(msg);
1382
+ return;
1383
+ }
1384
+ for (const d of visible) {
1385
+ const cacheWrites = (_a = forcedPassCacheChanges.get(d.viewIndex)) != null ? _a : [];
1386
+ const row = _UILayoutDebugger._el("div", [
1387
+ "padding: 5px 10px",
1388
+ "border-bottom: 1px solid rgba(255,255,255,0.05)",
1389
+ "font-size: 10px"
1390
+ ]);
1391
+ const topLine = _UILayoutDebugger._el("div", ["display: flex", "gap: 6px", "align-items: baseline", "margin-bottom: 2px"]);
1392
+ const kindColors = {
1393
+ appeared: "#88ff99",
1394
+ disappeared: "#ff8888",
1395
+ both: "#ffaa55",
1396
+ frame: "#7bc8ff",
1397
+ cache: "#ffcc88",
1398
+ unchanged: "#5a5a70"
1399
+ };
1400
+ const kindTag = _UILayoutDebugger._el("span", [
1401
+ `color: ${kindColors[d.kind]}`,
1402
+ "flex-shrink: 0",
1403
+ "font-size: 9px",
1404
+ "font-weight: bold",
1405
+ "min-width: 70px"
1406
+ ]);
1407
+ kindTag.textContent = d.kind.toUpperCase();
1408
+ const cls = _UILayoutDebugger._el("span", ["color: #ffcc88", "font-weight: bold"]);
1409
+ cls.textContent = d.className;
1410
+ const eid = _UILayoutDebugger._el("span", ["color: #6a6a80"]);
1411
+ eid.textContent = ` #${d.elementID}`;
1412
+ const jumpBtn = _UILayoutDebugger._el("span", [
1413
+ "color: #59599b",
1414
+ "margin-left: auto",
1415
+ "font-size: 9px",
1416
+ "cursor: pointer",
1417
+ "flex-shrink: 0"
1418
+ ]);
1419
+ jumpBtn.textContent = `pass #${passIndex} \u2197`;
1420
+ jumpBtn.title = "Jump to this view in the forced-layout pass";
1421
+ jumpBtn.onclick = () => {
1422
+ const trace = _UILayoutDebugger._traces.find((t) => t.passIndex === passIndex);
1423
+ if (!trace) {
1424
+ return;
1425
+ }
1426
+ const ti = _UILayoutDebugger._traces.indexOf(trace);
1427
+ const si = trace.steps.findIndex((s) => s.viewIndex === d.viewIndex);
1428
+ if (si < 0) {
1429
+ return;
1430
+ }
1431
+ _UILayoutDebugger._replayTraceIndex = ti;
1432
+ _UILayoutDebugger._replayStepIndex = si;
1433
+ _UILayoutDebugger._renderOverlay();
1434
+ };
1435
+ topLine.append(kindTag, cls, eid, jumpBtn);
1436
+ row.appendChild(topLine);
1437
+ if (d.kind === "frame" || d.kind === "both") {
1438
+ const frameLine = _UILayoutDebugger._el("div", ["padding-left: 76px", "color: #b0b0c8", "font-size: 9px", "margin-bottom: 1px"]);
1439
+ frameLine.textContent = "frame: " + _UILayoutDebugger._formatFrameDiff(d.baselineFrame, d.currentFrame);
1440
+ row.appendChild(frameLine);
1441
+ }
1442
+ if (d.kind === "cache" || d.kind === "both") {
1443
+ const cacheLine = _UILayoutDebugger._el("div", ["padding-left: 76px", "color: #a0a090", "font-size: 9px", "margin-bottom: 1px"]);
1444
+ cacheLine.textContent = "cache: " + _UILayoutDebugger._formatCacheDiff(d.baselineCache, d.currentCache);
1445
+ row.appendChild(cacheLine);
1446
+ if (cacheWrites.length > 0) {
1447
+ const xrefHeader = _UILayoutDebugger._el("div", [
1448
+ "padding-left: 76px",
1449
+ "color: #7060a0",
1450
+ "font-size: 9px",
1451
+ "margin-top: 3px",
1452
+ "margin-bottom: 1px",
1453
+ "font-weight: bold"
1454
+ ]);
1455
+ xrefHeader.textContent = `\u21B3 recomputed by (${cacheWrites.length} write${cacheWrites.length !== 1 ? "s" : ""} in forced pass):`;
1456
+ row.appendChild(xrefHeader);
1457
+ for (const ev of cacheWrites) {
1458
+ const writeRow = _UILayoutDebugger._el("div", [
1459
+ "padding-left: 84px",
1460
+ "display: flex",
1461
+ "gap: 5px",
1462
+ "align-items: baseline",
1463
+ "font-size: 9px"
1464
+ ]);
1465
+ const keyMatch = ev.cacheKey.match(/h_(\d+(?:\.\d+)?)__w_(\d+(?:\.\d+)?)/);
1466
+ const keyLabel = keyMatch ? keyMatch[1] !== "0" && keyMatch[2] !== "0" ? `h\u2264${keyMatch[1]} w\u2264${keyMatch[2]}` : keyMatch[2] !== "0" ? `w\u2264${keyMatch[2]}` : `h\u2264${keyMatch[1]}` : ev.cacheKey;
1467
+ const keySpan = _UILayoutDebugger._el("span", ["color: #6060808", "flex-shrink: 0"]);
1468
+ keySpan.textContent = keyLabel;
1469
+ const valSpan = _UILayoutDebugger._el("span", ["color: #88ddff", "flex-shrink: 0"]);
1470
+ valSpan.textContent = `\u2192 ${ev.newValue.width.toFixed(0)}\xD7${ev.newValue.height.toFixed(0)}`;
1471
+ const callerSpan = _UILayoutDebugger._el("span", ["color: #7070a8", "cursor: pointer"]);
1472
+ callerSpan.textContent = ev.callerFunction + "()";
1473
+ callerSpan.title = ev.cleanStack;
1474
+ let stackOpen = false;
1475
+ const stackEl = _UILayoutDebugger._el("div", [
1476
+ "display: none",
1477
+ "margin-top: 2px",
1478
+ "padding: 3px 6px",
1479
+ "background: rgba(255,255,255,0.04)",
1480
+ "border-radius: 3px",
1481
+ "color: #6060808",
1482
+ "font-size: 9px",
1483
+ "white-space: pre",
1484
+ "overflow-x: auto"
1485
+ ]);
1486
+ stackEl.textContent = ev.cleanStack;
1487
+ callerSpan.onclick = () => {
1488
+ stackOpen = !stackOpen;
1489
+ stackEl.style.display = stackOpen ? "block" : "none";
1490
+ };
1491
+ writeRow.append(keySpan, valSpan, callerSpan);
1492
+ row.appendChild(writeRow);
1493
+ row.appendChild(stackEl);
1494
+ }
1495
+ }
1496
+ }
1497
+ list.appendChild(row);
1498
+ }
1499
+ };
1500
+ const filterTabs = [
1501
+ ["All", "all", diffs.length, "#ffaa55"],
1502
+ ["Frame", "frame", counts.frame + counts.both, "#7bc8ff"],
1503
+ ["Cache", "cache", counts.cache + counts.both, "#ffcc88"]
1504
+ ];
1505
+ const buildTabs = () => {
1506
+ tabBar.innerHTML = "";
1507
+ for (const [label, filter, count, color] of filterTabs) {
1508
+ if (count === 0 && filter !== "all") {
1509
+ continue;
1510
+ }
1511
+ const tab = _UILayoutDebugger._el("div", [
1512
+ "padding: 4px 8px",
1513
+ "cursor: pointer",
1514
+ "border-bottom: 2px solid " + (staleFilter === filter ? color : "transparent"),
1515
+ `color: ${staleFilter === filter ? color : "#6a6a80"}`,
1516
+ "white-space: nowrap",
1517
+ "font-size: 10px"
1518
+ ]);
1519
+ tab.textContent = `${label} (${count})`;
1520
+ tab.onclick = () => {
1521
+ staleFilter = filter;
1522
+ buildTabs();
1523
+ renderList(staleFilter);
1524
+ };
1525
+ tabBar.appendChild(tab);
1526
+ }
1527
+ };
1528
+ buildTabs();
1529
+ panel.appendChild(tabBar);
1530
+ panel.appendChild(list);
1531
+ renderList(staleFilter);
1532
+ return panel;
1533
+ }
1534
+ static _renderHelpPanel() {
1535
+ const panel = _UILayoutDebugger._el("div", [
1536
+ "overflow-y: auto",
1537
+ "flex: 1",
1538
+ "padding: 14px 16px",
1539
+ "font-size: 11px",
1540
+ "line-height: 1.6",
1541
+ "color: #c0c0d8"
1542
+ ]);
1543
+ const section = (heading, body) => {
1544
+ const h = _UILayoutDebugger._el("div", [
1545
+ "font-weight: bold",
1546
+ "color: #c8d8ff",
1547
+ "margin-top: 14px",
1548
+ "margin-bottom: 4px",
1549
+ "font-size: 11px",
1550
+ "border-bottom: 1px solid rgba(255,255,255,0.08)",
1551
+ "padding-bottom: 3px"
1552
+ ]);
1553
+ h.textContent = heading;
1554
+ const b = _UILayoutDebugger._el("div", ["color: #a0a0b8", "white-space: pre-wrap"]);
1555
+ b.textContent = body;
1556
+ panel.appendChild(h);
1557
+ panel.appendChild(b);
1558
+ };
1559
+ const kv = (key, value) => {
1560
+ const row = _UILayoutDebugger._el("div", ["display: flex", "gap: 8px", "margin-bottom: 4px"]);
1561
+ const k = _UILayoutDebugger._el("span", [
1562
+ "color: #ffcc88",
1563
+ "font-weight: bold",
1564
+ "flex-shrink: 0",
1565
+ "min-width: 110px"
1566
+ ]);
1567
+ k.textContent = key;
1568
+ const v = _UILayoutDebugger._el("span", ["color: #a0a0b8"]);
1569
+ v.textContent = value;
1570
+ row.append(k, v);
1571
+ panel.appendChild(row);
1572
+ };
1573
+ section("Core concepts", "");
1574
+ kv(
1575
+ "Pass",
1576
+ "One full run of layoutViewsIfNeeded(). The scheduler collects every view that called setNeedsLayout() and works through them all. A single user action typically triggers one pass."
1577
+ );
1578
+ kv(
1579
+ "Iteration",
1580
+ "One loop of the while-loop inside a pass. Laying out a view can call setNeedsLayout() on another view, adding it to the queue mid-pass. The loop repeats until the queue is empty. Most passes have 1 iteration; more than 1 signals that layout is triggering further layout."
1581
+ );
1582
+ kv(
1583
+ "Step",
1584
+ "One view being laid out within a pass \u2014 one call to layoutIfNeeded(). A pass with 8 steps means 8 views had their layout computed. Steps are the atomic unit you step through in the scrubber."
1585
+ );
1586
+ kv(
1587
+ "Trigger",
1588
+ "The call stack at the moment setNeedsLayout() was called on a view. Tells you what caused the view to enter the layout queue."
1589
+ );
1590
+ kv(
1591
+ "Heat colour",
1592
+ "Green = laid out once. Orange = laid out twice (possible unnecessary work). Red = laid out 3+ times (likely a layout cycle or redundant invalidation)."
1593
+ );
1594
+ section("Pass inspector", "");
1595
+ kv(
1596
+ "Pass picker",
1597
+ "Selects which recorded pass to inspect. The most recent pass is shown first. Up to 20 passes are stored."
1598
+ );
1599
+ kv(
1600
+ "\u25C0 \u25B6 scrubber",
1601
+ "Steps through the views laid out in the selected pass one at a time. The active view is highlighted in the tree and its frame diff, intrinsic cache diff, trigger stack, and subview changes are shown above."
1602
+ );
1603
+ kv(
1604
+ "Frame filter",
1605
+ "All: show every step. Changed: only steps where the frame actually moved or resized. Unchanged: only no-ops. The counter shows n/total when filtered."
1606
+ );
1607
+ kv(
1608
+ "Compare \u29C9",
1609
+ "Splits the panel into two columns, each showing an independent pass. The tree expands and collapses in sync between both columns."
1610
+ );
1611
+ kv(
1612
+ "Cache writes",
1613
+ "Collapsible section at the bottom of each column. Every write to a view's intrinsic size cache during that pass is listed, with the value written and the caller that triggered the measurement."
1614
+ );
1615
+ section("Baseline & diff", "");
1616
+ kv(
1617
+ "\u{1F4CD} Baseline",
1618
+ "Captures a flat snapshot of every view's frame and intrinsic cache right now. Requires at least one layout pass to have run so the root view is known. Click again to recapture."
1619
+ );
1620
+ kv(
1621
+ "\u2295 Diff",
1622
+ "Captures a second snapshot and shows what changed since the baseline. Changes are sorted: appeared, disappeared, frame+cache, frame-only, cache-only. Click any row to jump to the pass that caused that change."
1623
+ );
1624
+ kv(
1625
+ "pass #N tag",
1626
+ "Shown on each diff row. Indicates the first recorded pass (by pass number) in which that view was laid out after the baseline was taken."
1627
+ );
1628
+ kv(
1629
+ "\u2B1A Frame / Bounds",
1630
+ "Toggles how frame comparisons are made across all diff panels. Frame mode (default): a view is considered changed if its position or size changed. Bounds mode: only size changes are counted \u2014 origin (x/y) is ignored. Use bounds mode when you care only about content-affecting size changes and want to suppress noise from views that merely moved."
1631
+ );
1632
+ section("Stale layout report \u2622", "");
1633
+ kv(
1634
+ "\u2622 Stale",
1635
+ "The primary tool for finding missing cache invalidations. Click once to snapshot the current tree state, then immediately call performForcedSubtreeLayout() for a complete cold remeasure, then diff the two snapshots. Any view that changed was holding stale/incorrect state \u2014 its invalidation was missed somewhere."
1636
+ );
1637
+ kv(
1638
+ "Corrected views",
1639
+ "Each row in the \u2622 panel is a view the forced pass corrected. The kind tag shows whether the frame, intrinsic cache, or both were stale."
1640
+ );
1641
+ kv(
1642
+ "\u21B3 recomputed by",
1643
+ "For cache corrections, the exact call path(s) that recomputed the correct value during the forced pass are listed below each row, with expandable stack traces. Work backwards from that call site to find where the corresponding invalidation should have been triggered but wasn't."
1644
+ );
1645
+ kv(
1646
+ "pass #N \u2197",
1647
+ "Each row has a jump link to the forced-layout pass in the pass inspector so you can step through the remeasure for that view in full detail."
1648
+ );
1649
+ kv(
1650
+ "Re-run",
1651
+ "Right-click the \u2622 Stale button (or use \u21BA Re-run in the panel) to re-run the report without closing and reopening. Useful after you've applied a fix and want to verify the stale count drops to zero."
1652
+ );
1653
+ kv(
1654
+ "When to use it",
1655
+ "Reproduce the bug, then immediately click \u2622 Stale before triggering any other layout. The tree must be in its incorrect state at the moment you click. The forced layout will correct it, which is visible in the UI after the report runs."
1656
+ );
1657
+ section("Live inspector \u{1F441}", "");
1658
+ kv(
1659
+ "\u{1F441} Live",
1660
+ "Shows the current view tree as it exists right now \u2014 not a recorded snapshot. Frames and cache entries reflect the live DOM state. Use \u21BA Refresh to re-read after triggering layout manually."
1661
+ );
1662
+ section("Breakpoint mode \u23F8", "");
1663
+ kv(
1664
+ "\u23F8 BP",
1665
+ "When enabled, a sentinel line executes before every layoutIfNeeded() call. Set a browser debugger breakpoint on that line to pause before each step with the full live JS stack in scope."
1666
+ );
1667
+ kv(
1668
+ "Finding the line",
1669
+ "In Chrome DevTools Sources, press Cmd+Opt+F (Mac) or Ctrl+Shift+F (Windows) and search for: breakpointOnThisLine"
1670
+ );
1671
+ section("Console API", "All methods are on the global UILayoutDebugger object.");
1672
+ kv("UILayoutDebugger.enable()", "Start or stop recording and show/hide the overlay.");
1673
+ kv("UILayoutDebugger.disable()", "Stop recording and hide the overlay.");
1674
+ kv("UILayoutDebugger.enableBreakpoints()", "Turn on breakpoint step-through mode.");
1675
+ kv("UILayoutDebugger.captureBaseline()", "Snapshot current state as baseline.");
1676
+ kv("UILayoutDebugger.captureAndDiff()", "Snapshot and diff against baseline.");
1677
+ kv("UILayoutDebugger.captureStaleLayoutReport()", "Snapshot \u2192 forced remeasure \u2192 diff. Primary cache-staleness diagnostic.");
1678
+ kv("UILayoutDebugger.clearStaleReport()", "Clear the stale report result and close the panel.");
1679
+ kv("UILayoutDebugger.clearTraces()", "Discard all recorded passes and reset the counter.");
1680
+ kv("UILayoutDebugger.goToStep(n)", "Jump to step n (0-based) in the current pass.");
1681
+ kv("UILayoutDebugger.toggleLiveInspector()", "Show or hide the live view tree panel.");
1682
+ kv("UILayoutDebugger._boundsBasedDiff = true", "Switch all frame diffs to size-only (bounds) mode from the console.");
1683
+ return panel;
1684
+ }
1685
+ static _renderLiveInspectorPanel() {
1686
+ const panel = _UILayoutDebugger._el("div", [
1687
+ "display: flex",
1688
+ "flex-direction: column",
1689
+ "flex: 1",
1690
+ "min-height: 0",
1691
+ "overflow: hidden"
1692
+ ]);
1693
+ const bar = _UILayoutDebugger._el("div", [
1694
+ "padding: 5px 10px",
1695
+ "border-bottom: 1px solid rgba(255,255,255,0.08)",
1696
+ "display: flex",
1697
+ "align-items: center",
1698
+ "gap: 6px",
1699
+ "flex-shrink: 0",
1700
+ "font-size: 10px"
1701
+ ]);
1702
+ const barTitle = _UILayoutDebugger._el("span", ["color: #a0a0b8", "flex: 1", "font-weight: bold"]);
1703
+ barTitle.textContent = "\u{1F441} Live View Tree";
1704
+ const refreshBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#9090a8"));
1705
+ refreshBtn.textContent = "\u21BA Refresh";
1706
+ refreshBtn.title = "Re-capture current state";
1707
+ refreshBtn.onclick = () => _UILayoutDebugger._renderOverlay();
1708
+ const syncBtn = _UILayoutDebugger._el("button", _UILayoutDebugger._btnStyle("#9090a8"));
1709
+ syncBtn.textContent = "\u21C4 Sync";
1710
+ syncBtn.title = "Sync collapse/expand state from pass inspector. Views not in the pass inspector are kept collapsed.";
1711
+ syncBtn.onclick = () => {
1712
+ const source = _UILayoutDebugger._compareMode ? _UILayoutDebugger._sharedExpandState : _UILayoutDebugger._singleExpandState;
1713
+ const tracedViews = /* @__PURE__ */ new Set();
1714
+ for (const trace of _UILayoutDebugger._traces) {
1715
+ for (const step of trace.steps) {
1716
+ tracedViews.add(step.viewIndex);
1717
+ }
1718
+ }
1719
+ const newState = /* @__PURE__ */ new Map();
1720
+ for (const [idx, expanded] of source) {
1721
+ newState.set(idx, expanded);
1722
+ }
1723
+ _UILayoutDebugger._walkViewTree(
1724
+ _UILayoutDebugger._lastKnownRootView,
1725
+ /* @__PURE__ */ new Map(),
1726
+ /* @__PURE__ */ new Set()
1727
+ );
1728
+ const liveIndices = /* @__PURE__ */ new Set();
1729
+ const collectIndices = (view, visited) => {
1730
+ var _a, _b;
1731
+ const idx = (_a = view == null ? void 0 : view._UIViewIndex) != null ? _a : -1;
1732
+ if (idx < 0 || visited.has(idx)) {
1733
+ return;
1734
+ }
1735
+ visited.add(idx);
1736
+ liveIndices.add(idx);
1737
+ for (const sv of (_b = view == null ? void 0 : view.subviews) != null ? _b : []) {
1738
+ collectIndices(sv, visited);
1739
+ }
1740
+ };
1741
+ collectIndices(_UILayoutDebugger._lastKnownRootView, /* @__PURE__ */ new Set());
1742
+ for (const idx of liveIndices) {
1743
+ if (!newState.has(idx)) {
1744
+ newState.set(idx, tracedViews.has(idx));
1745
+ }
1746
+ }
1747
+ _UILayoutDebugger._liveExpandState = newState;
1748
+ _UILayoutDebugger._renderOverlay();
1749
+ };
1750
+ bar.append(barTitle, refreshBtn, syncBtn);
1751
+ panel.appendChild(bar);
1752
+ const root = _UILayoutDebugger._lastKnownRootView;
1753
+ if (!root) {
1754
+ const msg = _UILayoutDebugger._el("div", ["padding: 10px 12px", "color: #6a6a80", "font-size: 10px"]);
1755
+ msg.textContent = "No root view found yet \u2014 trigger a layout pass first.";
1756
+ panel.appendChild(msg);
1757
+ return panel;
1758
+ }
1759
+ const treeContainer = _UILayoutDebugger._el("div", [
1760
+ "overflow-y: auto",
1761
+ "flex: 1",
1762
+ "padding: 4px 0",
1763
+ "position: relative"
1764
+ ]);
1765
+ _UILayoutDebugger._renderLiveNode(
1766
+ root,
1767
+ treeContainer,
1768
+ 0,
1769
+ /* @__PURE__ */ new Set(),
1770
+ _UILayoutDebugger._liveExpandState
1771
+ );
1772
+ panel.appendChild(treeContainer);
1773
+ return panel;
1774
+ }
1775
+ static _renderLiveNode(view, container, depth, visited, expandState) {
1776
+ var _a, _b, _c, _d, _e;
1777
+ const idx = (_a = view == null ? void 0 : view._UIViewIndex) != null ? _a : -1;
1778
+ if (idx < 0 || visited.has(idx)) {
1779
+ return;
1780
+ }
1781
+ visited.add(idx);
1782
+ const frame = _UILayoutDebugger._captureFrame(view);
1783
+ const cache = _UILayoutDebugger._captureCache(view);
1784
+ const className = (_c = (_b = view == null ? void 0 : view.constructor) == null ? void 0 : _b.name) != null ? _c : "UnknownView";
1785
+ const elementID = (_d = view == null ? void 0 : view.elementID) != null ? _d : String(idx);
1786
+ const subviews = (_e = view == null ? void 0 : view.subviews) != null ? _e : [];
1787
+ const hasChildren = subviews.length > 0;
1788
+ if (!expandState.has(idx)) {
1789
+ expandState.set(idx, true);
1790
+ }
1791
+ let expanded = expandState.get(idx);
1792
+ const row = _UILayoutDebugger._el("div", [
1793
+ "display: flex",
1794
+ "align-items: baseline",
1795
+ "padding: 1px 10px 1px " + (10 + depth * 12) + "px",
1796
+ "cursor: " + (hasChildren ? "pointer" : "default"),
1797
+ "border-radius: 3px",
1798
+ "margin: 0 4px"
1799
+ ]);
1800
+ const chevron = _UILayoutDebugger._el("span", [
1801
+ "display: inline-block",
1802
+ "width: 10px",
1803
+ "flex-shrink: 0",
1804
+ "color: #7070a0",
1805
+ "font-size: 8px",
1806
+ "margin-right: 2px",
1807
+ "text-align: center"
1808
+ ]);
1809
+ chevron.textContent = !hasChildren ? "" : expanded ? "\u25BE" : "\u25B8";
1810
+ const dot = _UILayoutDebugger._el("span", [
1811
+ "display: inline-block",
1812
+ "width: 7px",
1813
+ "height: 7px",
1814
+ "border-radius: 50%",
1815
+ "margin-right: 5px",
1816
+ "flex-shrink: 0",
1817
+ "background: #4a4a5a"
1818
+ ]);
1819
+ const classSpan = _UILayoutDebugger._el("span", ["color: #9090a8"]);
1820
+ classSpan.textContent = className;
1821
+ const eidSpan = _UILayoutDebugger._el("span", ["color: #6a6a80", "margin-left: 4px"]);
1822
+ eidSpan.textContent = `#${elementID}`;
1823
+ const frameSpan = _UILayoutDebugger._el("span", ["color: #55556a", "margin-left: 4px", "font-size: 9px"]);
1824
+ frameSpan.textContent = frame ? `${frame.left.toFixed(0)},${frame.top.toFixed(0)} ${frame.width.toFixed(0)}\xD7${frame.height.toFixed(0)}` : "";
1825
+ row.append(chevron, dot, classSpan, eidSpan, frameSpan);
1826
+ row.title = [
1827
+ `${className} #${elementID}`,
1828
+ frame ? `frame: ${frame.left.toFixed(1)},${frame.top.toFixed(1)} ${frame.width.toFixed(1)}\xD7${frame.height.toFixed(1)}` : "frame: (none)",
1829
+ "cache: " + _UILayoutDebugger._formatCacheSnapshot(cache)
1830
+ ].join("\n");
1831
+ container.appendChild(row);
1832
+ if (!hasChildren) {
1833
+ return;
1834
+ }
1835
+ const childContainer = _UILayoutDebugger._el("div", [
1836
+ "display: " + (expanded ? "block" : "none")
1837
+ ]);
1838
+ container.appendChild(childContainer);
1839
+ for (const sv of subviews) {
1840
+ _UILayoutDebugger._renderLiveNode(sv, childContainer, depth + 1, visited, expandState);
1841
+ }
1842
+ row.onclick = () => {
1843
+ expanded = !expanded;
1844
+ expandState.set(idx, expanded);
1845
+ childContainer.style.display = expanded ? "block" : "none";
1846
+ chevron.textContent = expanded ? "\u25BE" : "\u25B8";
1847
+ };
1848
+ }
1849
+ static _renderDiffPanel(baseline, current, onNavigate, baselineTakenAt) {
1850
+ const panel = _UILayoutDebugger._el("div", [
1851
+ "display: flex",
1852
+ "flex-direction: column",
1853
+ "flex: 1",
1854
+ "min-height: 0",
1855
+ "overflow: hidden"
1856
+ ]);
1857
+ const diffs = _UILayoutDebugger._diffSnapshots(baseline, current);
1858
+ const counts = { appeared: 0, disappeared: 0, frame: 0, cache: 0, both: 0, unchanged: 0 };
1859
+ for (const d of diffs) {
1860
+ counts[d.kind]++;
1861
+ }
1862
+ const changed = counts.appeared + counts.disappeared + counts.frame + counts.cache + counts.both;
1863
+ const summaryBar = _UILayoutDebugger._el("div", [
1864
+ "padding: 5px 10px",
1865
+ "border-bottom: 1px solid rgba(255,255,255,0.08)",
1866
+ "display: flex",
1867
+ "gap: 8px",
1868
+ "align-items: center",
1869
+ "flex-shrink: 0",
1870
+ "font-size: 10px",
1871
+ "flex-wrap: wrap"
1872
+ ]);
1873
+ const summaryItems = [
1874
+ ["appeared", counts.appeared, "#88ff99"],
1875
+ ["disappeared", counts.disappeared, "#ff8888"],
1876
+ ["frame+cache", counts.both, "#ffaa55"],
1877
+ ["frame", counts.frame, "#7bc8ff"],
1878
+ ["cache", counts.cache, "#ffcc88"],
1879
+ ["unchanged", counts.unchanged, "#5a5a70"]
1880
+ ];
1881
+ for (const [label, count, color] of summaryItems) {
1882
+ if (count === 0) {
1883
+ continue;
1884
+ }
1885
+ const chip = _UILayoutDebugger._el("span", [`color: ${color}`]);
1886
+ chip.textContent = `${count} ${label}`;
1887
+ summaryBar.appendChild(chip);
1888
+ }
1889
+ if (changed === 0) {
1890
+ const chip = _UILayoutDebugger._el("span", ["color: #9090a8"]);
1891
+ chip.textContent = "No changes";
1892
+ summaryBar.appendChild(chip);
1893
+ }
1894
+ const ts = _UILayoutDebugger._el("span", ["color: #5a5a70", "margin-left: auto", "white-space: nowrap"]);
1895
+ const elapsed = ((current.takenAt - baseline.takenAt) / 1e3).toFixed(1);
1896
+ ts.textContent = `+${elapsed}s`;
1897
+ summaryBar.appendChild(ts);
1898
+ panel.appendChild(summaryBar);
1899
+ let diffFilter = changed > 0 ? "frame" : "all";
1900
+ const tabBar = _UILayoutDebugger._el("div", [
1901
+ "display: flex",
1902
+ "border-bottom: 1px solid rgba(255,255,255,0.08)",
1903
+ "flex-shrink: 0",
1904
+ "font-size: 10px"
1905
+ ]);
1906
+ const list = _UILayoutDebugger._el("div", [
1907
+ "overflow-y: auto",
1908
+ "flex: 1",
1909
+ "padding: 4px 0"
1910
+ ]);
1911
+ const renderList = (filter) => {
1912
+ list.innerHTML = "";
1913
+ const visible = filter === "all" ? diffs : diffs.filter((d) => d.kind === filter || filter === "frame" && d.kind === "both");
1914
+ if (visible.length === 0) {
1915
+ const msg = _UILayoutDebugger._el("div", ["padding: 8px 12px", "color: #5a5a70"]);
1916
+ msg.textContent = "No items.";
1917
+ list.appendChild(msg);
1918
+ return;
1919
+ }
1920
+ for (const d of visible) {
1921
+ const kindColors = {
1922
+ appeared: "#88ff99",
1923
+ disappeared: "#ff8888",
1924
+ both: "#ffaa55",
1925
+ frame: "#7bc8ff",
1926
+ cache: "#ffcc88",
1927
+ unchanged: "#5a5a70"
1928
+ };
1929
+ const causingTrace = d.kind !== "disappeared" ? _UILayoutDebugger._findCausingTrace(d.viewIndex, baselineTakenAt) : null;
1930
+ const hasTrace = !!causingTrace;
1931
+ const row = _UILayoutDebugger._el("div", [
1932
+ "padding: 3px 10px",
1933
+ "border-bottom: 1px solid rgba(255,255,255,0.04)",
1934
+ "font-size: 10px",
1935
+ hasTrace ? "cursor: pointer" : "cursor: default"
1936
+ ]);
1937
+ if (hasTrace) {
1938
+ row.title = `Click to jump to pass #${causingTrace.passIndex} in the pass inspector`;
1939
+ row.onmouseenter = () => {
1940
+ row.style.background = "rgba(255,255,255,0.06)";
1941
+ };
1942
+ row.onmouseleave = () => {
1943
+ row.style.background = "";
1944
+ };
1945
+ row.onclick = () => onNavigate(d.viewIndex);
1946
+ }
1947
+ const topLine = _UILayoutDebugger._el("div", ["display: flex", "gap: 6px", "align-items: baseline"]);
1948
+ const kindTag = _UILayoutDebugger._el("span", [
1949
+ `color: ${kindColors[d.kind]}`,
1950
+ "flex-shrink: 0",
1951
+ "font-size: 9px",
1952
+ "font-weight: bold",
1953
+ "min-width: 70px"
1954
+ ]);
1955
+ kindTag.textContent = d.kind.toUpperCase();
1956
+ const cls = _UILayoutDebugger._el("span", ["color: #ffcc88", "font-weight: bold"]);
1957
+ cls.textContent = d.className;
1958
+ const eid = _UILayoutDebugger._el("span", ["color: #6a6a80"]);
1959
+ eid.textContent = `#${d.elementID}`;
1960
+ if (causingTrace) {
1961
+ const passTag = _UILayoutDebugger._el("span", [
1962
+ "color: #59599b",
1963
+ "margin-left: auto",
1964
+ "font-size: 9px",
1965
+ "flex-shrink: 0"
1966
+ ]);
1967
+ passTag.textContent = `pass #${causingTrace.passIndex}`;
1968
+ topLine.append(kindTag, cls, eid, passTag);
1969
+ } else {
1970
+ topLine.append(kindTag, cls, eid);
1971
+ }
1972
+ row.appendChild(topLine);
1973
+ if (d.kind !== "appeared" && d.kind !== "disappeared") {
1974
+ if (d.kind === "frame" || d.kind === "both") {
1975
+ const frameLine = _UILayoutDebugger._el("div", ["padding-left: 76px", "color: #b0b0c8", "font-size: 9px"]);
1976
+ frameLine.textContent = "frame: " + _UILayoutDebugger._formatFrameDiff(d.baselineFrame, d.currentFrame);
1977
+ row.appendChild(frameLine);
1978
+ }
1979
+ if (d.kind === "cache" || d.kind === "both") {
1980
+ const cacheLine = _UILayoutDebugger._el("div", ["padding-left: 76px", "color: #a0a090", "font-size: 9px"]);
1981
+ cacheLine.textContent = "cache: " + _UILayoutDebugger._formatCacheDiff(d.baselineCache, d.currentCache);
1982
+ row.appendChild(cacheLine);
1983
+ }
1984
+ } else {
1985
+ const frameLine = _UILayoutDebugger._el("div", ["padding-left: 76px", "color: #b0b0c8", "font-size: 9px"]);
1986
+ frameLine.textContent = "frame: " + _UILayoutDebugger._formatFrame(
1987
+ d.kind === "appeared" ? d.currentFrame : d.baselineFrame
1988
+ );
1989
+ row.appendChild(frameLine);
1990
+ }
1991
+ list.appendChild(row);
1992
+ }
1993
+ };
1994
+ const tabs = [
1995
+ ["All", "all", diffs.length, "#9090a8"],
1996
+ ["Frame", "frame", counts.frame + counts.both, "#7bc8ff"],
1997
+ ["Cache", "cache", counts.cache + counts.both, "#ffcc88"],
1998
+ ["Appeared", "appeared", counts.appeared, "#88ff99"],
1999
+ ["Disappeared", "disappeared", counts.disappeared, "#ff8888"]
2000
+ ];
2001
+ const buildTabs = () => {
2002
+ tabBar.innerHTML = "";
2003
+ for (const [label, filter, count, color] of tabs) {
2004
+ if (count === 0 && filter !== "all") {
2005
+ continue;
2006
+ }
2007
+ const tab = _UILayoutDebugger._el("div", [
2008
+ "padding: 4px 8px",
2009
+ "cursor: pointer",
2010
+ "border-bottom: 2px solid " + (diffFilter === filter ? color : "transparent"),
2011
+ `color: ${diffFilter === filter ? color : "#6a6a80"}`,
2012
+ "white-space: nowrap",
2013
+ "font-size: 10px"
2014
+ ]);
2015
+ tab.textContent = `${label} (${count})`;
2016
+ tab.onclick = () => {
2017
+ diffFilter = filter;
2018
+ buildTabs();
2019
+ renderList(diffFilter);
2020
+ };
2021
+ tabBar.appendChild(tab);
2022
+ }
2023
+ };
2024
+ buildTabs();
2025
+ panel.appendChild(tabBar);
2026
+ panel.appendChild(list);
2027
+ renderList(diffFilter);
2028
+ return panel;
2029
+ }
2030
+ static _renderStepDetail(activeStep) {
2031
+ var _a, _b, _c, _d, _e, _f, _g, _h;
2032
+ const detail = _UILayoutDebugger._el("div", [
2033
+ "padding: 6px 10px",
2034
+ "border-bottom: 1px solid rgba(255,255,255,0.08)",
2035
+ "flex-shrink: 0",
2036
+ "font-size: 10px"
2037
+ ]);
2038
+ const titleRow = _UILayoutDebugger._el("div", ["margin-bottom: 3px"]);
2039
+ const stepTag = _UILayoutDebugger._el("span", [
2040
+ "background: #2a2a44",
2041
+ "border-radius: 3px",
2042
+ "padding: 0 4px",
2043
+ "margin-right: 5px",
2044
+ "color: #aac8ff",
2045
+ "font-weight: bold"
2046
+ ]);
2047
+ stepTag.textContent = `step ${activeStep.stepIndex}`;
2048
+ const iterTag = _UILayoutDebugger._el("span", ["color: #9090a8", "margin-right: 6px"]);
2049
+ iterTag.textContent = `iter ${activeStep.iteration + 1}`;
2050
+ const className = _UILayoutDebugger._el("span", ["color: #ffcc88", "font-weight: bold"]);
2051
+ className.textContent = activeStep.className;
2052
+ const eid = _UILayoutDebugger._el("span", ["color: #9090a8", "margin-left: 4px"]);
2053
+ eid.textContent = `#${activeStep.elementID}`;
2054
+ titleRow.append(stepTag, iterTag, className, eid);
2055
+ detail.appendChild(titleRow);
2056
+ const frameRow = _UILayoutDebugger._el("div", ["color: #c0c0d8", "margin-bottom: 2px"]);
2057
+ frameRow.textContent = "frame: " + _UILayoutDebugger._formatFrameDiff(activeStep.frameBefore, activeStep.frameAfter);
2058
+ detail.appendChild(frameRow);
2059
+ const cacheDiffStr = _UILayoutDebugger._formatCacheDiff(activeStep.cacheBefore, activeStep.cacheAfter);
2060
+ const cacheRow = _UILayoutDebugger._el("div", ["margin-bottom: 3px"]);
2061
+ const cacheLabel = _UILayoutDebugger._el("span", ["color: #8888a0", "margin-right: 4px"]);
2062
+ cacheLabel.textContent = "cache:";
2063
+ const cacheChanged = ((_b = (_a = activeStep.cacheBefore) == null ? void 0 : _a.entryCount) != null ? _b : 0) !== ((_d = (_c = activeStep.cacheAfter) == null ? void 0 : _c.entryCount) != null ? _d : 0) || cacheDiffStr.includes("~") || cacheDiffStr.includes("+") || cacheDiffStr.includes("-");
2064
+ const cacheSummary = _UILayoutDebugger._el("span", [
2065
+ "color: " + (cacheChanged ? "#ffcc88" : "#7878a0"),
2066
+ "cursor: pointer",
2067
+ "font-weight: " + (cacheChanged ? "bold" : "normal")
2068
+ ]);
2069
+ const bCount = (_f = (_e = activeStep.cacheBefore) == null ? void 0 : _e.entryCount) != null ? _f : 0;
2070
+ const aCount = (_h = (_g = activeStep.cacheAfter) == null ? void 0 : _g.entryCount) != null ? _h : 0;
2071
+ cacheSummary.textContent = bCount === aCount ? `${aCount} entr${aCount === 1 ? "y" : "ies"}${cacheChanged ? " (changed)" : ""}` : `${bCount} \u2192 ${aCount} entries`;
2072
+ let cacheExpanded = false;
2073
+ const cacheDetail = _UILayoutDebugger._el("div", [
2074
+ "display: none",
2075
+ "margin-top: 2px",
2076
+ "padding: 4px 6px",
2077
+ "background: rgba(255,255,255,0.04)",
2078
+ "border-radius: 3px",
2079
+ "color: #9090a8",
2080
+ "font-size: 9px",
2081
+ "line-height: 1.6",
2082
+ "white-space: pre"
2083
+ ]);
2084
+ cacheDetail.textContent = cacheDiffStr;
2085
+ cacheSummary.onclick = () => {
2086
+ cacheExpanded = !cacheExpanded;
2087
+ cacheDetail.style.display = cacheExpanded ? "block" : "none";
2088
+ };
2089
+ cacheRow.append(cacheLabel, cacheSummary);
2090
+ detail.appendChild(cacheRow);
2091
+ detail.appendChild(cacheDetail);
2092
+ const trigger = activeStep.trigger;
2093
+ const triggerRow = _UILayoutDebugger._el("div", ["margin-top: 4px", "margin-bottom: 2px"]);
2094
+ if (trigger) {
2095
+ const triggerLabel = _UILayoutDebugger._el("span", ["color: #8888a0", "margin-right: 4px"]);
2096
+ triggerLabel.textContent = "setNeedsLayout from";
2097
+ const triggerFn = _UILayoutDebugger._el("span", ["color: #88ddff", "font-weight: bold", "cursor: pointer"]);
2098
+ triggerFn.textContent = trigger.callerFunction + "()";
2099
+ triggerFn.title = trigger.cleanStack;
2100
+ let stackExpanded = false;
2101
+ const stackEl = _UILayoutDebugger._el("div", [
2102
+ "display: none",
2103
+ "margin-top: 3px",
2104
+ "padding: 4px 6px",
2105
+ "background: rgba(255,255,255,0.04)",
2106
+ "border-radius: 3px",
2107
+ "color: #7878a0",
2108
+ "font-size: 9px",
2109
+ "line-height: 1.5",
2110
+ "white-space: pre",
2111
+ "overflow-x: auto"
2112
+ ]);
2113
+ stackEl.textContent = trigger.cleanStack;
2114
+ triggerFn.onclick = () => {
2115
+ stackExpanded = !stackExpanded;
2116
+ stackEl.style.display = stackExpanded ? "block" : "none";
2117
+ };
2118
+ triggerRow.append(triggerLabel, triggerFn);
2119
+ detail.appendChild(triggerRow);
2120
+ detail.appendChild(stackEl);
2121
+ } else {
2122
+ const triggerLabel = _UILayoutDebugger._el("span", ["color: #5a5a70"]);
2123
+ triggerLabel.textContent = "setNeedsLayout trigger not captured";
2124
+ triggerRow.appendChild(triggerLabel);
2125
+ detail.appendChild(triggerRow);
2126
+ }
2127
+ if (activeStep.subviewRecords.length > 0) {
2128
+ const svHeader = _UILayoutDebugger._el("div", ["color: #8888a0", "margin-top: 4px", "margin-bottom: 2px"]);
2129
+ svHeader.textContent = `Subviews set (${activeStep.subviewRecords.length}):`;
2130
+ detail.appendChild(svHeader);
2131
+ for (const sv of activeStep.subviewRecords) {
2132
+ const svRow = _UILayoutDebugger._el("div", ["padding-left: 8px", "color: #a0a0b8"]);
2133
+ const svClass = _UILayoutDebugger._el("span", ["color: #d8aacc"]);
2134
+ svClass.textContent = sv.className;
2135
+ const svEid = _UILayoutDebugger._el("span", ["color: #6a6a80"]);
2136
+ svEid.textContent = ` #${sv.elementID} `;
2137
+ const svFrames = _UILayoutDebugger._el("span", ["color: #9090a8"]);
2138
+ svFrames.textContent = _UILayoutDebugger._formatFrameDiff(sv.frameBefore, sv.frameAfter);
2139
+ svRow.append(svClass, svEid, svFrames);
2140
+ detail.appendChild(svRow);
2141
+ }
2142
+ }
2143
+ return detail;
2144
+ }
2145
+ static _subtreeHasTouched(node, countMap) {
2146
+ var _a;
2147
+ if (((_a = countMap.get(node.viewIndex)) != null ? _a : 0) > 0) {
2148
+ return true;
2149
+ }
2150
+ for (const child of node.children) {
2151
+ if (_UILayoutDebugger._subtreeHasTouched(child, countMap)) {
2152
+ return true;
2153
+ }
2154
+ }
2155
+ return false;
2156
+ }
2157
+ static _subtreeContains(node, viewIndex) {
2158
+ if (node.viewIndex === viewIndex) {
2159
+ return true;
2160
+ }
2161
+ for (const child of node.children) {
2162
+ if (_UILayoutDebugger._subtreeContains(child, viewIndex)) {
2163
+ return true;
2164
+ }
2165
+ }
2166
+ return false;
2167
+ }
2168
+ static _renderTreeNode(node, container, countMap, activeViewIndex, expandState, stepMap) {
2169
+ var _a;
2170
+ const count = (_a = countMap.get(node.viewIndex)) != null ? _a : 0;
2171
+ const isActive = node.viewIndex === activeViewIndex && activeViewIndex >= 0;
2172
+ const hasChildren = node.children.length > 0;
2173
+ const isAncestorOfActive = activeViewIndex >= 0 ? _UILayoutDebugger._subtreeContains(node, activeViewIndex) : node.children.some((c) => _UILayoutDebugger._subtreeHasTouched(c, countMap));
2174
+ const defaultExpanded = isAncestorOfActive || isActive;
2175
+ let childrenExpanded;
2176
+ if (expandState !== null) {
2177
+ if (!expandState.has(node.viewIndex)) {
2178
+ expandState.set(node.viewIndex, defaultExpanded);
2179
+ }
2180
+ childrenExpanded = expandState.get(node.viewIndex);
2181
+ } else {
2182
+ childrenExpanded = defaultExpanded;
2183
+ }
2184
+ const row = _UILayoutDebugger._el("div", [
2185
+ "display: flex",
2186
+ "align-items: baseline",
2187
+ "padding: 1px 10px 1px " + (10 + node.depth * 12) + "px",
2188
+ "cursor: " + (hasChildren ? "pointer" : "default"),
2189
+ "border-radius: 3px",
2190
+ "margin: 0 4px",
2191
+ isActive ? "background: rgba(100,120,220,0.35); outline: 1px solid #59599b" : "background: transparent"
2192
+ ]);
2193
+ const chevron = _UILayoutDebugger._el("span", [
2194
+ "display: inline-block",
2195
+ "width: 10px",
2196
+ "flex-shrink: 0",
2197
+ "color: #7070a0",
2198
+ "font-size: 8px",
2199
+ "margin-right: 2px",
2200
+ "text-align: center"
2201
+ ]);
2202
+ chevron.textContent = !hasChildren ? "" : childrenExpanded ? "\u25BE" : "\u25B8";
2203
+ const dot = _UILayoutDebugger._el("span", [
2204
+ "display: inline-block",
2205
+ "width: 7px",
2206
+ "height: 7px",
2207
+ "border-radius: 50%",
2208
+ "margin-right: 5px",
2209
+ "flex-shrink: 0",
2210
+ "background: " + _UILayoutDebugger._heatColor(count)
2211
+ ]);
2212
+ const untouchedColor = count === 0 ? "#9090a8" : "#ffcc88";
2213
+ const className = _UILayoutDebugger._el("span", [
2214
+ "color: " + untouchedColor,
2215
+ "font-weight: " + (count > 0 ? "bold" : "normal")
2216
+ ]);
2217
+ className.textContent = node.className;
2218
+ const eid = _UILayoutDebugger._el("span", [
2219
+ "color: " + (count === 0 ? "#6a6a80" : "#a0a0b8"),
2220
+ "margin-left: 4px"
2221
+ ]);
2222
+ eid.textContent = `#${node.elementID}`;
2223
+ const frameStr = node.frame ? ` ${node.frame.left.toFixed(0)},${node.frame.top.toFixed(0)} ${node.frame.width.toFixed(0)}\xD7${node.frame.height.toFixed(0)}` : "";
2224
+ const frameSpan = _UILayoutDebugger._el("span", [
2225
+ "color: " + (count === 0 ? "#55556a" : "#7878a0"),
2226
+ "margin-left: 4px"
2227
+ ]);
2228
+ frameSpan.textContent = frameStr;
2229
+ const step = stepMap.get(node.viewIndex);
2230
+ const frameChanged = step ? !_UILayoutDebugger._framesEqual(step.frameBefore, step.frameAfter) : false;
2231
+ const cacheChanged = step ? !_UILayoutDebugger._cachesEqual(step.cacheBefore, step.cacheAfter) : false;
2232
+ const deltaSpan = _UILayoutDebugger._el("span", [
2233
+ "margin-left: 5px",
2234
+ "font-size: 9px",
2235
+ "flex-shrink: 0"
2236
+ ]);
2237
+ if (frameChanged && cacheChanged) {
2238
+ deltaSpan.style.color = "#ffaa55";
2239
+ deltaSpan.textContent = "frame+cache";
2240
+ } else if (frameChanged) {
2241
+ deltaSpan.style.color = "#7bc8ff";
2242
+ deltaSpan.textContent = _UILayoutDebugger._formatFrameDiff(step.frameBefore, step.frameAfter);
2243
+ } else if (cacheChanged) {
2244
+ deltaSpan.style.color = "#ffcc88";
2245
+ deltaSpan.textContent = "cache changed";
2246
+ }
2247
+ if (count > 0) {
2248
+ const countBadge = _UILayoutDebugger._el("span", [
2249
+ "margin-left: 5px",
2250
+ "background: " + _UILayoutDebugger._heatColor(count),
2251
+ "color: #000",
2252
+ "border-radius: 8px",
2253
+ "padding: 0 4px",
2254
+ "font-size: 9px",
2255
+ "font-weight: bold"
2256
+ ]);
2257
+ countBadge.textContent = String(count) + "\xD7";
2258
+ if (frameChanged || cacheChanged) {
2259
+ row.append(chevron, dot, className, eid, frameSpan, deltaSpan, countBadge);
2260
+ } else {
2261
+ row.append(chevron, dot, className, eid, frameSpan, countBadge);
2262
+ }
2263
+ } else {
2264
+ row.append(chevron, dot, className, eid, frameSpan);
2265
+ }
2266
+ const cacheStr = _UILayoutDebugger._formatCacheSnapshot(node.cacheAfterPass);
2267
+ const tooltipLines = [
2268
+ node.className + " #" + node.elementID,
2269
+ "viewIndex: " + node.viewIndex,
2270
+ node.frame ? `frame: ${node.frame.left.toFixed(1)}, ${node.frame.top.toFixed(1)} ${node.frame.width.toFixed(1)}\xD7${node.frame.height.toFixed(1)}` : "frame: (none)",
2271
+ "laid out: " + count + "\xD7",
2272
+ "intrinsic cache (post-pass): " + cacheStr
2273
+ ];
2274
+ if (step && frameChanged) {
2275
+ tooltipLines.push("frame \u0394: " + _UILayoutDebugger._formatFrameDiff(step.frameBefore, step.frameAfter));
2276
+ }
2277
+ if (step && cacheChanged) {
2278
+ tooltipLines.push("cache \u0394: " + _UILayoutDebugger._formatCacheDiff(step.cacheBefore, step.cacheAfter));
2279
+ }
2280
+ row.title = tooltipLines.join("\n");
2281
+ container.appendChild(row);
2282
+ if (!hasChildren) {
2283
+ return isActive ? row : null;
2284
+ }
2285
+ const childContainer = _UILayoutDebugger._el("div", [
2286
+ "display: " + (childrenExpanded ? "block" : "none")
2287
+ ]);
2288
+ container.appendChild(childContainer);
2289
+ let activeRow = isActive ? row : null;
2290
+ for (const child of node.children) {
2291
+ const result = _UILayoutDebugger._renderTreeNode(
2292
+ child,
2293
+ childContainer,
2294
+ countMap,
2295
+ activeViewIndex,
2296
+ expandState,
2297
+ stepMap
2298
+ );
2299
+ if (result) {
2300
+ activeRow = result;
2301
+ }
2302
+ }
2303
+ row.onclick = () => {
2304
+ childrenExpanded = !childrenExpanded;
2305
+ if (expandState !== null) {
2306
+ expandState.set(node.viewIndex, childrenExpanded);
2307
+ }
2308
+ childContainer.style.display = childrenExpanded ? "block" : "none";
2309
+ chevron.textContent = childrenExpanded ? "\u25BE" : "\u25B8";
2310
+ };
2311
+ return activeRow;
2312
+ }
2313
+ static _formatFrame(f) {
2314
+ if (!f) {
2315
+ return "(none)";
2316
+ }
2317
+ return `${f.left.toFixed(0)},${f.top.toFixed(0)} ${f.width.toFixed(0)}\xD7${f.height.toFixed(0)}`;
2318
+ }
2319
+ static _formatFrameDiff(before, after) {
2320
+ if (!before && !after) {
2321
+ return "(no frame data)";
2322
+ }
2323
+ const bounds = _UILayoutDebugger._boundsBasedDiff;
2324
+ const fmt = (f) => {
2325
+ if (!f) {
2326
+ return "(none)";
2327
+ }
2328
+ return bounds ? `${f.width.toFixed(0)}\xD7${f.height.toFixed(0)}` : _UILayoutDebugger._formatFrame(f);
2329
+ };
2330
+ if (!before) {
2331
+ return `\u2192 ${fmt(after)}`;
2332
+ }
2333
+ if (!after) {
2334
+ return `${fmt(before)} \u2192 (none)`;
2335
+ }
2336
+ const changed = bounds ? before.width !== after.width || before.height !== after.height : before.left !== after.left || before.top !== after.top || before.width !== after.width || before.height !== after.height;
2337
+ if (!changed) {
2338
+ return `= ${fmt(after)}`;
2339
+ }
2340
+ return `${fmt(before)} \u2192 ${fmt(after)}`;
2341
+ }
2342
+ static _formatCacheSnapshot(c) {
2343
+ if (!c) {
2344
+ return "(none)";
2345
+ }
2346
+ const lines = [];
2347
+ if (c.entryCount === 0) {
2348
+ lines.push("intrinsic: empty");
2349
+ } else {
2350
+ const intrinsicLines = Object.entries(c.entries).map(([key, val]) => {
2351
+ const match = key.match(/h_(\d+(?:\.\d+)?)__w_(\d+(?:\.\d+)?)/);
2352
+ const label = match ? match[1] !== "0" && match[2] !== "0" ? `h\u2264${match[1]} w\u2264${match[2]}` : match[2] !== "0" ? `w\u2264${match[2]}` : `h\u2264${match[1]}` : key;
2353
+ return ` ${label}: ${val.width.toFixed(0)}\xD7${val.height.toFixed(0)}`;
2354
+ });
2355
+ const prefix = c.isShared ? `shared(${c.sharedKey}) ` : "";
2356
+ lines.push(`${prefix}${c.entryCount} entr${c.entryCount === 1 ? "y" : "ies"}`);
2357
+ lines.push(...intrinsicLines);
2358
+ }
2359
+ lines.push(c.hasFrameCache ? `frameCache: ${_UILayoutDebugger._formatFrame(c.frameCache)}` : "frameCache: (empty)");
2360
+ lines.push(c.hasVirtualFrameCache ? `virtualFrameCache: ${_UILayoutDebugger._formatFrame(c.virtualFrameCache)}` : "virtualFrameCache: (empty)");
2361
+ return lines.join("\n");
2362
+ }
2363
+ static _formatCacheDiff(before, after) {
2364
+ var _a, _b, _c, _d, _e, _f, _g, _h;
2365
+ if (!before && !after) {
2366
+ return "(no cache data)";
2367
+ }
2368
+ const bCount = (_a = before == null ? void 0 : before.entryCount) != null ? _a : 0;
2369
+ const aCount = (_b = after == null ? void 0 : after.entryCount) != null ? _b : 0;
2370
+ if (bCount === 0 && aCount === 0 && !(before == null ? void 0 : before.hasFrameCache) && !(after == null ? void 0 : after.hasFrameCache) && !(before == null ? void 0 : before.hasVirtualFrameCache) && !(after == null ? void 0 : after.hasVirtualFrameCache)) {
2371
+ return "empty \u2192 empty";
2372
+ }
2373
+ const lines = [];
2374
+ const allKeys = /* @__PURE__ */ new Set([
2375
+ ...Object.keys((_c = before == null ? void 0 : before.entries) != null ? _c : {}),
2376
+ ...Object.keys((_d = after == null ? void 0 : after.entries) != null ? _d : {})
2377
+ ]);
2378
+ for (const key of allKeys) {
2379
+ const b = before == null ? void 0 : before.entries[key];
2380
+ const a = after == null ? void 0 : after.entries[key];
2381
+ const match = key.match(/h_(\d+(?:\.\d+)?)__w_(\d+(?:\.\d+)?)/);
2382
+ const label = match ? match[1] !== "0" && match[2] !== "0" ? `h\u2264${match[1]} w\u2264${match[2]}` : match[2] !== "0" ? `w\u2264${match[2]}` : `h\u2264${match[1]}` : key;
2383
+ if (!b) {
2384
+ lines.push(` + ${label}: ${a.width.toFixed(0)}\xD7${a.height.toFixed(0)}`);
2385
+ } else if (!a) {
2386
+ lines.push(` - ${label}: ${b.width.toFixed(0)}\xD7${b.height.toFixed(0)}`);
2387
+ } else if (b.width !== a.width || b.height !== a.height) {
2388
+ lines.push(` ~ ${label}: ${b.width.toFixed(0)}\xD7${b.height.toFixed(0)} \u2192 ${a.width.toFixed(0)}\xD7${a.height.toFixed(0)}`);
2389
+ } else {
2390
+ lines.push(` = ${label}: ${a.width.toFixed(0)}\xD7${a.height.toFixed(0)}`);
2391
+ }
2392
+ }
2393
+ const bHasF = (_e = before == null ? void 0 : before.hasFrameCache) != null ? _e : false;
2394
+ const aHasF = (_f = after == null ? void 0 : after.hasFrameCache) != null ? _f : false;
2395
+ if (bHasF || aHasF) {
2396
+ if (bHasF && !aHasF) {
2397
+ lines.push(` - frameCache: ${_UILayoutDebugger._formatFrame(before.frameCache)}`);
2398
+ } else if (!bHasF && aHasF) {
2399
+ lines.push(` + frameCache: ${_UILayoutDebugger._formatFrame(after.frameCache)}`);
2400
+ } else {
2401
+ const bf = before.frameCache, af = after.frameCache;
2402
+ const changed = !bf || !af || bf.top !== af.top || bf.left !== af.left || bf.width !== af.width || bf.height !== af.height;
2403
+ lines.push(changed ? ` ~ frameCache: ${_UILayoutDebugger._formatFrame(bf)} \u2192 ${_UILayoutDebugger._formatFrame(af)}` : ` = frameCache: ${_UILayoutDebugger._formatFrame(af)}`);
2404
+ }
2405
+ } else {
2406
+ lines.push(" = frameCache: (empty)");
2407
+ }
2408
+ const bHasV = (_g = before == null ? void 0 : before.hasVirtualFrameCache) != null ? _g : false;
2409
+ const aHasV = (_h = after == null ? void 0 : after.hasVirtualFrameCache) != null ? _h : false;
2410
+ if (bHasV || aHasV) {
2411
+ if (bHasV && !aHasV) {
2412
+ lines.push(` - virtualFrameCache: ${_UILayoutDebugger._formatFrame(before.virtualFrameCache)}`);
2413
+ } else if (!bHasV && aHasV) {
2414
+ lines.push(` + virtualFrameCache: ${_UILayoutDebugger._formatFrame(after.virtualFrameCache)}`);
2415
+ } else {
2416
+ const bv = before.virtualFrameCache, av = after.virtualFrameCache;
2417
+ const changed = !bv || !av || bv.top !== av.top || bv.left !== av.left || bv.width !== av.width || bv.height !== av.height;
2418
+ lines.push(changed ? ` ~ virtualFrameCache: ${_UILayoutDebugger._formatFrame(bv)} \u2192 ${_UILayoutDebugger._formatFrame(av)}` : ` = virtualFrameCache: ${_UILayoutDebugger._formatFrame(av)}`);
2419
+ }
2420
+ }
2421
+ const header = bCount === aCount ? `${aCount} entr${aCount === 1 ? "y" : "ies"}` : `${bCount} \u2192 ${aCount} entries`;
2422
+ return lines.length ? `${header}
2423
+ ${lines.join("\n")}` : header;
2424
+ }
2425
+ static _heatColor(count) {
2426
+ if (count === 0) {
2427
+ return "#4a4a5a";
2428
+ }
2429
+ if (count === 1) {
2430
+ return "#3a8";
2431
+ }
2432
+ if (count === 2) {
2433
+ return "#e80";
2434
+ }
2435
+ return "#d33";
2436
+ }
2437
+ static _el(tag, styles) {
2438
+ const el = document.createElement(tag);
2439
+ el.style.cssText = styles.join("; ");
2440
+ return el;
2441
+ }
2442
+ static _btnStyle(color) {
2443
+ return [
2444
+ `color: ${color}`,
2445
+ "background: rgba(255,255,255,0.06)",
2446
+ "border: 1px solid rgba(255,255,255,0.1)",
2447
+ "border-radius: 4px",
2448
+ "padding: 1px 6px",
2449
+ "font: inherit",
2450
+ "cursor: pointer",
2451
+ "flex-shrink: 0"
2452
+ ];
2453
+ }
2454
+ static _makeDraggable(panel) {
2455
+ let dragging = false;
2456
+ let startX = 0, startY = 0, origRight = 8, origTop = 8;
2457
+ panel.addEventListener("mousedown", (e) => {
2458
+ const target = e.target;
2459
+ if (!target.closest("[data-drag-handle]")) {
2460
+ return;
2461
+ }
2462
+ dragging = true;
2463
+ startX = e.clientX;
2464
+ startY = e.clientY;
2465
+ origRight = parseInt(panel.style.right, 10) || 8;
2466
+ origTop = parseInt(panel.style.top, 10) || 8;
2467
+ e.preventDefault();
2468
+ });
2469
+ document.addEventListener("mousemove", (e) => {
2470
+ if (!dragging) {
2471
+ return;
2472
+ }
2473
+ const dx = e.clientX - startX;
2474
+ const dy = e.clientY - startY;
2475
+ panel.style.right = origRight - dx + "px";
2476
+ panel.style.top = origTop + dy + "px";
2477
+ });
2478
+ document.addEventListener("mouseup", () => {
2479
+ dragging = false;
2480
+ });
2481
+ }
2482
+ };
2483
+ let UILayoutDebugger = _UILayoutDebugger;
2484
+ UILayoutDebugger._isEnabled = false;
2485
+ UILayoutDebugger._breakpointsEnabled = false;
2486
+ UILayoutDebugger._passIndex = 0;
2487
+ UILayoutDebugger._currentTrace = null;
2488
+ UILayoutDebugger._currentIteration = 0;
2489
+ UILayoutDebugger._pendingStep = null;
2490
+ UILayoutDebugger._pendingSubviewsBefore = /* @__PURE__ */ new Map();
2491
+ UILayoutDebugger._layoutCountsThisPass = /* @__PURE__ */ new Map();
2492
+ UILayoutDebugger._liveViewRegistry = /* @__PURE__ */ new Map();
2493
+ UILayoutDebugger._triggerMap = /* @__PURE__ */ new Map();
2494
+ UILayoutDebugger._noiseFramePrefixes = [
2495
+ "UILayoutDebugger",
2496
+ "UIView.setNeedsLayout",
2497
+ "setNeedsLayout",
2498
+ "UIView.didLayoutSubviews",
2499
+ "didLayoutSubviews",
2500
+ "UIView.layoutSubviews",
2501
+ "UIView.layoutIfNeeded",
2502
+ "layoutIfNeeded",
2503
+ "UIView.layoutViewsIfNeeded",
2504
+ "layoutViewsIfNeeded",
2505
+ "UIView._setCachedIntrinsicSize",
2506
+ "_setCachedIntrinsicSize"
2507
+ ];
2508
+ UILayoutDebugger._traces = [];
2509
+ UILayoutDebugger.maxStoredTraces = 20;
2510
+ UILayoutDebugger._replayTraceIndex = 0;
2511
+ UILayoutDebugger._replayStepIndex = -1;
2512
+ UILayoutDebugger._compareMode = false;
2513
+ UILayoutDebugger._frameFilter = "all";
2514
+ UILayoutDebugger._boundsBasedDiff = false;
2515
+ UILayoutDebugger._compareTraceIndex = 1;
2516
+ UILayoutDebugger._compareStepIndex = -1;
2517
+ UILayoutDebugger._sharedExpandState = /* @__PURE__ */ new Map();
2518
+ UILayoutDebugger._singleExpandState = /* @__PURE__ */ new Map();
2519
+ UILayoutDebugger._liveExpandState = /* @__PURE__ */ new Map();
2520
+ UILayoutDebugger._baseline = null;
2521
+ UILayoutDebugger._diffSnapshot = null;
2522
+ UILayoutDebugger._diffMode = false;
2523
+ UILayoutDebugger._liveInspectorMode = false;
2524
+ UILayoutDebugger._staleReportResult = null;
2525
+ UILayoutDebugger._staleReportMode = false;
2526
+ UILayoutDebugger._lastKnownRootView = null;
2527
+ UILayoutDebugger._overlayRoot = null;
2528
+ UILayoutDebugger._overlayVisible = true;
2529
+ UILayoutDebugger._helpMode = false;
2530
+ window.UILayoutDebugger = UILayoutDebugger;
2531
+ // Annotate the CommonJS export names for ESM import in node:
2532
+ 0 && (module.exports = {
2533
+ UILayoutDebugger
2534
+ });
2535
+ //# sourceMappingURL=UILayoutDebugger.js.map