ArtificialSenseDocs

Docs / Enterprise

SenseAuditTimelineComponent

Commercial, read-only audit-event timeline from @artificialsenselabs/enterprise. An immutable stream of events grouped by day with filtering, search, relative timestamps, rich metadata, virtualization, live streaming, burst clustering, anomaly detection, a density minimap, shareable filter URLs, and an opt-in AI summary slot.

EnterpriseELv2Pure rendererWCAG 2.2 AAZoneless
β–Ά Live demo β€” run nx serve showcase and open /enterprise/audit-timeline for interactive examples of every feature below.
import { SenseAuditTimelineComponent } from '@artificialsenselabs/enterprise';

Overview

Category: Compound / data display. Selector: sense-audit-timeline. The component is a pure renderer β€” no HTTP, no router, no global state. Every capability arrives as a typed input; every interaction leaves as a typed output. New behaviour is default-off, so adopting it never changes existing behaviour. The filtering, sorting, grouping, clustering, histogram, anomaly, and serialization logic are exported as independently-usable pure functions.

Feature catalogue

Filtering & search0.4

Severity, category, actor, date-range, and debounced full-text search β€” all ANDed, with an active-count badge and clear-all.

Virtualization0.1

Windowed rendering keeps the DOM bounded (<100 rows) for 50k+ event histories at 60fps.

Live streaming1.1

Push deltas in real time; merge + de-dupe by id, "N new events" pill, and pause-to-read.

Burst clustering1.2

Collapse hundreds of near-identical events into a single expandable count row.

Remediation actions1.3

Host-declared, scoped action buttons that emit typed events β€” the component never acts.

Export1.4

Injection-safe CSV + JSON of the filtered set, with an optional built-in download.

Resource deep-links1.5

Emit typed navigation intents for events that reference a resource β€” router stays in the host.

Selected-state1.6

Reflect the selected id back for a highlighted row + aria-current, even when virtualized.

Density histogram4.1

A brushable minimap over the full set that drives the date-range filter.

Anomaly detection4.2

Deterministic heuristics (error spikes, off-hours, repeated failures) flag noteworthy events.

Saved views & URLs4.3

Serialize the filter to a shareable, refresh-surviving URL; render named preset chips.

AI summarize4.4

Opt-in button emits the visible window for a host-owned AI summary β€” no network calls inside.

Content slots2.1

Override the row, actor, metadata, empty, loading, and summary rendering via templates.

Extensible taxonomy2.2

Re-label, recolour, reorder, or extend severities/categories via a DI token.

i18n & timezone2.3

Intl-driven locale formatting, RTL, and a fixed timeZone for compliance "all times UTC".

Rich metadata2.4

Type-aware value rendering (JSON, URLs, booleans) with copy-to-clipboard.

Installation

npm install @artificialsenselabs/enterprise

Peer dependency: @angular/core ^21.2.0. Standalone β€” import the component directly.

Quick start

import { Component, signal } from '@angular/core';
import { SenseAuditTimelineComponent } from '@artificialsenselabs/enterprise';
import type {
  SenseAuditEvent,
  SenseAuditEventSelectEvent,
  SenseAuditTimelineFilter,
} from '@artificialsenselabs/enterprise';

@Component({
  standalone: true,
  imports: [SenseAuditTimelineComponent],
  template: `
    <sense-audit-timeline
      [events]="events()"
      [loading]="loading()"
      colorScheme="auto"
      (eventSelect)="onSelect($event)"
      (filterChange)="onFilter($event)"
    />
  `,
})
export class AuditPanel {
  events = signal<readonly SenseAuditEvent[]>([]);
  loading = signal(false);

  onSelect(e: SenseAuditEventSelectEvent) { /* open detail drawer */ }
  onFilter(f: SenseAuditTimelineFilter)  { /* persist / server-fetch */ }
}

Architecture & principles

  • Purity is the product. No HTTP, router, or global singletons. Capabilities in as inputs; interactions out as typed outputs.
  • Default-off. Streaming, clustering, histogram, anomalies, export, summarize β€” all gated behind inputs that default to the original behaviour.
  • Pure logic layer. Filtering, clustering, histogram, anomaly, and serialization are exhaustively unit-tested pure functions.
  • A11y is not a phase. Roving tabindex, aria-live announcements, focus management, and axe-clean states ship with every feature.

