Pure functions and constants for x-trace-history: schema, ring-buffer ops, JS record construction, dock filtering / preview formatting, dock CSS, activation predicate. Effects (atom mutation, hook installation, JS API, DOM mounting) live in recorder.cljs / x_trace_history.cljs.
Pure functions and constants for x-trace-history: schema, ring-buffer ops, JS record construction, dock filtering / preview formatting, dock CSS, activation predicate. Effects (atom mutation, hook installation, JS API, DOM mounting) live in recorder.cljs / x_trace_history.cljs.
(active-lanes filtered-records)Build the ordered lane list from a sequence of (already-filtered) records. Each lane is {:lane-id :tag :min-t}; lanes are ordered by first-appearance time so older lanes stay at the top as new ones emerge below them.
Build the ordered lane list from a sequence of (already-filtered)
records. Each lane is {:lane-id :tag :min-t}; lanes are ordered by
first-appearance time so older lanes stay at the top as new ones
emerge below them.All category keywords in stable display order.
All category keywords in stable display order.
(axis-mode? m)Predicate: is m one of the supported axis modes?
Predicate: is `m` one of the supported axis modes?
Two axis layouts the dock supports. :order is the default — uniform horizontal spacing by record index — and matches the use case (sequence + causality navigation) the tool is built around. :time keeps the linear-time mapping for users debugging timing or animation density.
Two axis layouts the dock supports. :order is the default — uniform horizontal spacing by record index — and matches the use case (sequence + causality navigation) the tool is built around. :time keeps the linear-time mapping for users debugging timing or animation density.
(bin-opacity count lane-max-count)Linear opacity from bin-opacity-floor (a single-record bin) to
1.0 (a bin at lane-max-count). One-record bins land at the low
end — but not below the contrast floor — so the heatmap still
reads as a continuous band including the sparse bins inside an
otherwise-dense lane. Bins above the lane's own max-count clamp
to 1.0 (defensive — shouldn't happen).
Linear opacity from `bin-opacity-floor` (a single-record bin) to 1.0 (a bin at `lane-max-count`). One-record bins land at the low end — but not below the contrast floor — so the heatmap still reads as a continuous band including the sparse bins inside an otherwise-dense lane. Bins above the lane's own max-count clamp to 1.0 (defensive — shouldn't happen).
(bin-records-by-x lane-records record-cx-fn)Bucket a lane's records into pixel-aligned bins for heatmap
rendering. Each record contributes to exactly one bin via
record-cx under the active axis mode.
record-cx-fn is a (^js r) → x function the caller threads from
the renderer (it already knows the index map, bounds, and plot
width). Keeps this fn pure — model.cljs has no DOM knowledge.
Returns a vector of bin maps sorted by :bin-i, each:
{:bin-i N
:records [r ...] (sorted by id ascending)
:dominant-cat :kw
:x-start px
:x-end px
:t-min ms
:t-max ms}
Bucket a lane's records into pixel-aligned bins for heatmap
rendering. Each record contributes to exactly one bin via
`record-cx` under the active axis mode.
`record-cx-fn` is a (^js r) → x function the caller threads from
the renderer (it already knows the index map, bounds, and plot
width). Keeps this fn pure — model.cljs has no DOM knowledge.
Returns a vector of bin maps sorted by `:bin-i`, each:
{:bin-i N
:records [r ...] (sorted by id ascending)
:dominant-cat :kw
:x-start px
:x-end px
:t-min ms
:t-max ms}(categorize-type type-str)Map a record-type string to its category keyword (:events, :state, :dom, :lifecycle), or :other if unrecognised.
Map a record-type string to its category keyword (:events, :state, :dom, :lifecycle), or :other if unrecognised.
Catppuccin-ish palette mapped from category keyword to hex. Matches the .cat-* classes used in the row body of earlier PRs so the visual language is consistent across UI views.
Catppuccin-ish palette mapped from category keyword to hex. Matches the .cat-* classes used in the row body of earlier PRs so the visual language is consistent across UI views.
(causality-empty-message)Hint string for the causality pane when no record is selected. The pane is only useful with a selection, so we explain that instead of showing a blank area.
Hint string for the causality pane when no record is selected. The pane is only useful with a selection, so we explain that instead of showing a blank area.
(causality-leaf-message)Hint shown above the lone node when the selected record has neither a cause nor any effects in the buffer — most commonly a lifecycle / dom-attribute record emitted by a component's construction path (no enclosing dispatch frame, so causeId is null). Explains why the tree is a single node and tells the user what kind of record to pick if they want to see a chain.
Hint shown above the lone node when the selected record has neither a cause nor any effects in the buffer — most commonly a lifecycle / dom-attribute record emitted by a component's construction path (no enclosing dispatch frame, so causeId is null). Explains why the tree is a single node and tells the user what kind of record to pick if they want to see a chain.
Hard cap on tree size the renderer will draw. Trees over this size show a notice instead, asking the user to narrow the filter or pick a leaf. 200 keeps the SVG dense-but-legible at the dock's typical 420 px width and is well clear of the 5000-record ring buffer cap.
Hard cap on tree size the renderer will draw. Trees over this size show a notice instead, asking the user to narrow the filter or pick a leaf. 200 keeps the SVG dense-but-legible at the dock's typical 420 px width and is well clear of the 5000-record ring buffer cap.
(causality-over-cap-message {:keys [node-count]})Hint string shown when the tree containing the selected record
exceeds causality-max-nodes. The build truncates DFS once the
cap is hit, so node-count is a lower bound rather than an exact
count for deep trees; the wording (≥ N nodes) reflects that.
Suggests picking a smaller leaf — the dock filter intentionally
does NOT apply to the causality tree (which would silently break
chains by hiding causes), so we don't suggest narrowing the
filter here.
Hint string shown when the tree containing the selected record exceeds `causality-max-nodes`. The build truncates DFS once the cap is hit, so `node-count` is a lower bound rather than an exact count for deep trees; the wording (`≥ N nodes`) reflects that. Suggests picking a smaller leaf — the dock filter intentionally does NOT apply to the causality tree (which would silently break chains by hiding causes), so we don't suggest narrowing the filter here.
(causality-root records record)Walk the causeId chain up from record and return the highest
ancestor still present in records. When a record's causeId points
to something evicted from the ring buffer (or never present, in an
imported partial trace), the walk stops there — that's the root we
render. Returns record itself when it has no cause.
Walk the causeId chain up from `record` and return the highest ancestor still present in `records`. When a record's causeId points to something evicted from the ring buffer (or never present, in an imported partial trace), the walk stops there — that's the root we render. Returns `record` itself when it has no cause.
(causality-tree records root-record)Nested causality tree rooted at root-record:
{:record :children :capped?}.
Children come from records whose causeId matches a parent's id.
The build is capped at causality-max-nodes in DFS order; when
truncated, the root carries :capped? true and the renderer shows
an over-cap notice. Pure: depends only on its arguments.
Nested causality tree rooted at `root-record`:
{:record :children :capped?}.
Children come from records whose causeId matches a parent's id.
The build is capped at `causality-max-nodes` in DFS order; when
truncated, the root carries :capped? true and the renderer shows
an over-cap notice. Pure: depends only on its arguments.Upper bound on how deep causality-root will walk up the cause
chain. Records have a single causeId so this should never matter,
but the defensive cap means a corrupted import with a circular
causeId can never hang the renderer.
Upper bound on how deep `causality-root` will walk up the cause chain. Records have a single causeId so this should never matter, but the defensive cap means a corrupted import with a circular causeId can never hang the renderer.
(cause-of records record)Return the cause record (record with id = record's causeId) from
records, or nil if the record has no cause or the cause has been
evicted from the ring buffer.
Return the cause record (record with id = `record`'s causeId) from `records`, or nil if the record has no cause or the cause has been evicted from the ring buffer.
(default-categories forensic?)Initial filter category set the dock applies on mount. In normal mode :state is excluded — instance-field writes are the noisiest record type (every component caches models, refs, etc.) and defaulting them off keeps the timeline focused on user-relevant events. Forensic mode includes everything so users can capture raw mutation history.
Initial filter category set the dock applies on mount. In normal mode :state is excluded — instance-field writes are the noisiest record type (every component caches models, refs, etc.) and defaulting them off keeps the timeline focused on user-relevant events. Forensic mode includes everything so users can capture raw mutation history.
(density-bin-tooltip-text first-record bin-count dominant-cat)Multi-line text for the heatmap-bin hover tooltip. Mirrors
tooltip-text's shape so users get the same information density.
Takes the bin's first record, its count, and its dominant
category as separate args (rather than a full bin map) — the UI
layer reads count + category from the rect's data attributes and
resolves the first-record from the recorder, which avoids
re-binning at hover time AND keeps the SVG markup lean.
Multi-line text for the heatmap-bin hover tooltip. Mirrors `tooltip-text`'s shape so users get the same information density. Takes the bin's first record, its count, and its dominant category as separate args (rather than a full bin map) — the UI layer reads count + category from the rect's data attributes and resolves the first-record from the recorder, which avoids re-binning at hover time AND keeps the SVG markup lean.
Width of one heatmap bin in CSS pixels. Records are bucketed by
floor(x / bin-width). 4 px is wide enough to be a visible band
at typical dock zoom and narrow enough to preserve temporal
resolution within a lane.
Width of one heatmap bin in CSS pixels. Records are bucketed by `floor(x / bin-width)`. 4 px is wide enough to be a visible band at typical dock zoom and narrow enough to preserve temporal resolution within a lane.
If any bin in a lane holds MORE than this many records, the lane renders as a heatmap band instead of individual dots. 3 records per 4 px (i.e. > one record per pixel) is the point at which dot overlap becomes visually confusing.
If any bin in a lane holds MORE than this many records, the lane renders as a heatmap band instead of individual dots. 3 records per 4 px (i.e. > one record per pixel) is the point at which dot overlap becomes visually confusing.
(dock-mode? m)Predicate: is m one of the supported dock modes?
Predicate: is `m` one of the supported dock modes?
View kinds the dock supports above the detail pane. :timeline is the SVG lane view (the default since PR 5); :causality is the tree view added in this PR. Dock-mode is orthogonal to axis-mode — only :timeline reads axis-mode at all.
View kinds the dock supports above the detail pane. :timeline is the SVG lane view (the default since PR 5); :causality is the tree view added in this PR. Dock-mode is orthogonal to axis-mode — only :timeline reads axis-mode at all.
Sentinel lane-id for records whose target was js/document (componentId is nil on the wire). Distinct from any numeric componentId.
Sentinel lane-id for records whose target was js/document (componentId is nil on the wire). Distinct from any numeric componentId.
(dominant-category records)Return the category keyword that appears most often in a sequence
of records. Ties between two all-categories entries are broken
by all-categories order so a bin with equal counts always
renders in the same colour across runs (e.g. an equal mix of
events + dom always picks :events).
Records whose type is unrecognised contribute to the :other
bucket. :other only wins when it STRICTLY beats every recognised
category — a tie with any all-categories entry resolves to that
entry, since recognised categories carry more semantic meaning
for visualization than the catch-all.
Returns nil for an empty seq.
Return the category keyword that appears most often in a sequence of records. Ties between two `all-categories` entries are broken by `all-categories` order so a bin with equal counts always renders in the same colour across runs (e.g. an equal mix of events + dom always picks :events). Records whose `type` is unrecognised contribute to the `:other` bucket. `:other` only wins when it STRICTLY beats every recognised category — a tie with any `all-categories` entry resolves to that entry, since recognised categories carry more semantic meaning for visualization than the catch-all. Returns nil for an empty seq.
(dot-color r)Fill colour for a record's timeline dot, derived from its category.
Fill colour for a record's timeline dot, derived from its category.
(download-filename d)Format a Date into the canonical export filename:
baredom-trace-YYYYMMDD-HHmmss.trace.json. Using local time
(matches the user's clock on disk) and zero-padded fields so
filenames sort chronologically.
Format a Date into the canonical export filename: `baredom-trace-YYYYMMDD-HHmmss.trace.json`. Using local time (matches the user's clock on disk) and zero-padded fields so filenames sort chronologically.
Cap on how many effect records the detail pane lists. Prevents the pane from blowing up when a single dispatch causes hundreds of downstream records (e.g. a render fan-out).
Cap on how many effect records the detail pane lists. Prevents the pane from blowing up when a single dispatch causes hundreds of downstream records (e.g. a render fan-out).
(effects-of records record)Return {:total N :records [recs ...]} for record, where :records is
the list of records whose causeId equals record's id, sorted by t
ascending and capped at effects-display-limit. :total is the
un-capped count, so the UI can show 'N of TOTAL' on truncation.
Return {:total N :records [recs ...]} for `record`, where :records is
the list of records whose causeId equals `record`'s id, sorted by `t`
ascending and capped at `effects-display-limit`. :total is the
un-capped count, so the UI can show 'N of TOTAL' on truncation.(enabled? window)True iff the URL search of the supplied window contains
?baredom-trace-history as a complete query parameter OR
window.BAREDOM_TRACE_HISTORY is true or the forensic-value
string "raw". Anchored URL match prevents look-alike params
from accidentally activating.
True iff the URL search of the supplied window contains `?baredom-trace-history` as a complete query parameter OR window.BAREDOM_TRACE_HISTORY is `true` or the forensic-value string "raw". Anchored URL match prevents look-alike params from accidentally activating.
Map of UI category keyword → set of record-type strings the dock groups under it. The dock filter uses categories rather than the 9 raw subtypes so the checkbox row stays scannable.
Map of UI category keyword → set of record-type strings the dock groups under it. The dock filter uses categories rather than the 9 raw subtypes so the checkbox row stays scannable.
(filter-components components referenced-ids)Pure: keep only entries from components whose id is in referenced-ids.
The recorder's index is monotonic across the page lifetime and
survives clear!, so a fresh capture still picks up stale entries
from prior interactions. The export filter trims the index to
what the captured records actually reference so an exported trace
carries the minimum useful side-info.
Pure: keep only entries from `components` whose id is in `referenced-ids`. The recorder's index is monotonic across the page lifetime and survives clear!, so a fresh capture still picks up stale entries from prior interactions. The export filter trims the index to what the captured records actually reference so an exported trace carries the minimum useful side-info.
(filter-records records spec)Return a CLJS vector of records matching the filter spec, sorted by t
ascending (chronological order). Sorting is necessary because PR 7
pushes a dispatch's record AFTER the records produced inside its
handlers — the ring buffer's insertion order no longer matches time
order. See record-matches? for spec keys.
Return a CLJS vector of records matching the filter spec, sorted by `t` ascending (chronological order). Sorting is necessary because PR 7 pushes a dispatch's record AFTER the records produced inside its handlers — the ring buffer's insertion order no longer matches time order. See `record-matches?` for spec keys.
(find-laid-node layout target-id)Return the laid-out node with id target-id from a layout map's
:nodes vector, or nil if not found. Used by the UI layer to drive
fit-to-view scroll after a render.
Return the laid-out node with id `target-id` from a layout map's :nodes vector, or nil if not found. Used by the UI layer to drive fit-to-view scroll after a render.
(find-record-by-id records id)Return the record with id id, or nil if not found / id non-numeric.
Delegates to Array.prototype.find for short-circuiting linear search.
Return the record with id `id`, or nil if not found / id non-numeric. Delegates to Array.prototype.find for short-circuiting linear search.
(fit-to-view-scroll cx cy viewport-w viewport-h tree-w tree-h)Compute {:scroll-left :scroll-top} that centres a node at (cx, cy)
within a viewport of viewport-w × viewport-h rendering a tree of
tree-w × tree-h. Clamps to [0, max-scroll] on each axis so we
never scroll past the edges. Pure — the actual scrollLeft / scrollTop
assignment lives in the UI layer.
Compute {:scroll-left :scroll-top} that centres a node at (cx, cy)
within a viewport of `viewport-w × viewport-h` rendering a tree of
`tree-w × tree-h`. Clamps to [0, max-scroll] on each axis so we
never scroll past the edges. Pure — the actual scrollLeft / scrollTop
assignment lives in the UI layer.(forensic? window)True iff trace-history is activated in forensic mode — ?baredom- trace-history=raw in the URL or window.BAREDOM_TRACE_HISTORY = "raw". Forensic mode disables the sample-rate cap and shows
:state events in the dock by default. Returns false when
trace-history is off or activated in normal mode.
True iff trace-history is activated in forensic mode — `?baredom- trace-history=raw` in the URL or `window.BAREDOM_TRACE_HISTORY = "raw"`. Forensic mode disables the sample-rate cap and shows :state events in the dock by default. Returns false when trace-history is off or activated in normal mode.
(format-timestamp t)Format a performance.now() timestamp (ms since page load) as a compact
relative string. <1s → 'Nms'; <1min → 'N.NNNs'; otherwise 'MmS.Ss'.
Non-numeric input (nil / string / undefined) → empty string. The guard
matters because PR 11 will accept imported records whose t field is
external data; without it .toFixed throws on nil.
Format a performance.now() timestamp (ms since page load) as a compact relative string. <1s → 'Nms'; <1min → 'N.NNNs'; otherwise 'MmS.Ss'. Non-numeric input (nil / string / undefined) → empty string. The guard matters because PR 11 will accept imported records whose `t` field is external data; without it `.toFixed` throws on nil.
(import-view? view)True when the dock's current view targets a specific imported trace. Imports are PR 11's loaded-from-disk record sets; structurally a third view-kind alongside :live and [:session N]. The dock chooses the record source for rendering by dispatching on these three predicates.
True when the dock's current view targets a specific imported trace. Imports are PR 11's loaded-from-disk record sets; structurally a third view-kind alongside :live and [:session N]. The dock chooses the record source for rendering by dispatching on these three predicates.
(index-from-x x n plot-width)Inverse of index-x. Return the record index (0-based) closest to
cursor x for an n-record list. Out-of-range x clamps to [0, n-1].
Returns nil for n=0 so callers can treat 'no record under cursor'
distinctly from 'first record'.
Inverse of `index-x`. Return the record index (0-based) closest to cursor x for an n-record list. Out-of-range x clamps to [0, n-1]. Returns nil for n=0 so callers can treat 'no record under cursor' distinctly from 'first record'.
(index-of-record records target-id)Linear-scan records (a CLJS vector or seq, in the dock's filtered
t-sorted order) and return the index of the record whose id matches
target-id. nil if not found / non-numeric id. Used by the order-
axis renderer to translate a record into its x-coordinate, and by
keyboard / scrubber paths (step-record) to find adjacent records.
Linear-scan `records` (a CLJS vector or seq, in the dock's filtered t-sorted order) and return the index of the record whose id matches `target-id`. nil if not found / non-numeric id. Used by the order- axis renderer to translate a record into its x-coordinate, and by keyboard / scrubber paths (step-record) to find adjacent records.
(index-x i n plot-width)Position the i-th record (0-based) of n total records inside the
padded plot. Uniform spacing — i=0 lands at the padded left edge,
i=n-1 lands at the padded right edge. A single-record plot
(n=1) places its dot at the centre of the inner plot rather than
collapsing to one edge, so it's discoverable. Padding matches
plot-padding-x, so dot radii survive at the extremes without
clipping just like the time-axis path.
Position the i-th record (0-based) of `n` total records inside the padded plot. Uniform spacing — i=0 lands at the padded left edge, i=n-1 lands at the padded right edge. A single-record plot (n=1) places its dot at the centre of the inner plot rather than collapsing to one edge, so it's discoverable. Padding matches `plot-padding-x`, so dot radii survive at the extremes without clipping just like the time-axis path.
(lane-id-of r)Return the lane-id a record belongs in: its componentId, or
document-lane for document-target events.
Return the lane-id a record belongs in: its componentId, or `document-lane` for document-target events.
(lane-is-dense? bins)True iff any bin in bins exceeds density-threshold. Empty input
is sparse by definition.
True iff any bin in `bins` exceeds `density-threshold`. Empty input is sparse by definition.
(lane-label {:keys [lane-id tag]})Human-readable lane label: 'tag #cid' or 'document'.
Human-readable lane label: 'tag #cid' or 'document'.
(lane-max-bin-count bins)Largest record count across any bin in the lane. Used to scale bin opacities relative to the lane's local peak so sparse-but- dense lanes still read as full-strength.
Largest record count across any bin in the lane. Used to scale bin opacities relative to the lane's local peak so sparse-but- dense lanes still read as full-strength.
(layout-tree tree)Pure layout: take a built causality tree and produce {:nodes [...] :edges [...] :width W :height H} in a coordinate system where the leftmost leaf's centre sits at x = causality-padding + node-w/2 and the root sits at y = causality-padding + node-h/2. Width / height span the full tree bounding box including padding on both ends.
Pure layout: take a built causality tree and produce
{:nodes [...] :edges [...] :width W :height H}
in a coordinate system where the leftmost leaf's centre sits at
x = causality-padding + node-w/2 and the root sits at y =
causality-padding + node-h/2. Width / height span the full tree
bounding box including padding on both ends.(live-view? view)True when the dock's current view is the live buffer (default).
True when the dock's current view is the live buffer (default).
(make-export records
components
sessions
{:keys [exported-at origin user-agent forensic?]})Build the JS-object envelope that gets JSON.stringified into a
.trace.json file. Pure: depends only on its arguments, no atom
reads, no Date.now() calls.
Inputs: records — JS array of record objects (the recorder's memoised view, oldest first). components — CLJS map componentId → {:tag :first-seen}. sessions — CLJS vector of session maps with the recorder's internal keys (:id :label :start-t :end-t :start-id :end-id). ctx — CLJS map of caller-supplied context: :exported-at (number) — Date.now() ms :origin (string) — window.location.href :user-agent (string) — navigator.userAgent :forensic? (boolean) — recorder/forensic-mode?
Returns a JS object suitable for JSON.stringify(envelope, null, 2).
The envelope's records field is reference-equal to the passed-in
records array — make-export does NOT copy. Callers passing the
recorder's memoised array (records-as-js-array) must treat the
resulting envelope as read-only; mutating envelope.records corrupts
the recorder's internal cache.
components is FILTERED to only those entries whose id appears in
records. The recorder's live index is monotonic for the page
lifetime and survives clear!; without the filter an exported
trace carries every component ever seen, including stale entries
from prior interactions the user explicitly cleared. The filter
ensures envelope.components is the minimum side-info needed to
resolve the captured records — no more, no less.
The full schema lives in docs/x-trace-history-schema.md and the
schemaVersion field gates importers.
Build the JS-object envelope that gets JSON.stringified into a
`.trace.json` file. Pure: depends only on its arguments, no atom
reads, no Date.now() calls.
Inputs:
records — JS array of record objects (the recorder's
memoised view, oldest first).
components — CLJS map componentId → {:tag :first-seen}.
sessions — CLJS vector of session maps with the recorder's
internal keys (:id :label :start-t :end-t :start-id
:end-id).
ctx — CLJS map of caller-supplied context:
:exported-at (number) — Date.now() ms
:origin (string) — window.location.href
:user-agent (string) — navigator.userAgent
:forensic? (boolean) — recorder/forensic-mode?
Returns a JS object suitable for `JSON.stringify(envelope, null, 2)`.
The envelope's `records` field is reference-equal to the passed-in
`records` array — make-export does NOT copy. Callers passing the
recorder's memoised array (`records-as-js-array`) must treat the
resulting envelope as read-only; mutating envelope.records corrupts
the recorder's internal cache.
`components` is FILTERED to only those entries whose id appears in
`records`. The recorder's live index is monotonic for the page
lifetime and survives `clear!`; without the filter an exported
trace carries every component ever seen, including stale entries
from prior interactions the user explicitly cleared. The filter
ensures `envelope.components` is the minimum side-info needed to
resolve the captured records — no more, no less.
The full schema lives in docs/x-trace-history-schema.md and the
`schemaVersion` field gates importers.(make-record {:keys [type tag component-id cause-id] :as payload} id t)Construct a JS-shape record from a hook payload, monotonic id, and a timestamp (performance.now() value).
Common payload keys: :type :tag :component-id :cause-id. :cause-id is the id of the synchronously-enclosing dispatchEvent call, or nil for records produced outside any dispatch (top-level, or async). Type-specific keys are documented in docs/x-trace-history-schema.md.
Uses string-keyed js-obj so Closure Advanced does not rename the schema field names — consumers read these properties from the wire.
Construct a JS-shape record from a hook payload, monotonic id, and a timestamp (performance.now() value). Common payload keys: :type :tag :component-id :cause-id. :cause-id is the id of the synchronously-enclosing dispatchEvent call, or nil for records produced outside any dispatch (top-level, or async). Type-specific keys are documented in docs/x-trace-history-schema.md. Uses string-keyed js-obj so Closure Advanced does not rename the schema field names — consumers read these properties from the wire.
(nearest-record records target-t)Return the record from records whose t is closest to target-t. nil
for empty input. Used by the scrubber to translate a cursor x back
into a record selection.
Return the record from `records` whose t is closest to `target-t`. nil for empty input. Used by the scrubber to translate a cursor x back into a record selection.
(parse-export json-str)Parse a JSON string into a validated envelope. Returns
{:ok ^js envelope} or {:error msg} — never throws. Wraps
validate-envelope so callers can hand it raw user input from
a drag-drop / file-picker / paste path without try/catch.
Empty string / whitespace-only / non-JSON input all produce structured errors. The recorder's import path uses this so the dock can surface 'why' to the user.
Parse a JSON string into a validated envelope. Returns
`{:ok ^js envelope}` or `{:error msg}` — never throws. Wraps
`validate-envelope` so callers can hand it raw user input from
a drag-drop / file-picker / paste path without try/catch.
Empty string / whitespace-only / non-JSON input all produce
structured errors. The recorder's import path uses this so the
dock can surface 'why' to the user.(payload-preview r)Produce a short one-line preview string for the row UI given a record. Pure: depends only on record fields.
Produce a short one-line preview string for the row UI given a record. Pure: depends only on record fields.
Horizontal padding (in CSS pixels) reserved on each side of the SVG
plot so dots at the temporal extremes (tmin / tmax) stay fully
inside the viewport instead of being half-clipped by the edges.
Sized to cover the largest dot radius the dock renders (dot-r-sel)
plus a small visual margin. Also bumps up the effective hit-target
for edge events, which a 0-padding linear mapping would render
inaccessible at the far left.
Horizontal padding (in CSS pixels) reserved on each side of the SVG plot so dots at the temporal extremes (tmin / tmax) stay fully inside the viewport instead of being half-clipped by the edges. Sized to cover the largest dot radius the dock renders (`dot-r-sel`) plus a small visual margin. Also bumps up the effective hit-target for edge events, which a 0-padding linear mapping would render inaccessible at the far left.
(push-record records record capacity)Push a record onto a ring-buffer (PersistentQueue). When the buffer is at or beyond capacity, drop the oldest record before pushing. Returns the new buffer.
Push a record onto a ring-buffer (PersistentQueue). When the buffer is at or beyond capacity, drop the oldest record before pushing. Returns the new buffer.
(record-matches? r
{:keys [tag categories search haystack-fn]
:or {haystack-fn searchable-haystack}})True iff r passes the filter spec.
spec keys:
:tag — string tag name; nil / blank / 'all' = match any
:categories — set of category keywords to include; nil = include all
:search — lowercase substring required in the record's
JSON-serialised form; nil / blank = include all
:haystack-fn — fn from ^js record → lowercase JSON string. Defaults
to searchable-haystack. The dock passes a
WeakMap-memoised version so search across the whole
ring buffer pays JSON.stringify once per record.
True iff `r` passes the filter spec.
spec keys:
:tag — string tag name; nil / blank / 'all' = match any
:categories — set of category keywords to include; nil = include all
:search — lowercase substring required in the record's
JSON-serialised form; nil / blank = include all
:haystack-fn — fn from ^js record → lowercase JSON string. Defaults
to `searchable-haystack`. The dock passes a
WeakMap-memoised version so search across the whole
ring buffer pays JSON.stringify once per record.(referenced-component-ids records)Pure: return the set of componentIds appearing in the records JS array. Records whose componentId is nil (document-target events like dispatch-document) don't contribute — there's no component to keep in the index for them.
Pure: return the set of componentIds appearing in the records JS array. Records whose componentId is nil (document-target events like dispatch-document) don't contribute — there's no component to keep in the index for them.
(searchable-haystack r)Pure: lowercase JSON-serialised view of r, suitable for substring
matching. Returns "" if JSON.stringify throws (e.g. cyclic detail
payload that slipped past the recorder's coercion).
This namespace caches nothing — recomputing on every search would
be wasteful for big buffers, so the dock memoises via WeakMap and
threads the memoised fn into record-matches? as :haystack-fn.
Pure: lowercase JSON-serialised view of `r`, suitable for substring matching. Returns "" if JSON.stringify throws (e.g. cyclic detail payload that slipped past the recorder's coercion). This namespace caches nothing — recomputing on every search would be wasteful for big buffers, so the dock memoises via WeakMap and threads the memoised fn into `record-matches?` as `:haystack-fn`.
(session-view? view)True when the dock's current view targets a specific session id.
True when the dock's current view targets a specific session id.
(set-ui-state! host k v)Write a transient UI-state value on host. Initializes the slot
lazily on first write.
Write a transient UI-state value on `host`. Initializes the slot lazily on first write.
(step-record filtered-records current-id dir)Return the next or previous filtered record relative to current-id.
dir is :next or :prev. Returns the first/last record when current-id
is nil or when current-id is no longer in the filtered list.
Stepping is by position in filtered-records — id-ordering is no
longer reliable since PR 7 dispatch records receive lower ids than
the children they cause (children push first, then the dispatch).
Callers pass the t-sorted vector that filter-records produces.
Return the next or previous filtered record relative to `current-id`. `dir` is :next or :prev. Returns the first/last record when current-id is nil or when current-id is no longer in the filtered list. Stepping is by position in `filtered-records` — id-ordering is no longer reliable since PR 7 dispatch records receive lower ids than the children they cause (children push first, then the dispatch). Callers pass the t-sorted vector that filter-records produces.
(tag-of el)Returns the lowercase tag name of a DOM element, or 'document' when the target is js/document or anything without a tagName.
Returns the lowercase tag name of a DOM element, or 'document' when the target is js/document or anything without a tagName.
(time-bounds records)Return {:tmin :tmax :span} from the records' t fields, or nil for an
empty input. :span is clamped to a 1ms minimum so single-record sets
still produce a usable scale.
Return {:tmin :tmax :span} from the records' t fields, or nil for an
empty input. `:span` is clamped to a 1ms minimum so single-record sets
still produce a usable scale.(time-from-x x {:keys [tmin span] :as bounds} plot-width)Inverse of time-x: map an x-coordinate within a plot of width
plot-width back to a time value. Returns 0 for nil bounds and
clamps x outside the padded plot range
[plot-padding-x, plot-width - plot-padding-x] into [tmin, tmax].
Inverse of `time-x`: map an x-coordinate within a plot of width `plot-width` back to a time value. Returns 0 for nil bounds and clamps x outside the padded plot range [plot-padding-x, plot-width - plot-padding-x] into [tmin, tmax].
(time-x t {:keys [tmin span]} plot-width)Map a time value to an x-coordinate inside a plot of width plot-width,
given the bounds map returned by time-bounds. Returns the padded
left edge (plot-padding-x) for nil bounds (no records) and clamps
t outside [tmin, tmax] into the padded plot range
[plot-padding-x, plot-width - plot-padding-x]. Padding prevents the
leftmost / rightmost dots from being clipped at the SVG edges; when
plot-width is too narrow to honour both paddings (degenerate case),
the plot collapses to a single x value at the centre.
Map a time value to an x-coordinate inside a plot of width `plot-width`, given the bounds map returned by `time-bounds`. Returns the padded left edge (`plot-padding-x`) for nil bounds (no records) and clamps t outside [tmin, tmax] into the padded plot range [plot-padding-x, plot-width - plot-padding-x]. Padding prevents the leftmost / rightmost dots from being clipped at the SVG edges; when plot-width is too narrow to honour both paddings (degenerate case), the plot collapses to a single x value at the centre.
(timeline-hint cnt-filtered cnt-total bounds lane-count)One-line description of the current plot extent for the dock hint area.
cnt-filtered is records visible after filter; cnt-total is full
buffer size.
One-line description of the current plot extent for the dock hint area. `cnt-filtered` is records visible after filter; `cnt-total` is full buffer size.
(tooltip-text r)Multi-line text shown when hovering a timeline dot.
Multi-line text shown when hovering a timeline dot.
(tree-max-depth tree)Maximum depth (0-based) of a built causality tree.
Maximum depth (0-based) of a built causality tree.
(tree-node-count tree)Total number of records in a built causality tree.
Total number of records in a built causality tree.
(tree-size-stats tree)Return {:node-count N :max-depth D} for a built causality tree. Used by the renderer to decide whether to draw or show a notice.
Return {:node-count N :max-depth D} for a built causality tree.
Used by the renderer to decide whether to draw or show a notice.(ui-state host k)Read a transient UI-state value from host by keyword key. Returns
nil when the slot or key is unset. Pure read; no mutation.
Read a transient UI-state value from `host` by keyword key. Returns nil when the slot or key is unset. Pure read; no mutation.
(validate-envelope env)Pure validator. Takes a parsed JS value and returns either
{:ok ^js envelope} for a well-formed .trace.json payload or
{:error msg} with a short user-readable reason on rejection.
The recorder surfaces the error string back through the JS API
and the dock hint area, so phrasing should read cleanly in both.
Validation rules — kept deliberately narrow so future schema additions stay backward-compatible:
Pure validator. Takes a parsed JS value and returns either
`{:ok ^js envelope}` for a well-formed `.trace.json` payload or
`{:error msg}` with a short user-readable reason on rejection.
The recorder surfaces the error string back through the JS API
and the dock hint area, so phrasing should read cleanly in both.
Validation rules — kept deliberately narrow so future schema
additions stay backward-compatible:
- envelope must be a JS object
- envelope.schemaVersion must equal the current schema-version
- envelope.records must be an array of objects, each with the
baseline fields (id, t, type) populated with the right types(view-id view)Extract the id from a session or import view, or nil for :live. Used for indexing the per-view selection map and for resolving a chip click back to a stored session/import.
Extract the id from a session or import view, or nil for :live. Used for indexing the per-view selection map and for resolving a chip click back to a stored session/import.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |