uicore-ts 1.1.310 → 1.1.315

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,393 @@
1
+ /**
2
+ * UILayoutDebugger
3
+ *
4
+ * A development-only utility for visualizing and debugging the UIView layout
5
+ * system. Disabled entirely at runtime unless explicitly enabled.
6
+ *
7
+ * FEATURES
8
+ * ─────────
9
+ *
10
+ * 1. Record-and-replay step debugger
11
+ * Every layout pass is recorded as an ordered sequence of steps. Each step
12
+ * captures the view that was laid out, its frame before and after, and the
13
+ * frames assigned to each of its subviews. After the pass completes you can
14
+ * scrub through the steps one at a time in the overlay UI, seeing exactly
15
+ * which view was processed at each point and how the frames changed.
16
+ *
17
+ * 2. Live breakpoint step-through
18
+ * When breakpoint mode is enabled (UILayoutDebugger.enableBreakpoints()), a
19
+ * special sentinel line is executed before each view's layoutIfNeeded() call.
20
+ * Put a browser debugger breakpoint on that line and the JS debugger will
21
+ * pause before every layout step — the full live JS stack and all object
22
+ * state are available at that point, exactly as with any other breakpoint.
23
+ *
24
+ * The sentinel line is:
25
+ * const breakpointOnThisLine = "Add a breakpoint on this line to step through layout."
26
+ * Search for that string in the Sources panel to find it quickly.
27
+ *
28
+ * 3. View-tree heat-map overlay
29
+ * A floating panel renders the full view hierarchy as an indented tree.
30
+ * Each node is coloured by how many times it was laid out in the most recent
31
+ * pass: untouched (grey), once (green), twice (orange), three-or-more (red).
32
+ * The node currently active in the step scrubber is highlighted in blue.
33
+ * Hovering a node shows its class, elementID, and frame.
34
+ *
35
+ * INTEGRATION POINTS IN UIView.ts
36
+ * ────────────────────────────────
37
+ * Add the following calls alongside the existing UILayoutCycleTracer calls:
38
+ *
39
+ * layoutViewsIfNeeded():
40
+ * window.UILayoutDebugger?.willBeginLayoutPass(UIView._viewsToLayout) // before the while loop
41
+ * window.UILayoutDebugger?.willBeginIteration(iteration) // inside the while loop, top
42
+ * window.UILayoutDebugger?.willLayoutView(view) // before view.layoutIfNeeded()
43
+ * [breakpoint sentinel — see below]
44
+ * view.layoutIfNeeded()
45
+ * window.UILayoutDebugger?.didLayoutView(view) // after view.layoutIfNeeded()
46
+ * window.UILayoutDebugger?.didFinishLayoutPass(iteration) // after the while loop
47
+ *
48
+ * layoutSubviews():
49
+ * window.UILayoutDebugger?.willSetSubviewFrames(this) // before the subview loop
50
+ * [existing subview loop]
51
+ * window.UILayoutDebugger?.didSetSubviewFrames(this) // after the subview loop
52
+ *
53
+ * The breakpoint sentinel block (inside layoutViewsIfNeeded, before
54
+ * view.layoutIfNeeded()):
55
+ *
56
+ * if (window.UILayoutDebugger?._shouldHitBreakpoint(view)) {
57
+ * const breakpointOnThisLine = "Add a breakpoint on this line to step through layout."
58
+ * }
59
+ *
60
+ * USAGE
61
+ * ─────
62
+ * UILayoutDebugger.enable() — record traces and show the overlay
63
+ * UILayoutDebugger.disable() — hide overlay and stop recording
64
+ * UILayoutDebugger.enableBreakpoints() — also pause at each layout step
65
+ * UILayoutDebugger.stepForward() — advance the replay scrubber by one
66
+ * UILayoutDebugger.stepBack() — retreat the replay scrubber by one
67
+ * UILayoutDebugger.goToStep(n) — jump to step n (0-based)
68
+ */
69
+ interface UILayoutDebugFrame {
70
+ top: number;
71
+ left: number;
72
+ width: number;
73
+ height: number;
74
+ }
75
+ /** A snapshot of a view's intrinsic size cache at a point in time. */
76
+ interface UILayoutDebugCacheSnapshot {
77
+ entryCount: number;
78
+ entries: Record<string, {
79
+ width: number;
80
+ height: number;
81
+ }>;
82
+ isShared: boolean;
83
+ sharedKey?: string;
84
+ /** Whether _frameCache was populated at snapshot time. */
85
+ hasFrameCache: boolean;
86
+ /** Snapshot of _frameCache if populated; null if absent. */
87
+ frameCache: UILayoutDebugFrame | null;
88
+ /** Whether _frameCacheForVirtualLayouting was populated at snapshot time. */
89
+ hasVirtualFrameCache: boolean;
90
+ /** Snapshot of _frameCacheForVirtualLayouting if populated; null if absent. */
91
+ virtualFrameCache: UILayoutDebugFrame | null;
92
+ }
93
+ /**
94
+ * Global UITextMeasurement cache sizes at a snapshot instant.
95
+ * These are not per-view — they're attached to the snapshot as a whole.
96
+ */
97
+ interface UILayoutDebugTextMeasurementSnapshot {
98
+ preparedCacheSize: number;
99
+ styleCacheSize: number;
100
+ }
101
+ interface UILayoutDebugSubviewRecord {
102
+ viewIndex: number;
103
+ className: string;
104
+ elementID: string;
105
+ frameBefore: UILayoutDebugFrame | null;
106
+ frameAfter: UILayoutDebugFrame | null;
107
+ }
108
+ /** What caused a view to enter the layout queue. */
109
+ interface UILayoutDebugTrigger {
110
+ callerFunction: string;
111
+ cleanStack: string;
112
+ }
113
+ /** One step = one call to layoutIfNeeded() on one view. */
114
+ interface UILayoutDebugStep {
115
+ stepIndex: number;
116
+ iteration: number;
117
+ viewIndex: number;
118
+ className: string;
119
+ elementID: string;
120
+ frameBefore: UILayoutDebugFrame | null;
121
+ frameAfter: UILayoutDebugFrame | null;
122
+ cacheBefore: UILayoutDebugCacheSnapshot | null;
123
+ cacheAfter: UILayoutDebugCacheSnapshot | null;
124
+ subviewRecords: UILayoutDebugSubviewRecord[];
125
+ trigger: UILayoutDebugTrigger | null;
126
+ }
127
+ interface UILayoutDebugTreeNode {
128
+ viewIndex: number;
129
+ className: string;
130
+ elementID: string;
131
+ depth: number;
132
+ frame: UILayoutDebugFrame | null;
133
+ layoutCount: number;
134
+ cacheAfterPass: UILayoutDebugCacheSnapshot | null;
135
+ children: UILayoutDebugTreeNode[];
136
+ }
137
+ interface UILayoutDebugTrace {
138
+ passIndex: number;
139
+ steps: UILayoutDebugStep[];
140
+ roots: UILayoutDebugTreeNode[];
141
+ totalIterations: number;
142
+ cacheChanges: UILayoutDebugCacheChangeEvent[];
143
+ }
144
+ /** A flat snapshot of every view's frame and intrinsic cache at a point in time. */
145
+ interface UILayoutDebugStateSnapshot {
146
+ label: string;
147
+ takenAt: number;
148
+ views: Map<number, UILayoutDebugViewState>;
149
+ /** Global UITextMeasurement cache sizes at the instant this snapshot was taken. */
150
+ textMeasurement: UILayoutDebugTextMeasurementSnapshot;
151
+ }
152
+ interface UILayoutDebugViewState {
153
+ viewIndex: number;
154
+ className: string;
155
+ elementID: string;
156
+ frame: UILayoutDebugFrame | null;
157
+ cache: UILayoutDebugCacheSnapshot | null;
158
+ }
159
+ type UILayoutDebugDiffKind = "appeared" | "disappeared" | "frame" | "cache" | "both" | "unchanged";
160
+ interface UILayoutDebugViewDiff {
161
+ kind: UILayoutDebugDiffKind;
162
+ viewIndex: number;
163
+ className: string;
164
+ elementID: string;
165
+ baselineFrame: UILayoutDebugFrame | null;
166
+ currentFrame: UILayoutDebugFrame | null;
167
+ baselineCache: UILayoutDebugCacheSnapshot | null;
168
+ currentCache: UILayoutDebugCacheSnapshot | null;
169
+ }
170
+ /**
171
+ * Fired when _getCachedIntrinsicSize returns a value that differs from the
172
+ * last value we observed for that view+cacheKey combination.
173
+ */
174
+ interface UILayoutDebugCacheChangeEvent {
175
+ eventIndex: number;
176
+ stepIndex: number;
177
+ iteration: number;
178
+ viewIndex: number;
179
+ className: string;
180
+ elementID: string;
181
+ cacheKey: string;
182
+ newValue: {
183
+ width: number;
184
+ height: number;
185
+ };
186
+ callerFunction: string;
187
+ cleanStack: string;
188
+ }
189
+ export declare class UILayoutDebugger {
190
+ static _isEnabled: boolean;
191
+ static _breakpointsEnabled: boolean;
192
+ static get isEnabled(): boolean;
193
+ static get breakpointsEnabled(): boolean;
194
+ static _passIndex: number;
195
+ static _currentTrace: UILayoutDebugTrace | null;
196
+ static _currentIteration: number;
197
+ static _pendingStep: UILayoutDebugStep | null;
198
+ static _pendingSubviewsBefore: Map<number, UILayoutDebugFrame | null>;
199
+ static _layoutCountsThisPass: Map<number, number>;
200
+ static _liveViewRegistry: Map<number, any>;
201
+ static _triggerMap: Map<number, UILayoutDebugTrigger>;
202
+ static _noiseFramePrefixes: string[];
203
+ static _traces: UILayoutDebugTrace[];
204
+ static readonly maxStoredTraces = 20;
205
+ static _replayTraceIndex: number;
206
+ static _replayStepIndex: number;
207
+ static _compareMode: boolean;
208
+ static _frameFilter: "all" | "changed" | "unchanged";
209
+ /**
210
+ * When true, frame comparisons ignore origin (x/y) and only consider size
211
+ * (width/height) — i.e. they diff bounds rather than frames.
212
+ * A position-only move does not trigger a layout recompute of content, so
213
+ * bounds mode surfaces only the changes that actually matter for sizing.
214
+ */
215
+ static _boundsBasedDiff: boolean;
216
+ static _compareTraceIndex: number;
217
+ static _compareStepIndex: number;
218
+ static _sharedExpandState: Map<number, boolean>;
219
+ static _singleExpandState: Map<number, boolean>;
220
+ static _liveExpandState: Map<number, boolean>;
221
+ static enable(): void;
222
+ static disable(): void;
223
+ /**
224
+ * Enable the breakpoint sentinel. Once enabled, _shouldHitBreakpoint()
225
+ * returns true before every layoutIfNeeded() call so the browser debugger
226
+ * can pause on the sentinel line in UIView.ts.
227
+ */
228
+ static enableBreakpoints(): void;
229
+ static disableBreakpoints(): void;
230
+ static stepForward(): void;
231
+ static stepBack(): void;
232
+ static goToStep(stepIndex: number): void;
233
+ static goToCompareStep(stepIndex: number): void;
234
+ static showTrace(traceIndex: number): void;
235
+ static showCompareTrace(traceIndex: number): void;
236
+ static get _currentReplayTrace(): UILayoutDebugTrace | null;
237
+ static get _currentCompareTrace(): UILayoutDebugTrace | null;
238
+ static willBeginLayoutPass(viewsToLayout: any[]): void;
239
+ static willBeginIteration(iteration: number): void;
240
+ /**
241
+ * Called from setNeedsLayout() each time a view is enqueued.
242
+ * Only the *first* enqueue per view per pass is recorded — that is the
243
+ * call that actually caused the view to enter the queue. Subsequent calls
244
+ * on the same view within the same pass are redundant and ignored.
245
+ */
246
+ static viewDidCallSetNeedsLayout(view: any): void;
247
+ /**
248
+ * Called from _setCachedIntrinsicSize() after the value is written.
249
+ * Every write is a change by definition, so no history comparison is needed.
250
+ *
251
+ * Call site in UIView.ts, at the end of _setCachedIntrinsicSize():
252
+ *
253
+ * window.UILayoutDebugger?.didSetCachedIntrinsicSize(this, cacheKey, size)
254
+ */
255
+ static didSetCachedIntrinsicSize(view: any, cacheKey: string, value: any): void;
256
+ static willLayoutView(view: any): void;
257
+ /**
258
+ * Called immediately after view.layoutIfNeeded(). Closes the pending step
259
+ * with the post-layout frame.
260
+ */
261
+ static didLayoutView(view: any): void;
262
+ static didFinishLayoutPass(iterationCount: number): void;
263
+ /** Discard all recorded traces and reset the replay state. */
264
+ static clearTraces(): void;
265
+ /** Capture the current view tree state as the baseline for future diffs. */
266
+ static captureBaseline(): void;
267
+ /** Capture the current state and diff it against the baseline. */
268
+ static captureAndDiff(): void;
269
+ static clearDiff(): void;
270
+ /**
271
+ * ☢ Stale Layout Report
272
+ *
273
+ * The single authoritative way to discover missing cache invalidations.
274
+ *
275
+ * What this does, in order:
276
+ * 1. Snapshots every view's frame and intrinsic cache right now (the
277
+ * "before" state — potentially stale/incorrect).
278
+ * 2. Calls performForcedSubtreeLayout() on the root view, which nukes all
279
+ * caches and forces a complete cold remeasure of the entire tree.
280
+ * 3. Snapshots again ("after" state — ground truth).
281
+ * 4. Diffs the two snapshots. Any view that changed between before and
282
+ * after had stale state that was never correctly invalidated.
283
+ * 5. For each changed view, cross-references the cache writes from the
284
+ * forced pass so you can see exactly which call path recomputed the
285
+ * correct value — working backwards from that to find the missing
286
+ * invalidation site.
287
+ *
288
+ * The result is shown in the ☢ Stale panel to the right of the pass
289
+ * inspector. Views corrected by the forced pass are also tinted amber in
290
+ * the pass inspector tree on the subsequent pass.
291
+ *
292
+ * Limitations:
293
+ * - Calls performForcedSubtreeLayout(), which is itself a nuclear option.
294
+ * The tree will be left in its corrected state — not the buggy state.
295
+ * Use this at the point where the bug is visible, not before.
296
+ * - The forced layout will generate a new trace (the remeasure pass).
297
+ * The ☢ panel cross-references its cache writes automatically.
298
+ * - Only intrinsic-size cache corrections are cross-referenced. Frame
299
+ * corrections are shown as diffs but do not yet have a write-stack.
300
+ */
301
+ static captureStaleLayoutReport(): void;
302
+ static clearStaleReport(): void;
303
+ static toggleLiveInspector(): void;
304
+ static _captureStateSnapshot(label: string): UILayoutDebugStateSnapshot | null;
305
+ /**
306
+ * Like _captureStateSnapshot but walks from an explicit root rather than
307
+ * _lastKnownRootView. Use this whenever the root must be pinned across a
308
+ * call that may update _lastKnownRootView (e.g. captureStaleLayoutReport,
309
+ * which drives a layout pass internally).
310
+ */
311
+ static _captureStateSnapshotFromRoot(rootView: any, label: string): UILayoutDebugStateSnapshot;
312
+ static _walkViewTree(view: any, out: Map<number, UILayoutDebugViewState>, visited: Set<number>): void;
313
+ static _diffSnapshots(baseline: UILayoutDebugStateSnapshot, current: UILayoutDebugStateSnapshot): UILayoutDebugViewDiff[];
314
+ static _framesEqual(a: UILayoutDebugFrame | null, b: UILayoutDebugFrame | null): boolean;
315
+ static _cachesEqual(a: UILayoutDebugCacheSnapshot | null, b: UILayoutDebugCacheSnapshot | null): boolean;
316
+ /**
317
+ * Called at the top of layoutSubviews(), before the subview frame loop.
318
+ * Nothing to do here — before-frames were already captured in willLayoutView().
319
+ */
320
+ static willSetSubviewFrames(_view: any): void;
321
+ /**
322
+ * Called at the bottom of layoutSubviews(), after the subview frame loop.
323
+ * Merges the before/after subview frames into the pending step.
324
+ */
325
+ static didSetSubviewFrames(view: any): void;
326
+ /**
327
+ * Returns true when breakpoints are enabled, causing the sentinel block
328
+ * in UIView.ts to execute. Put a browser debugger breakpoint on the
329
+ * `const breakpointOnThisLine` assignment inside that block.
330
+ */
331
+ static _shouldHitBreakpoint(_view: any): boolean;
332
+ static _cleanStack(rawStack: string): string;
333
+ static _extractCallerFunctionName(cleanStack: string): string;
334
+ static _captureFrame(view: any): UILayoutDebugFrame | null;
335
+ static _captureCache(view: any): UILayoutDebugCacheSnapshot | null;
336
+ static _buildTreeSnapshot(view: any, depth: number, visited?: Set<number>): UILayoutDebugTreeNode;
337
+ static _baseline: UILayoutDebugStateSnapshot | null;
338
+ static _diffSnapshot: UILayoutDebugStateSnapshot | null;
339
+ static _diffMode: boolean;
340
+ static _liveInspectorMode: boolean;
341
+ /** Result of the last captureStaleLayoutReport() run. */
342
+ static _staleReportResult: {
343
+ before: UILayoutDebugStateSnapshot;
344
+ after: UILayoutDebugStateSnapshot;
345
+ diffs: UILayoutDebugViewDiff[];
346
+ /** viewIndex → cacheChanges from the forced-layout pass, for cross-referencing */
347
+ forcedPassCacheChanges: Map<number, UILayoutDebugCacheChangeEvent[]>;
348
+ passIndex: number;
349
+ } | null;
350
+ /** Whether the stale report side-panel is open. */
351
+ static _staleReportMode: boolean;
352
+ static _lastKnownRootView: any;
353
+ /**
354
+ * Finds the first trace (chronologically) recorded after baselineTakenAt
355
+ * that contains a step for the given viewIndex. Returns {traceIndex, stepIndex}
356
+ * into _traces, or null if none found.
357
+ */
358
+ static _findCausingTrace(viewIndex: number, baselineTakenAt: number): {
359
+ traceIndex: number;
360
+ stepIndex: number;
361
+ passIndex: number;
362
+ } | null;
363
+ static _overlayRoot: HTMLElement | null;
364
+ static _overlayVisible: boolean;
365
+ static _helpMode: boolean;
366
+ static _ensureOverlay(): void;
367
+ static _removeOverlay(): void;
368
+ static _renderOverlay(): void;
369
+ static _renderPassColumn(traceIndex: number, stepIndex: number, onStepChange: (si: number) => void, onTraceChange: (ti: number) => void, expandState: Map<number, boolean> | null, registerTree: (el: HTMLElement) => void, getPeerTree: () => HTMLElement | null): HTMLElement;
370
+ static _renderStaleReportPanel(result: NonNullable<typeof UILayoutDebugger._staleReportResult>): HTMLElement;
371
+ static _renderHelpPanel(): HTMLElement;
372
+ static _renderLiveInspectorPanel(): HTMLElement;
373
+ static _renderLiveNode(view: any, container: HTMLElement, depth: number, visited: Set<number>, expandState: Map<number, boolean>): void;
374
+ static _renderDiffPanel(baseline: UILayoutDebugStateSnapshot, current: UILayoutDebugStateSnapshot, onNavigate: (viewIndex: number) => void, baselineTakenAt: number): HTMLElement;
375
+ static _renderStepDetail(activeStep: UILayoutDebugStep): HTMLElement;
376
+ static _subtreeHasTouched(node: UILayoutDebugTreeNode, countMap: Map<number, number>): boolean;
377
+ static _subtreeContains(node: UILayoutDebugTreeNode, viewIndex: number): boolean;
378
+ static _renderTreeNode(node: UILayoutDebugTreeNode, container: HTMLElement, countMap: Map<number, number>, activeViewIndex: number, expandState: Map<number, boolean> | null, stepMap: Map<number, UILayoutDebugStep>): HTMLElement | null;
379
+ static _formatFrame(f: UILayoutDebugFrame | null): string;
380
+ static _formatFrameDiff(before: UILayoutDebugFrame | null, after: UILayoutDebugFrame | null): string;
381
+ static _formatCacheSnapshot(c: UILayoutDebugCacheSnapshot | null): string;
382
+ static _formatCacheDiff(before: UILayoutDebugCacheSnapshot | null, after: UILayoutDebugCacheSnapshot | null): string;
383
+ static _heatColor(count: number): string;
384
+ static _el(tag: string, styles: string[]): HTMLElement;
385
+ static _btnStyle(color: string): string[];
386
+ static _makeDraggable(panel: HTMLElement): void;
387
+ }
388
+ declare global {
389
+ interface Window {
390
+ UILayoutDebugger?: typeof UILayoutDebugger;
391
+ }
392
+ }
393
+ export {};