Inputs 23

NameTypeDefaultDescription
eventsreadonly SenseAuditEvent[][]The full set of audit events to display. The component normalises, filters, sorts, and groups this array reactively β€” it never mutates the input. Defaults to `[]` (empty list).
filterSenseAuditTimelineFilter | nullnullExternal filter state. When provided, the component's internal filter UI is synchronised to match these values on first render and whenever they change. Emitting `filterChange` from the component does **not** push back into this input β€” the host controls authority. Defaults to `null` (no external filter; internal UI is the source of truth).
loadingbooleanfalseWhen `true` the component shows a loading indicator instead of the event list. Pass `true` while the initial data fetch is in flight and `false` once the first batch of events arrives. Defaults to `false`.
maxItemsnumber | nullnullHard cap on the number of events passed to the filter/sort/group pipeline. Useful as a safety rail when the host cannot server-side page the result set. Events beyond `maxItems` are silently discarded **before** filtering, so filter counts may be lower than the full dataset. Defaults to `null` (no cap).
i18nOverridesPartial<SenseAuditTimelineI18n> | nullnullPartial overrides for every user-visible string in the component. Only the keys you supply are replaced β€” all others fall back to the English defaults in . Defaults to `null` (no overrides; English strings used throughout).
styleClassstring | nullnullAdditional CSS class(es) to add to the host element alongside the BEM block class `sense-audit-timeline`. Useful for layout utility classes (`w-full`, `flex-1`, etc.) without breaking encapsulation. Defaults to `null` (no extra classes).
virtualScrollbooleantrueWhen true (default), the event list is windowed once it exceeds rows, keeping the DOM node count bounded for very large histories. Set to false to always render every row (useful for print/export views or when an outer container handles scrolling).
colorSchemeSenseAuditColorScheme'auto'Colour scheme the component applies to itself. - `'auto'` (default) β€” follows `prefers-color-scheme`. - `'light'` / `'dark'` β€” forced regardless of the OS setting. The scheme is reflected as a host class (`sense-audit-timeline--dark` / `sense-audit-timeline--light`) so host-app CSS can also key off it.
liveEventsreadonly SenseAuditEvent[] | nullnullDelta batch of incoming live events. Each time the host updates this signal the component merges and de-dupes by `id`: - **New ID** β€” prepended to the top of the list; the "N new events" pill appears, and the aria-live region announces the arrival. - **Existing ID (in live cache)** β€” the cached event is updated in-place; no duplicate is created and no new-count increment occurs. - **ID already in `events()`** β€” ignored (the base snapshot is authoritative). Set to `null` (default) to disable live mode. For RxJS streams, see the README section "Live streaming β€” RxJS adapter".
pausedbooleanfalseWhen `true`, incoming live events are buffered instead of merged, so an investigator can pause the feed while reading. Setting back to `false` flushes the buffer in one batch. Defaults to `false` (live mode active).
clusteringSenseAuditClusteringOptions | nullnullControls burst grouping. When `enabled` is `true`, consecutive events that share the same action/category (configurable via `by`) and fall within `windowMs` of each other are collapsed into a single cluster row showing a count badge. Clicking a cluster row expands it to show the individual events. Defaults to `null` (off) β€” the component renders each event individually, preserving backward-compatible behaviour.
actionsreadonly SenseAuditEventAction[][]Contextual remediation actions to render on matching event rows. Each action defines a label and optional scope filters (severity, category, predicate) β€” the component shows it only on events that pass all filters. Clicking an action button emits ; it does NOT emit `eventSelect`. The component is a pure renderer and never executes the action itself. Defaults to `[]` (no action buttons rendered).
selectedIdstring | nullnullID of the currently-selected event. When set, the matching event row receives a `sense-audit-timeline__item--selected` BEM modifier and an `aria-current="true"` attribute so both sighted and assistive-technology users can identify the selection. The component never selects an event itself β€” the host reflects back whichever event it received via `eventSelect`. Defaults to `null` (no row highlighted). Works with virtualization: when the selected row is scrolled off-screen the modifier is applied automatically as soon as the row re-enters the rendered viewport.
enableExportbooleanfalseWhen `true`, "Export CSV" and "Export JSON" buttons appear in the toolbar. Clicking either button: 1. Emits with the currently-filtered event set. 2. Triggers a client-side file download via a Blob so the user can save the file immediately β€” no server round-trip required. Set to `false` (default) to hide the buttons. The host can still listen to regardless of this setting when the buttons are shown.
showHistogrambooleanfalseWhen `true`, an optional compact density histogram strip is rendered above the toolbar. The histogram buckets the **full unfiltered** event set so it always reflects the complete data shape regardless of any active filters. - Click or drag across a region to apply a matching `from`/`to` date-range filter (emits `filterChange`). - Two accessible `<input type="range">` controls below the bars provide an equivalent keyboard interface. Defaults to `false` (feature is off; no DOM impact).
histogramBucketsunknownDEFAULT_HISTOGRAM_BUCKETSNumber of equal-width time buckets to split the event timeline into when rendering the histogram strip. Higher values give more granular brushing; lower values produce thicker, easier-to-click bars. Only meaningful when is `true`. Defaults to `30`.
anomalyRulesreadonly AnomalyRule[][]Heuristic anomaly-detection rules applied to the full unfiltered event set. When an event matches one or more rules a subtle `⚠` badge is rendered on that event row carrying a tooltip that explains the reason. Each rule is a plain object with a `kind` discriminant plus optional threshold/window overrides β€” no callbacks or class instances required. **All rules are default-off.** Passing `[]` (default) produces no markers and zero computational overhead.
savedViewsreadonly SavedView[][]Named filter presets rendered as quick-select chips above the filter bar. Clicking a chip applies its `filter` to the component's internal state and emits `filterChange` β€” exactly as if the user had set all the controls manually. The active chip (whose filter matches the current state) receives a visual highlight and `aria-pressed="true"`. Defaults to `[]` (no chips rendered; zero DOM impact). Use / to build the filter values from a URL query parameter for a refresh-surviving deep link.
enableSummarizebooleanfalseWhen `true`, a "Summarize" button is rendered in the toolbar. Clicking it emits with the **currently-visible** (filtered) event set and the active filter β€” the host calls the AI service and writes the result back via and . The component is a **pure renderer** β€” it never makes a network request. Defaults to `false` (button hidden; zero DOM impact).
summaryTextstring | nullnullThe AI-generated summary text produced by the host after receiving a event. Pass `null` (default) to show nothing or to clear a previous summary. Only meaningful when a slot is provided.
summaryLoadingbooleanfalseSet to `true` while the host is waiting for the AI service to respond. The value is forwarded to the slot context as `loading` so the host template can render a spinner. Defaults to `false`.
localestring | nullnullBCP 47 locale tag used for all date/time formatting within the component (absolute timestamps, relative times, and day-group headers). Defaults to `null`, which causes the component to use the Angular `LOCALE_ID` token value injected at module/component level (typically `'en-US'` unless the app provides a different value).
timeZonestring | nullnullIANA timezone identifier for all date/time display and day-boundary grouping (e.g. `'UTC'`, `'America/New_York'`, `'Asia/Tokyo'`). When `null` (default) the browser / Node local timezone is used, which matches the existing behavior and keeps the feature default-off. Setting `timeZone="UTC"` is recommended for compliance dashboards where "all times UTC" is a regulatory requirement.

