/** * Derivation over recorded timeline events. * * The capture layers emit one event per phase boundary, which is the right * wire format and the wrong thing to read: a single update tick of one * component is five to ten rows whose durations the reader has to subtract by * hand. Everything needed to do better is already on the events — `groupId` * pairs a start with its end, `data.changed` names the reactive properties * that caused the update — so this module turns a flat event list into the * three shapes the panel and agents actually ask for (when the Changed values * layer was on, `data.changedDetail` adds old/new previews to each): * * - {@link TimelineSpan} — a start/end pair collapsed into one entry with a * duration ("`update` took 1.8ms"). * - {@link UpdateCycle} — one component's whole update tick, with the changed * property keys that triggered it ("`` updated because * `count` changed"). * - {@link ComponentRollup} — per-component totals over a recording ("this * component updated 340 times for 120ms"). * * Pure and dependency-free by design: it imports only the shared event types, * so the panel derives client-side over its 5000-event buffer and * `devframe/definition.ts` — which must stay framework-neutral — can derive * server-side over the ring buffer for the agent-facing summary. */ import type { ChangedValue, TimelineCause, TimelineEvent } from '../../types/timeline.js'; /** * A `@lit/task` run's span (`task:start` to `task:end`): on the lifecycle * layer with an element, but not a phase of any update tick. */ export declare const TASK_SPAN = "task"; /** How a `devtools` cause reads: what the panel did, then "in DevTools". */ export declare const devtoolsCauseType: (command: string) => string; /** * A start/end pair collapsed into one entry, or a point event that never had * an end to begin with. */ export interface TimelineSpan { layerId: string; /** * Stable across re-derivation of the same events, which a Lit `repeat()` * key and a selection comparison both need — the span objects themselves * are rebuilt whenever the event buffer changes. */ key: string; /** The event title with its `:start` / `:end` suffix removed. */ name: string; /** Pairing id as emitted; `undefined` for point events. */ groupId?: number | string; start: number; /** Absent while the span is still open, or if its end was never recorded. */ end?: number; /** `end - start`. Absent for open spans and for point events. */ duration?: number; /** Reactive property keys that caused this update, where the phase has them. */ changed?: string[]; /** Old/new value previews; present only when the Changed values layer was on. */ changedDetail?: ChangedValue[]; /** * What scheduled this update, copied from the start event. Recorded by the * runtime on `performUpdate` spans only; absent when unknown. */ cause?: TimelineCause; subtitle?: string; logType?: TimelineEvent['logType']; /** Set when the phase threw; from the end event. */ error?: SpanError; meta?: TimelineEvent['meta']; /** The events this span was built from, for the detail pane. */ events: TimelineEvent[]; } /** A recorded failure; `async` and `task` are set when it did not throw synchronously. */ export interface SpanError { name: string; message: string; /** Rejected promise or failed task rather than a synchronous throw. */ async?: true; /** The `@lit/task` field (or class) that failed. */ task?: string; } /** One component's complete update tick. */ export interface UpdateCycle { /** The `${elementId}:${tick}` groupId the phases share. */ key: string; elementId: number; tagName: string; source?: { file: string; line: number; column?: number; url?: string; }; /** Where this instance was written in a template, when the transform knew. */ callSite?: { file: string; line: number; column: number; }; start: number; /** The `performUpdate` span's duration — see {@link toUpdateCycles}. */ duration?: number; /** Union of the changed reactive property keys across the tick's phases. */ changed: string[]; /** Old/new previews per changed key; omitted when none were recorded. */ changedDetail?: ChangedValue[]; phases: TimelineSpan[]; /** The input event or update this tick followed, if any. See {@link attributeInput}. */ cause?: { layerId: string; type: string; detail?: string; time: number; }; /** Set when the runtime recorded another tick as the cause; its cycle `key`. */ causedBy?: { groupId: string; }; /** * The element's own previous tick scheduled this one: a property set in * `updated()` (or after `super.update()`) ran the whole cycle again with * no new outside cause. See {@link isSelfCaused}. */ selfCaused?: true; /** The first phase in this tick that threw, if any. */ error?: { phase: string; } & SpanError; /** * `shouldUpdate` returned false: `performUpdate` ran but `update` and the * phases after it did not, so this tick rendered nothing. */ skipped?: true; } /** Per-component totals over a set of update cycles. */ export interface ComponentRollup { tagName: string; /** Every instance of this component that updated, in first-seen order. */ elementIds: number[]; updates: number; /** * Ticks `shouldUpdate` vetoed. Counted apart from `updates`, which stays the * number of renders; omitted when none were seen. */ skipped?: number; /** Summed `performUpdate` durations; excludes cycles with no measured end. */ totalMs: number; maxMs: number; /** Changed-key frequency across the cycles, most frequent first. */ reasons: Array<{ key: string; count: number; }>; /** * Keys that changed to a new reference with equal content, by cycles, most * frequent first. Omitted when none were seen. */ redundantChanges?: Array<{ key: string; count: number; }>; /** Cycles in which a phase threw. */ errors: number; /** * Updates the component's own previous update scheduled (a double render); * omitted when none were seen. Counted within `updates` too. */ selfCaused?: number; source?: { file: string; line: number; column?: number; url?: string; }; /** * Where the first instance seen was written. Per instance, unlike `source`, * so it only stands for the component when `elementIds` holds one. */ callSite?: { file: string; line: number; column: number; }; } /** * A `performUpdate` span whose recorded cause is the same element's previous * tick: the element rendered twice for one outside change, typically because * `updated()` set a reactive property. A property set before the update * finished (in `willUpdate`, say) joins the running tick instead, so it never * shows up here. The causing tick need not still be in the buffer: its * `groupId` (`${elementId}:${tick}`) names the element on its own. */ export declare const isSelfCaused: (span: TimelineSpan) => boolean; /** * Collapses `:start` / `:end` event pairs into spans, passing anything else * through as a point span. * * Pairing is keyed on **layer + groupId + phase name**, never on `groupId` * alone: a whole update tick shares one groupId (`runtime/timeline/ * lifecycle.ts` builds it as `${elementId}:${tick}`), so matching on the id * would marry `willUpdate:start` to `performUpdate:end`. Each phase occurs at * most once per tick, which makes the triple unique. * * Unmatched halves survive rather than disappear — the buffer is a bounded * ring, so its oldest end and its newest start routinely have no partner, and * an update that is still running has a genuinely open span. */ export declare const toSpans: (events: readonly TimelineEvent[]) => TimelineSpan[]; /** * Groups lifecycle spans into update ticks. * * `duration` is taken from the `performUpdate` span alone, never from a sum of * the phases: `willUpdate` / `update` / `updated` all run *inside* * `performUpdate`, so summing counts the same milliseconds several times over. * * `changed` is the union across the tick's phases because only the phases that * receive a `PropertyValues` argument carry it — `performUpdate` takes none, * so the bracketing span is always silent about the reason. */ export declare const toUpdateCycles: (spans: readonly TimelineSpan[]) => UpdateCycle[]; /** * Totals update cycles per component tag. * * Grouped by tag rather than by instance because the question this answers is * "which *component* re-renders too much" — a list of 200 rows each updating * twice is one component's problem, not 200. The instance ids are kept so the * caller can still drill in. */ export declare const rollup: (cycles: readonly UpdateCycle[]) => ComponentRollup[]; /** * Attributes each update cycle to what caused it. * * This is what the mouse and keyboard layers are for. On their own they * duplicate what the browser's own event log already shows; joined to the * updates they precede they answer the question the raw layers only imply — * an update with a cause is an interaction, and an update without one is a * timer, a signal, or a stray `requestUpdate()`. * * A cause the runtime recorded on the tick's `performUpdate` span wins over * guessing: an `event` cause is resolved to that event in `events`, and an * `update` cause to the causing cycle in `cycles` (`cause` reads " * update" on the `lit-lifecycle` layer and `causedBy` names its key), and a * `task` cause to that run's `task:start` (" task"), and a `devtools` * cause reads as {@link devtoolsCauseType} names its command. A recorded cause whose target is not in the buffer falls back to the * heuristic: the nearest mouse or keyboard event before the cycle, within * `windowMs`. * * Returns a new array; cycles with no cause are returned as-is. */ export declare const attributeInput: (cycles: readonly UpdateCycle[], events: readonly TimelineEvent[], windowMs?: number) => UpdateCycle[]; //# sourceMappingURL=derive.d.ts.map