Outputs 6

NamePayloadDescription
eventSelectSenseAuditEventSelectEventEmitted when the user clicks (or activates via keyboard) an event row. The payload contains the raw that was activated. Reflect the `event.id` back to the component via the `selectedId` input to highlight the selected row and set `aria-current="true"`.
filterChangeSenseAuditTimelineFilterEmitted whenever the internal filter state changes β€” severity chips, category chips, actor select, date-range inputs, or search field. The payload is the full snapshot so the host can persist, share, or route it without diffing individual keys.
actionInvokeSenseAuditActionInvokeEventEmitted when the user activates a remediation action button on an event. Does NOT fire alongside `eventSelect` β€” propagation is stopped.
eventExportSenseAuditExportEventEmitted when an export button is clicked. The payload contains the requested format and the **currently-filtered** event set (i.e. exactly what the user sees β€” not the full `events()` input). The host can use the payload to drive a custom download, post the data to an audit-log API, or open a preview modal. When is `true` the component also triggers a built-in Blob download automatically.
resourceSelectSenseAuditResourceSelectEventEmitted when the user activates the "View resource" affordance on an event that carries both `resourceId` and `resourceType`. The affordance is rendered automatically β€” no input is required. The component never calls the router itself (ADR-0032 Β§4): the host handles routing. Clicking the affordance does **not** also emit `eventSelect`.
summarizeRequestSenseAuditSummarizeRequestEmitted when the user clicks the "Summarize" button (requires `enableSummarize = true`). The payload contains the **currently-visible** (post-filter) event set and the active filter snapshot so the host has everything needed to build an AI prompt. **The component never calls any AI service.** The host owns the model interaction and writes the result back via the `summaryText` and `summaryLoading` inputs.

Content-projection slots

Pass a TemplateRef to override the built-in rendering. With no slot supplied, output is identical to the default.

Slot inputContext typeDescription
summaryTemplateSenseAuditSummaryContextOptional template rendered between the toolbar and the event list whenever the host has a summary to display (loading state, text, or both). The template receives a as context: ```html <ng-template #mySummary let-text let-loading="loading" let-filter="filter">
eventRowTemplateSenseAuditEventRowContextOptional custom template for an event row. When provided it completely replaces the built-in row UI for every event. The template receives a as context: ```html <ng-template #myRow let-event let-isSelected="isSelected" let-formattedTime="formattedTime"> <div [class.selected]="isSelected">{{ event.action }} β€” {{ formattedTime }}</div> </ng-template> <sense-audit-timeline [events]="events" [eventRowTemplate]="myRow" /> ``` When `null` (default) the built-in row template is used β€” **output is identical to before this feature was introduced**.
actorTemplateSenseAuditActorContextOptional custom template for the actor chip inside an event row. Replaces the built-in avatar + name + email block. The template receives a as context: ```html <ng-template #myActor let-actor let-initials="initials"> <strong>{{ actor.name }}</strong> ({{ initials }}) </ng-template> ``` Only takes effect when `eventRowTemplate` is **not** set (a fully custom row template owns its own actor rendering). Defaults to `null`.
metadataTemplateSenseAuditMetadataContextOptional custom template for the metadata content panel (the `<dl>` that appears after the user expands an event row). The expand-button affordance is still managed by the component; this slot only replaces the panel itself. The template receives a as context: ```html <ng-template #myMeta let-entries let-event="event"> <ul>@for (e of entries; track e.key) { <li>{{ e.key }}: {{ e.value }}</li> }</ul> </ng-template> ``` Only takes effect when `eventRowTemplate` is **not** set. Defaults to `null`.
emptyTemplateunknownOptional custom template shown in place of the built-in "No events found" paragraph when the visible event list is empty. No context is passed. Defaults to `null` (built-in empty-state message).
loadingTemplateunknownOptional custom template shown in place of the built-in "Loading…" paragraph when `loading` is `true`. No context is passed. Defaults to `null` (built-in loading message).

Types

All exported from @artificialsenselabs/enterprise.

TypeShapeDescription
SenseAuditEventobjectA single immutable audit entry: id, timestamp, action, severity, category + optional description, actor, metadata, resourceId, resourceType.
SenseAuditActorobjectPrincipal responsible for an event: id (required), name, email?, avatarUrl?.
SenseAuditEventSeverity'info' | 'warning' | 'error' | 'success'Closed union of urgency levels (extendable via the config token).
SenseAuditEventCategory'auth' | 'data' | 'system' | 'user' | 'config' | 'workflow'Closed union of functional domains (extendable via the config token).
SenseAuditTimelineFilterobjectFilter criteria (all ANDed): severity[], category[], actorId, from, to, search. Omit a key to leave it unrestricted.
SenseAuditColorScheme'light' | 'dark' | 'auto'Colour-scheme input values.
SenseAuditClusteringOptionsobjectBurst-grouping config: enabled, windowMs?, by? (action|category|actorId).
SenseAuditEventActionobjectA host-declared remediation action: id, label, severityScope?, categoryScope?, predicate?.
SenseAuditEventSelectEvent{ event }eventSelect payload.
SenseAuditActionInvokeEvent{ action, event }actionInvoke payload.
SenseAuditResourceSelectEvent{ resourceType, resourceId, event }resourceSelect payload.
SenseAuditExportEvent{ format, events }eventExport payload (format = csv | json).
SenseAuditExportFormat'csv' | 'json'Available serialisation formats.
SenseAuditSummarizeRequest4.4{ events, filter }summarizeRequest payload β€” visible events + active filter. May contain PII.
SenseAuditSummaryContext4.4{ $implicit, loading, filter }Context for the summaryTemplate slot.
SenseAuditEventRowContextobjectContext for eventRowTemplate.
SenseAuditActorContextobjectContext for actorTemplate.
SenseAuditMetadataContextobjectContext for metadataTemplate.
SenseAuditTimelineConfig2.2objectTaxonomy override value for the config token: severities?, categories?.
SenseAuditSeverityMeta2.2objectPer-severity display metadata: label?, color?, order?.
SenseAuditCategoryMeta2.2objectPer-category display metadata: label?, color?, icon?, order?.
SenseAuditTimelineI18nobjectAll user-visible strings (18 keys).
MetadataValueKind2.4unionDetected value kind: string | number | boolean | null | timestamp | url | object.
RichMetadataEntry2.4objectEnriched metadata entry: key, kind, display, copyText, href.
SavedView4.3{ id, label, filter }A named filter preset chip.
AnomalyRule4.2unionrepeated-failures | error-spike | off-hours | same-category-burst (each with threshold/window options).
AnomalyAnnotation4.2{ kind, reason }One anomaly flag attached to an event.
HistogramBucket4.1objectA single histogram time bucket: from, to, count, dominant severity.
DayGroup / ClusteredDayGroupobjectA calendar-day group of events (or clustered items).
EventCluster1.2objectA collapsed burst of consecutive similar events.
TimelineRowunionA flattened render row: header | event | cluster (virtualization).

DI tokens & constants

SymbolTypeDescription
SENSE_AUDIT_TIMELINE_CONFIGInjectionToken<SenseAuditTimelineConfig>Optional DI token to customise taxonomy labels/colors/order and add extended severity/category values.
SENSE_AUDIT_TIMELINE_DEFAULT_I18NSenseAuditTimelineI18nThe default English string set. Spread it as a base when building a locale file.
SENSE_AUDIT_SEVERITY_COLORSRecord<string, string>Severity hex values for programmatic use (charts). The component itself uses CSS variables.
SENSE_AUDIT_TIMELINE_PACKAGEstringThe package name constant.
DEFAULT_HISTOGRAM_BUCKETSnumber (30)Default histogram bucket count.
VIRTUALIZE_THRESHOLDnumber (100)Row count above which virtualization engages.
ALL_SEVERITIES / ALL_CATEGORIESreadonly arraysThe built-in severity and category union values.
CSV_COLUMNSreadonly CsvColumn[]Default CSV column set used by toCsv.
ACTOR_PALETTE_SIZEnumber (8)Number of deterministic avatar colours.

Pure utilities

Importable independently of the component β€” useful for server-side export, tests, or custom UIs.

FunctionReturnsDescription
filterEvents(events, filter)β†’ readonly SenseAuditEvent[]Applies a filter (all dimensions ANDed). Pure.
sortByNewest(events)β†’ readonly SenseAuditEvent[]Stable sort, newest first. Does not mutate the input.
groupByDay(events, locale?, tz?)β†’ DayGroup[]Buckets events into calendar-day groups, timezone-aware.
normalizeEvents(events)β†’ NormalizedEvent[]Pre-parses timestamps to epoch millis (_ts) for cheap downstream ops.
clusterEvents(events, options)β†’ TimelineItem[]Collapses consecutive similar events into clusters. Pure & unit-tested.
isCluster(item)β†’ item is EventClusterType guard distinguishing a cluster from an event.
applyClusteringToGroups(groups, opts, expandedIds)β†’ ClusteredDayGroup[]Applies clustering across day groups respecting expanded state.
toCsv(events, columns?)β†’ stringInjection-safe CSV (escapes , " newlines and leading = + - @). Flattens actor + metadata.
toJson(events)β†’ stringPretty-printed JSON of the event set; round-trips to equivalent events.
buildHistogram(events, buckets)β†’ HistogramBucket[]Buckets events into equal-width time bins with a dominant severity. Performant for 100k events.
detectAnomalies(events, rules)β†’ Map<id, AnomalyAnnotation[]>Runs heuristic rules; returns annotations keyed by event id. Pure & deterministic.
serializeFilter(filter)β†’ stringEncodes a filter to a compact URL-safe string. Empty filter β†’ "".
deserializeFilter(str)β†’ SenseAuditTimelineFilterDecodes the string back. serialize(deserialize(x)) === x for all shapes.
buildRichMetadataEntries(metadata)β†’ RichMetadataEntry[]Classifies each metadata value (kind, display, copyText, href).
formatTimestamp(value, locale?, tz?)β†’ stringAbsolute timestamp formatting via Intl.DateTimeFormat.
formatRelative(value, now?, locale?)β†’ stringRelative time ("2 h ago") via Intl.RelativeTimeFormat. Pure (injectable now).
toDate / toIsoString / toLocalIsoDay / toTzIsoDay→ Date | stringDate coercion + ISO day helpers (timezone-aware).
actorInitials(name) / actorColor(id)β†’ string | numberDeterministic avatar initials and palette index.
flattenGroups / flattenClusteredGroups / rowKey / computeWindow / buildPrefixSums / findRowAt / estimateRowHeightvirtualization helpersLow-level windowing utilities used by the built-in virtual scroller.

Filtering & search 0.4

The toolbar exposes severity chips, category chips, an actor dropdown (derived live from the data), a from/to date range, and a debounced free-text search across action, description, and actor name. All dimensions are ANDed.

An active-filter count badge and a "Clear all" button reset every dimension in one action. Every change emits the full filter snapshot via filterChange.

<sense-audit-timeline
  [events]="events()"
  [filter]="{ severity: ['error'], from: '2026-06-01' }"
  (filterChange)="lastFilter.set($event)"
/>

Virtualization 0.1

Above VIRTUALIZE_THRESHOLD (100) rows the list is windowed: only the visible slice plus a small overscan is in the DOM, keeping node count bounded for 50k+ events at 60fps. Variable row heights (expanded metadata) are measured after render and fed back so scroll offsets stay accurate.

Set virtualScroll="false" to always render every row β€” useful for print or full-page export views where an outer container scrolls.

Live streaming 1.1

Update the liveEvents signal with each delta batch. New ids prepend to the top; existing ids update in place (no duplicates); ids already present in the base events() snapshot are ignored. A "N new events ↑" pill appears when the user has scrolled away from the top, and arrivals are announced via the aria-live region.

Set paused="true" to buffer incoming events while an investigator reads; unpausing flushes the buffer in one batch. For RxJS sources, map your stream to a signal with toSignal.

<sense-audit-timeline [events]="snapshot()" [liveEvents]="delta()" [paused]="paused()" />

Burst clustering 1.2

When a batch emits hundreds of near-identical events, clustering collapses consecutive events sharing the same key(s) within windowMs into a single row with a count badge ("500Γ— Imported record Β· 2:01–2:03"). Expanding reveals the members. Off by default; total count and ordering are preserved when expanded.

<sense-audit-timeline
  [events]="events()"
  [clustering]="{ enabled: true, windowMs: 60000, by: ['action', 'category'] }"
/>

Remediation actions 1.3

Declare contextual actions ("Revoke session", "Roll back") scoped by severity, category, or a predicate. Matching events render small action buttons; invoking emits actionInvoke with the typed payload and stops propagation so eventSelect does not also fire. The component never performs the action itself.

actions: SenseAuditEventAction[] = [
  { id: 'revoke', label: 'Revoke session',
    severityScope: ['error'], categoryScope: ['auth'] },
];

Export (CSV / JSON) 1.4

Set enableExport to show CSV/JSON buttons. Clicking emits eventExport with the currently-filtered set and triggers a client-side Blob download. The pure toCsv / toJson utilities are injection-safe (escaping , " newlines and leading = + - @) and flatten actor + metadata; JSON round-trips to equivalent events.

<sense-audit-timeline [events]="events()" [enableExport]="true"
  (eventExport)="audit.log($event.format, $event.events)" />

Resource deep-links 1.5

Events carrying both resourceId and resourceType render a subtle "View resource" affordance. Activating it emits resourceSelect with the typed payload β€” the component never injects the Router (ADR-004); the host routes. It does not also emit eventSelect.

<sense-audit-timeline [events]="events()"
  (resourceSelect)="router.navigate(['/', $event.resourceType, $event.resourceId])" />

Selected-state reflection 1.6

Reflect the id you received from eventSelect back into selectedId to highlight the matching row with the --selected modifier and aria-current="true". Works with virtualization β€” the highlight reapplies as the row re-enters the rendered viewport.

<sense-audit-timeline [events]="events()" [selectedId]="selected()?.id ?? null"
  (eventSelect)="selected.set($event.event)" />

Density histogram 4.1

Set showHistogram to render a compact density strip over the full unfiltered set, coloured by dominant severity per bucket β€” a navigation map for long histories. Click or drag across bars to apply a matching from/to filter; two range inputs provide a keyboard-accessible equivalent. histogramBuckets tunes granularity. Renders performantly for 100k events.

<sense-audit-timeline [events]="events()" [showHistogram]="true" [histogramBuckets]="40" />

Anomaly detection 4.2

Pass deterministic, ML-free heuristic rules to flag noteworthy events with a ⚠ badge and an explanatory tooltip. Built-in rules: repeated-failures (same actor), error-spike, off-hours, and same-category-burst β€” each with threshold/window options. The pure detectAnomalies util is unit-tested per rule. Default-off: passing [] is zero-cost.

rules: AnomalyRule[] = [
  { kind: 'error-spike', threshold: 3, windowMs: 120000 },
  { kind: 'off-hours', businessStart: 8, businessEnd: 18, timeZone: 'UTC' },
];

Saved views & shareable URLs 4.3

serializeFilter / deserializeFilter encode a filter to a compact, URL-safe string and back β€” serialize(deserialize(x)) === x for every shape, including dates and arrays. Wire filterChange to the Router query params for a refresh-surviving deep link. Pass savedViews to render named preset chips; the chip matching current state is highlighted with aria-pressed.

presets: SavedView[] = [
  { id: 'errors', label: 'Errors',      filter: { severity: ['error'] } },
  { id: 'auth',   label: 'Auth events', filter: { category: ['auth'] } },
];
// share: router.navigate([], { queryParams: { view: serializeFilter(f) || null } })

AI summarize 4.4

Set enableSummarize to show a "Summarize" button. Clicking emits summarizeRequest with the currently-visible events and active filter β€” the component NEVER makes a network call. The host calls its model, then writes the result back via summaryText / summaryLoading, which the summaryTemplate slot renders. Raw audit data may contain PII: the host decides what to send.

async onSummarize(req: SenseAuditSummarizeRequest) {
  this.loading.set(true);
  const text = await myAiService.summarize(req.events, req.filter);
  this.text.set(text);
  this.loading.set(false);
}

Content-projection slots 2.1

Override any part of the rendering with a TemplateRef. eventRowTemplate replaces the entire row (receiving a rich context); actorTemplate and metadataTemplate target sub-regions; emptyTemplate and loadingTemplate replace the placeholder states; summaryTemplate hosts the AI summary. With no slot supplied, the default output is byte-for-byte unchanged.

<ng-template #row let-event let-time="formattedTime" let-sel="isSelected">
  <div [class.is-selected]="sel">{{ event.action }} Β· {{ time }}</div>
</ng-template>
<sense-audit-timeline [events]="events()" [eventRowTemplate]="row" />

Theming & tokens

Every colour, radius, and spacing value is a CSS custom property on :host. Override any token from host CSS β€” no ::ng-deep. A prefers-color-scheme: dark block remaps the defaults; force a scheme with the colorScheme input.

/* Host CSS β€” override any token, no ::ng-deep needed */
sense-audit-timeline.brand {
  --sat-color-surface: #ffffff;
  --sat-color-border: #e2e8f0;
  --sat-text-primary: #0f172a;
  --sat-severity-error: #dc2626;
  --sat-radius: 10px;
}

Token reference

TokenTypePurpose
--sat-color-surfacecolorBase background surface.
--sat-color-surface-hovercolorRow hover background.
--sat-color-selectedcolorSelected-row background.
--sat-color-bordercolorDefault border colour.
--sat-color-border-inputcolorForm-control border colour.
--sat-color-border-focuscolorActive chip / focus border.
--sat-color-focus-ringcolorKeyboard focus ring colour.
--sat-text-primarycolorPrimary text colour.
--sat-text-secondarycolorSecondary text colour.
--sat-text-mutedcolorMuted / tertiary text colour.
--sat-severity-infocolorInfo severity colour.
--sat-severity-warningcolorWarning severity colour.
--sat-severity-errorcolorError severity colour.
--sat-severity-successcolorSuccess severity colour.
--sat-severity-currentcolorActive-chip colour (set per chip via config).
--sat-actor-color-0 … -7colorEight-step palette for avatar fallbacks.
--sat-radius / --sat-radius-smlengthCorner radii.
--sat-gaplengthBase spacing unit.
--sat-viewport-max-heightlengthMax height of the scroll viewport.

i18n, locale & timezone

Override any of the 18 user-visible strings via i18nOverrides. Dates and relative times use Intl driven by the locale input (falling back to LOCALE_ID). Set timeZone="UTC" for compliance dashboards β€” it shifts both displayed times and day-grouping boundaries deterministically. Layout uses CSS logical properties, so dir="rtl" mirrors correctly.

<sense-audit-timeline
  [events]="events()"
  locale="de"
  timeZone="UTC"
  [i18nOverrides]="{ noEventsMessage: 'Keine Ereignisse gefunden.' }"
/>

Extensible taxonomy

Provide SENSE_AUDIT_TIMELINE_CONFIG to relabel, recolour, reorder, or add extended severity/category values without forking. Keys matching built-in unions override; new keys append as extra chips.

providers: [
  {
    provide: SENSE_AUDIT_TIMELINE_CONFIG,
    useValue: {
      categories: {
        auth:    { label: 'Authentication', order: 0 },
        billing: { label: 'Billing', order: 6 },          // extended value
      },
      severities: {
        error:    { label: 'Error', color: '#dc2626', order: 0 },
        critical: { label: 'Critical', color: '#7f1d1d', order: 1 }, // extended
      },
    } satisfies SenseAuditTimelineConfig,
  },
],

Rich metadata

The metadata panel detects each value's kind β€” string, number, boolean, null, ISO timestamp, URL, or object/array β€” and renders it appropriately. Objects pretty-print as collapsible JSON; URLs render as safe rel="noopener noreferrer" links (never raw HTML). Each value and the whole block have a keyboard-accessible copy button.

Accessibility

  • Roving tabindex across event rows β€” a single Tab enters the list.
  • aria-live="polite" region announces result counts after filtering and new live arrivals.
  • Selected rows expose aria-current="true"; filter chips expose aria-pressed.
  • Visible focus rings from theme tokens; default tokens meet AA contrast.
  • Verified with axe-core across default, loading, empty, dark, and filtered states.

Keyboard model

KeysAction
TabEnter the event list (single tab stop via roving tabindex).
↑ / ↓Move focus to the previous / next event.
Home / EndJump to the first / last event.
Enter / SpaceActivate the focused event (emits eventSelect).
Enter / Space (on expand)Expand / collapse the metadata panel.
Tab (within row)Reach action buttons, copy buttons, resource link.