CONTROLLED EXPLORATION
Find the moment.
Follow the signal.
Brush a time range. Pan the details. Pin an observation across latency, errors and raw data. Distinguish missing values from zero, inspect supplied intervals, and recognise source freshness. Capture an exact baseline and compare another observation without losing its provenance. Select a recorded event at its exact time without inventing a measurement or a causal link. Pure geometry follows explicit Foldkit state.
Illustrative static telemetry. Independent Y scales stay fixed as you explore time. This example demonstrates composition, not a production monitoring system.
See how it works.
The source running this example. Expand a file to follow the Model, Messages and view.
data.ts
import { Schema } from 'effect';
export const Sample = Schema.Struct({
id: Schema.String,
time: Schema.Number,
latencyMs: Schema.Number,
errorPercent: Schema.Number,
});
export type Sample = typeof Sample.Type;
export const signalData: ReadonlyArray<Sample> = Array.from({ length: 120 }, (_, i) => ({
id: `signal-${String(i).padStart(3, '0')}`,
time: 1700000000000 + i * 1000,
latencyMs: 80 + ((i * 17) % 23) + (i >= 48 && i <= 54 ? 200 : 0),
errorPercent: (2 + (i % 5) + (i >= 50 && i <= 57 ? 40 : 0)) / 10,
}));
quality.ts
import { Schema } from 'effect';
import { defineTaggedUnion } from 'foldkit/schema';
import type { Sample } from './data';
import type { EventDataset } from './events';
export const Bounds = Schema.Struct({
lower: Schema.Number,
upper: Schema.Number,
label: Schema.String,
support: Schema.NullOr(Schema.Number),
});
export type Bounds = typeof Bounds.Type;
const valued = { value: Schema.Number, bounds: Schema.NullOr(Bounds) };
export const Reading = defineTaggedUnion({
Observed: valued,
Estimated: { ...valued, method: Schema.String },
Missing: { reason: Schema.String },
Invalid: { raw: Schema.String, reason: Schema.String },
});
export type Reading = typeof Reading.Type;
export const SignalRecord = Schema.Struct({
id: Schema.String,
time: Schema.Number,
latency: Reading,
errors: Reading,
});
export type SignalRecord = typeof SignalRecord.Type;
export const SourceSnapshot = Schema.Struct({
revision: Schema.String,
asOf: Schema.Number,
updatedAt: Schema.Number,
staleAfterMs: Schema.Number,
});
export type SourceSnapshot = typeof SourceSnapshot.Type;
const Style = Schema.Struct({
stroke: Schema.optional(Schema.String),
fill: Schema.optional(Schema.String),
dashPattern: Schema.optional(Schema.String),
opacity: Schema.optional(Schema.Number),
strokeWidth: Schema.optional(Schema.Number),
pointRadius: Schema.optional(Schema.Number),
symbol: Schema.optional(
Schema.Literals(['circle', 'cross', 'diamond', 'square', 'star', 'triangle', 'wye']),
),
});
export const SignalThreshold = Schema.Struct({
id: Schema.String,
metric: Schema.Literals(['latency', 'errors']),
value: Schema.Number,
label: Schema.String,
style: Style,
});
export type SignalThreshold = typeof SignalThreshold.Type;
export const Props = Schema.Struct({
events: Schema.optional(Schema.Unknown),
data: Schema.Array(SignalRecord),
snapshot: SourceSnapshot,
maxGapMs: Schema.Number,
thresholds: Schema.Array(SignalThreshold),
scenarioAsOf: Schema.Struct({ Fresh: Schema.Number, Stale: Schema.Number }),
});
export type Props = Omit<typeof Props.Type, 'events'> & { readonly events?: EventDataset };
export type ObservationProps = Omit<typeof Props.Type, 'events'>;
const nonempty = (text: string) => text.trim().length > 0;
const usableTime = (time: number) => Number.isFinite(time) && Math.abs(time) <= 8.64e15;
export const validSourceSnapshot = (s: SourceSnapshot): boolean =>
nonempty(s.revision) &&
usableTime(s.asOf) &&
usableTime(s.updatedAt) &&
s.updatedAt <= s.asOf &&
Number.isFinite(s.asOf - s.updatedAt) &&
Number.isFinite(s.staleAfterMs) &&
s.staleAfterMs >= 0;
export function sourceFreshness(snapshot: SourceSnapshot): 'Fresh' | 'Stale' {
if (!validSourceSnapshot(snapshot))
throw new RangeError(
'Provide usable source snapshot times and a non-negative freshness cutoff.',
);
return snapshot.asOf - snapshot.updatedAt <= snapshot.staleAfterMs ? 'Fresh' : 'Stale';
}
export const readingValue = (reading: Reading): number | null =>
Reading.match(reading, {
Observed: (r) => r.value,
Estimated: (r) => r.value,
Missing: () => null,
Invalid: () => null,
});
export const readingText = (reading: Reading): string =>
Reading.match(reading, {
Observed: (r) => String(r.value),
Estimated: (r) => String(r.value),
Missing: (r) => `Missing — ${r.reason}`,
Invalid: (r) => `Invalid (${r.raw}) — ${r.reason}`,
});
export const readingBounds = (reading: Reading): Bounds | null =>
Reading.match(reading, {
Observed: (r) => r.bounds,
Estimated: (r) => r.bounds,
Missing: () => null,
Invalid: () => null,
});
const validValue = (v: number, metric: 'latency' | 'errors') =>
Number.isFinite(v) && v >= 0 && (metric === 'latency' || v <= 100) && Number.isFinite(v * 1.1);
export function classifyReading(value: number | null, metric: 'latency' | 'errors'): Reading {
if (value === null) return Reading.Missing({ reason: 'No measurement supplied' });
return validValue(value, metric)
? Reading.Observed({ value, bounds: null })
: Reading.Invalid({
raw: String(value),
reason: 'Value is non-finite or outside the metric range',
});
}
export const observedRecords = (samples: ReadonlyArray<Sample>): ReadonlyArray<SignalRecord> =>
samples.map((d) => ({
id: d.id,
time: d.time,
latency: Reading.Observed({ value: d.latencyMs, bounds: null }),
errors: Reading.Observed({ value: d.errorPercent, bounds: null }),
}));
const validReading = (reading: Reading, metric: 'latency' | 'errors'): boolean =>
Reading.match(reading, {
Missing: (r) => nonempty(r.reason),
Invalid: (r) => nonempty(r.reason),
Observed: (r) => validValuedReading(r, metric),
Estimated: (r) => nonempty(r.method) && validValuedReading(r, metric),
});
function validValuedReading(
r: { readonly value: number; readonly bounds: Bounds | null },
metric: 'latency' | 'errors',
): boolean {
const b = r.bounds;
return (
validValue(r.value, metric) &&
(b === null ||
(validValue(b.lower, metric) &&
validValue(b.upper, metric) &&
b.lower <= r.value &&
r.value <= b.upper &&
nonempty(b.label) &&
(b.support === null || (Number.isSafeInteger(b.support) && b.support >= 0))))
);
}
/** Schema decoding precedes this semantic validation. No input is repaired or discarded. */
export function validSignalProps(props: ObservationProps): boolean {
if (
!validSourceSnapshot(props.snapshot) ||
!Number.isFinite(props.maxGapMs) ||
props.maxGapMs <= 0
)
return false;
const fresh = { ...props.snapshot, asOf: props.scenarioAsOf.Fresh },
stale = { ...props.snapshot, asOf: props.scenarioAsOf.Stale };
if (
!validSourceSnapshot(fresh) ||
!validSourceSnapshot(stale) ||
sourceFreshness(fresh) !== 'Fresh' ||
sourceFreshness(stale) !== 'Stale'
)
return false;
const ids = new Set<string>(),
times = new Set<number>(),
thresholdIds = new Set<string>();
for (const d of props.data) {
if (
!nonempty(d.id) ||
ids.has(d.id) ||
!usableTime(d.time) ||
times.has(d.time) ||
!validReading(d.latency, 'latency') ||
!validReading(d.errors, 'errors')
)
return false;
ids.add(d.id);
times.add(d.time);
}
for (const t of props.thresholds) {
const s = t.style;
if (
!nonempty(t.id) ||
thresholdIds.has(t.id) ||
!nonempty(t.label) ||
!validValue(t.value, t.metric) ||
(s.opacity !== undefined &&
(!Number.isFinite(s.opacity) || s.opacity < 0 || s.opacity > 1)) ||
[s.strokeWidth, s.pointRadius].some((v) => v !== undefined && (!Number.isFinite(v) || v < 0))
)
return false;
thresholdIds.add(t.id);
}
return true;
}
quality-data.ts
import { signalData } from './data';
import { eventDataset } from './events-data';
import { Reading, observedRecords } from './quality';
import type { Props, SignalRecord } from './quality';
const t0 = 1700000000000;
export const freshSnapshot = {
revision: 'signal-quality-v1',
updatedAt: t0 + 119000,
asOf: t0 + 124000,
staleAfterMs: 10000,
};
export const staleSnapshot = { ...freshSnapshot, asOf: t0 + 134001 };
export const qualityData: ReadonlyArray<SignalRecord> = observedRecords(signalData).flatMap(
(d, i) => {
if (i >= 70 && i <= 74) return [];
if (i >= 10 && i <= 14)
return [{ ...d, latency: Reading.Missing({ reason: 'collector unavailable' }) }];
if (i === 22)
return [
{
...d,
errors: Reading.Invalid({ raw: 'NaN', reason: 'collector emitted non-finite value' }),
},
];
if (i === 40)
return [
{
...d,
latency: Reading.Observed({ value: 0, bounds: null }),
errors: Reading.Observed({ value: 0, bounds: null }),
},
];
if (i >= 30 && i <= 39) {
const base = signalData[i];
if (!base) throw new RangeError('Missing quality fixture');
const label = 'Illustrative supplied range',
method = 'illustrative interpolation';
return [
{
...d,
latency: Reading.Estimated({
value: base.latencyMs,
bounds: {
lower: base.latencyMs - 8,
upper: base.latencyMs + 8,
label,
support: i === 35 ? 0 : 3,
},
method,
}),
errors: Reading.Estimated({
value: base.errorPercent,
bounds: {
lower: Math.max(0, base.errorPercent - 0.1),
upper: Math.min(100, base.errorPercent + 0.1),
label,
support: 3,
},
method,
}),
},
];
}
return [d];
},
);
export const qualityProps: Props = {
events: eventDataset,
data: qualityData,
snapshot: freshSnapshot,
maxGapMs: 1500,
thresholds: [
{
id: 'latency-reference',
metric: 'latency',
value: 180,
label: 'Latency reference',
style: { dashPattern: '2 4' },
},
{
id: 'errors-reference',
metric: 'errors',
value: 2,
label: 'Error reference',
style: { dashPattern: '2 4' },
},
],
scenarioAsOf: { Fresh: freshSnapshot.asOf, Stale: staleSnapshot.asOf },
};
quality-view.ts
import { resolveSeriesStyle } from '@opsydyn/foldkit-viz/chart/theme';
import { lineSeries, pointSeries } from '@opsydyn/foldkit-viz/foldkit/cartesian';
import { line } from '@opsydyn/foldkit-viz/shape/line';
import type { Html, HtmlBuilder } from 'foldkit/html';
import { exampleTheme } from '#example/frame';
import type { deriveSignalChart } from './derive';
import type { Message } from './message';
import type { ReadyModel, ChartRole } from './model';
import { Reading, readingBounds } from './quality';
export const qualityDetails = (reading: Reading, unit: string): string => {
const detail = Reading.match(reading, {
Observed: () => 'Observed',
Estimated: (r) => `Estimated · ${r.method}`,
Missing: () => 'Missing',
Invalid: () => 'Invalid',
});
const b = readingBounds(reading);
return b === null
? detail
: `${detail} · caller-supplied interval “${b.label}”: ${b.lower} → ${b.upper} ${unit} · support: ${b.support === null ? 'unspecified' : b.support}`;
};
export const qualityLegend = (h: HtmlBuilder<Message>): Html =>
h.ul(
[h.Class('signal-quality-legend'), h.AriaLabel('Signal quality legend')],
[
h.li([], ['━ Observed · solid']),
h.li([], ['◇ Estimated · dashed / hollow diamond']),
h.li([], ['M Missing · no measured value']),
h.li([], ['! Invalid · raw diagnostic retained']),
h.li([], ['G Gap · no records']),
h.li([], ['▱ caller-supplied interval · no statistical inference']),
],
);
export const qualityLayers = (
model: ReadyModel,
role: ChartRole,
chart: ReturnType<typeof deriveSignalChart>,
h: HtmlBuilder<Message>,
selectedIntervalKeys: ReadonlySet<string>,
): ReadonlyArray<Html> => {
const { plot } = chart.geometry.layout,
layout = chart.geometry.layout;
const colour =
role === 'errors'
? 'var(--chart-series-b, var(--coral))'
: 'var(--chart-series-a, var(--blue))';
const style = resolveSeriesStyle(
exampleTheme,
{ stroke: colour, fill: colour, strokeWidth: 2.5 },
{},
{},
);
const layers: Html[] = [];
for (const band of chart.bands) {
if (band.path !== null)
layers.push(
h.path(
[
h.Class('signal-band'),
h.D(band.path),
h.Fill(colour),
h.FillOpacity('0.12'),
h.Stroke('none'),
],
[],
),
);
const equal = band.points.every((p) => p.lowerY === p.upperY);
if (band.points.length === 1 || equal) {
for (const p of band.points) {
// Selected intervals are owned by the comparison layer; bands remain intact.
if (selectedIntervalKeys.has(p.key)) continue;
layers.push(
h.g(
[h.Class('signal-interval-mark'), h.AriaLabel(`Supplied interval for ${p.key}`)],
[
h.line(
[
h.X1(String(p.x)),
h.X2(String(p.x)),
h.Y1(String(p.lowerY)),
h.Y2(String(p.upperY)),
h.Stroke(colour),
],
[],
),
...[...new Set([p.lowerY, p.upperY])].map((y) =>
h.line(
[
h.X1(String(p.x - 4)),
h.X2(String(p.x + 4)),
h.Y1(String(y)),
h.Y2(String(y)),
h.Stroke(colour),
],
[],
),
),
],
),
);
}
}
for (const side of ['lowerY', 'upperY'] as const) {
const path = line(band.points.map((p) => [p.x, p[side]] as const));
if (path)
layers.push(
h.path(
[
h.D(path),
h.Fill('none'),
h.Stroke(colour),
h.StrokeWidth('1'),
h.StrokeDasharray('1 3'),
],
[],
),
);
}
}
for (const run of chart.runs) {
if (run.path)
layers.push(
lineSeries(h, {
path: run.path,
style: { ...style, dashPattern: run.quality === 'Estimated' ? '7 4' : '' },
}),
);
const endpoints = new Set(
run.records.length === 1
? run.records.map((d) => d.id)
: run.quality === 'Estimated'
? [run.records[0]?.id, run.records.at(-1)?.id]
: [],
);
layers.push(
pointSeries(h, {
points: chart.geometry.points.filter((p) => endpoints.has(p.key)),
styleFor: () => ({
...style,
pointRadius: 3,
symbol: run.quality === 'Estimated' ? 'diamond' : 'circle',
fill: run.quality === 'Estimated' ? 'var(--surface)' : colour,
}),
labelFor: (d) => `${d.id}, ${run.quality}`,
activeKey: model.inspection.key,
}),
);
}
for (const threshold of chart.thresholds) {
const defaults = resolveSeriesStyle(
exampleTheme,
{ stroke: 'var(--muted)', strokeWidth: 1, dashPattern: '2 4' },
{},
{},
);
const t = {
stroke: threshold.style.stroke ?? defaults.stroke,
fill: threshold.style.fill ?? defaults.fill,
opacity: threshold.style.opacity ?? defaults.opacity,
strokeWidth: threshold.style.strokeWidth ?? defaults.strokeWidth,
pointRadius: threshold.style.pointRadius ?? defaults.pointRadius,
symbol: threshold.style.symbol ?? defaults.symbol,
dashPattern: threshold.style.dashPattern ?? defaults.dashPattern,
};
const y = layout.y(threshold.value);
layers.push(
h.line(
[
h.DataAttribute('threshold-id', threshold.id),
h.AriaLabel(`${threshold.label}: ${threshold.value} ${role === 'errors' ? '%' : 'ms'}`),
h.X1(String(plot.left)),
h.X2(String(plot.right)),
h.Y1(String(y)),
h.Y2(String(y)),
h.Stroke(t.stroke),
h.StrokeWidth(String(t.strokeWidth)),
h.StrokeDasharray(t.dashPattern),
h.Opacity(String(t.opacity)),
],
[],
),
);
}
const lane: Html[] = [];
for (const span of [
...chart.qualitySpans,
...chart.gaps.map((g) => ({ ...g, status: 'Gap' as const, keys: [] })),
]) {
if (span.end < layout.xDomain[0] || span.start > layout.xDomain[1]) continue;
const x = Math.max(plot.left, layout.x(span.start)),
end = Math.min(plot.right, layout.x(span.end)),
y = plot.bottom + 26;
const label = span.status === 'Missing' ? 'M' : span.status === 'Invalid' ? '!' : 'G';
lane.push(
h.g(
[
h.AriaLabel(
`${span.status}: ${span.keys.length > 0 ? span.keys.join(', ') : `${span.end - span.start} ms between records`}`,
),
h.DataAttribute('quality-status', span.status),
],
[
h.line(
[
h.X1(String(x)),
h.X2(String(Math.max(x + 2, end))),
h.Y1(String(y + 3)),
h.Y2(String(y + 3)),
h.Stroke('var(--muted)'),
h.StrokeWidth('2'),
h.StrokeDasharray(
span.status === 'Missing' ? '2 2' : span.status === 'Gap' ? '1 4' : '',
),
],
[],
),
h.text(
[
h.X(String(Math.min(x + 2, plot.right - 8))),
h.Y(String(y + 15)),
h.FontSize('10'),
h.Fill('var(--text)'),
],
[label],
),
],
),
);
}
return [
h.g([h.Attribute('clip-path', `url(#signal-quality-clip-${role})`)], layers),
h.g([h.Class('signal-quality-lane')], lane),
];
};
baseline.ts
import { defineTaggedUnion } from 'foldkit/schema';
import { Reading, SignalRecord, SourceSnapshot } from './quality';
export const Baseline = defineTaggedUnion({
None: {},
Captured: { record: SignalRecord, snapshot: SourceSnapshot },
});
export type Baseline = typeof Baseline.Type;
const copyReading = (reading: Reading): Reading =>
Reading.match(reading, {
Observed: (r) => Reading.Observed({ ...r, bounds: r.bounds === null ? null : { ...r.bounds } }),
Estimated: (r) =>
Reading.Estimated({ ...r, bounds: r.bounds === null ? null : { ...r.bounds } }),
Missing: (r) => Reading.Missing({ reason: r.reason }),
Invalid: (r) => Reading.Invalid({ raw: r.raw, reason: r.reason }),
});
/** Capture model-owned evidence, including independent nested supplied bounds. */
export const captureBaseline = (
record: SignalRecord,
snapshot: SourceSnapshot,
): Extract<Baseline, { _tag: 'Captured' }> =>
Baseline.Captured({
record: { ...record, latency: copyReading(record.latency), errors: copyReading(record.errors) },
snapshot: { ...snapshot },
});
comparison.ts
import type { CartesianLayout } from '@opsydyn/foldkit-viz/chart/cartesian';
import { errorBarGeometry } from '@opsydyn/foldkit-viz/chart/errorBars';
import type { ErrorBarMark } from '@opsydyn/foldkit-viz/chart/errorBars';
import { Schema } from 'effect';
import { defineTaggedUnion } from 'foldkit/schema';
import { Baseline } from './baseline';
import type { ReadyModel, ChartRole } from './model';
import { Reading, readingValue, readingBounds } from './quality';
import type { SignalRecord } from './quality';
export type Metric = 'latency' | 'errors';
const metricSchema = Schema.Literals(['latency', 'errors']);
export const MetricComparison = defineTaggedUnion({
Available: {
metric: metricSchema,
current: Reading,
baseline: Reading,
delta: Schema.Number,
unit: Schema.Literals(['ms', 'percentage points']),
},
Unavailable: {
metric: metricSchema,
current: Schema.NullOr(Reading),
baseline: Schema.NullOr(Reading),
reason: Schema.Literals(['NoBaseline', 'NoCurrent', 'NonNumeric']),
},
});
export type MetricComparison = typeof MetricComparison.Type;
export const compareReading = (
metric: Metric,
current: Reading | null,
baseline: Reading | null,
): MetricComparison => {
if (baseline === null)
return MetricComparison.Unavailable({ metric, current, baseline, reason: 'NoBaseline' });
if (current === null)
return MetricComparison.Unavailable({ metric, current, baseline, reason: 'NoCurrent' });
const a = readingValue(current),
b = readingValue(baseline);
if (a === null || b === null)
return MetricComparison.Unavailable({ metric, current, baseline, reason: 'NonNumeric' });
return MetricComparison.Available({
metric,
current,
baseline,
delta: a - b,
unit: metric === 'latency' ? 'ms' : 'percentage points',
});
};
export const deriveSignalComparison = (model: ReadyModel) => {
const current = model.records.find((d) => d.id === model.inspection.key) ?? null;
const record = Baseline.match(model.baseline, { None: () => null, Captured: (b) => b.record });
return {
current,
baseline: model.baseline,
baselineOutsideView:
record !== null && (record.time < model.viewport[0] || record.time > model.viewport[1]),
latency: compareReading('latency', current?.latency ?? null, record?.latency ?? null),
errors: compareReading('errors', current?.errors ?? null, record?.errors ?? null),
};
};
export type ComparisonBar = Readonly<{
purpose: 'inspection' | 'baseline' | 'both';
mark: ErrorBarMark<SignalRecord>;
}>;
export type ComparisonChart = Readonly<{
errorBars: ReadonlyArray<ComparisonBar>;
baselineReference: Readonly<{ record: SignalRecord; reading: Reading; value: number }> | null;
}>;
export const deriveComparisonChart = (
model: ReadyModel,
role: ChartRole,
layout: CartesianLayout,
): ComparisonChart => {
const metric = role === 'errors' ? 'errors' : 'latency';
const { current } = deriveSignalComparison(model);
const baseline = Baseline.match(model.baseline, { None: () => null, Captured: (b) => b.record });
const selected: Array<Readonly<{ record: SignalRecord; purpose: ComparisonBar['purpose'] }>> = [];
if (baseline !== null)
selected.push({ record: baseline, purpose: current?.id === baseline.id ? 'both' : 'baseline' });
if (current !== null && current.id !== baseline?.id)
selected.push({ record: current, purpose: 'inspection' });
const errorBars = selected.flatMap(({ record, purpose }) => {
const bounds = readingBounds(record[metric]);
if (bounds === null) return [];
return errorBarGeometry(
[record],
{
position: (d) => d.time,
lower: () => bounds.lower,
upper: () => bounds.upper,
datumKey: (d) => d.id,
},
layout,
{ axis: 'y', capSize: 8 },
).map((mark) => ({ purpose, mark }));
});
const reading = baseline?.[metric] ?? null,
value = reading === null ? null : readingValue(reading);
return {
errorBars,
baselineReference:
baseline !== null && reading !== null && value !== null
? { record: baseline, reading, value }
: null,
};
};
comparison-view.ts
import type { CartesianLayout } from '@opsydyn/foldkit-viz/chart/cartesian';
import type { ErrorBarSegment } from '@opsydyn/foldkit-viz/chart/errorBars';
import { resolveSeriesStyle } from '@opsydyn/foldkit-viz/chart/theme';
import { pointSeries } from '@opsydyn/foldkit-viz/foldkit/cartesian';
import type { Html, HtmlBuilder } from 'foldkit/html';
import { exampleTheme } from '#example/frame';
import { Baseline } from './baseline';
import { deriveComparisonChart, deriveSignalComparison, MetricComparison } from './comparison';
import { utc } from './derive';
import { Message } from './message';
import type { ReadyModel, ChartRole } from './model';
import { readingText, readingValue, sourceFreshness } from './quality';
import type { SignalRecord, SourceSnapshot } from './quality';
import { qualityDetails } from './quality-view';
const recordSummary = (
label: string,
record: SignalRecord | null,
snapshot: SourceSnapshot | null,
captured: boolean,
h: HtmlBuilder<Message>,
): Html =>
h.article(
[h.Class('signal-comparison-record')],
[
h.h3([], [label]),
record === null
? h.p([], [captured ? 'No baseline captured' : 'No current inspection'])
: h.p([h.Class('signal-comparison-identity')], [`${record.id} / ${utc(record.time)}`]),
...(record === null
? []
: [
h.dl(
[],
(['latency', 'errors'] as const).map((metric) => {
const unit = metric === 'latency' ? 'ms' : '%',
reading = record[metric];
return h.div(
[],
[
h.dt([], [metric === 'latency' ? 'Latency (ms)' : 'Errors (%)']),
h.dd(
[],
[
`${readingText(reading)}${readingValue(reading) === null ? '' : ` ${unit}`}`,
],
),
h.p([h.Class('signal-reading-detail')], [qualityDetails(reading, unit)]),
],
);
}),
),
]),
...(snapshot === null
? []
: [
h.p(
[h.Class('signal-reading-detail')],
[
`${captured ? 'Baseline source' : 'Current source'}: ${sourceFreshness(snapshot)}${captured ? ' at capture' : ''} · ${snapshot.revision} · as of ${utc(snapshot.asOf)} · updated ${utc(snapshot.updatedAt)} · stale after ${snapshot.staleAfterMs} ms`,
],
),
]),
],
);
export const comparisonPanel = (model: ReadyModel, h: HtmlBuilder<Message>): Html => {
const comparison = deriveSignalComparison(model);
const captured = Baseline.match(model.baseline, { None: () => null, Captured: (b) => b });
const delta = (label: string, value: MetricComparison) =>
h.div(
[],
[
h.dt([], [label]),
MetricComparison.match(value, {
Available: (r) =>
h.dd(
[h.Class('signal-comparison-delta')],
[`${r.delta > 0 ? '+' : ''}${r.delta} ${r.unit}`],
),
Unavailable: (r) =>
h.dd(
[h.Class('signal-comparison-unavailable')],
[
{
NoBaseline: 'No baseline captured',
NoCurrent: 'No current inspection',
NonNumeric: 'Comparison unavailable',
}[r.reason],
],
),
}),
],
);
return h.section(
[h.Class('signal-comparison')],
[
h.h2([], ['Compare observations']),
h.p(
[h.Class('signal-reading-detail')],
[
'Capture a baseline, then resume inspection to compare another record. Supplied ranges describe each reading; differences do not imply statistical significance.',
],
),
h.div(
[h.Class('signal-controls')],
[
h.button(
[
h.Type('button'),
h.OnClick(Message.ClickedCaptureBaseline()),
h.Disabled(comparison.current === null),
],
[captured === null ? 'Capture baseline' : 'Replace baseline'],
),
h.button(
[
h.Type('button'),
h.OnClick(Message.ClickedClearBaseline()),
h.Disabled(captured === null),
],
['Clear baseline'],
),
],
),
h.div(
[h.Class('signal-comparison-grid'), h.AriaLive('polite')],
[
recordSummary(
'Captured baseline',
captured?.record ?? null,
captured?.snapshot ?? null,
true,
h,
),
recordSummary(
'Current inspection',
comparison.current,
comparison.current === null ? null : model.snapshot,
false,
h,
),
h.dl(
[h.Class('signal-comparison-differences')],
[
delta('Latency difference', comparison.latency),
delta('Error rate difference', comparison.errors),
],
),
...(comparison.baselineOutsideView
? [
h.p(
[h.Class('signal-comparison-outside')],
['Outside current view · captured baseline retained'],
),
]
: []),
],
),
],
);
};
export const comparisonLayers = (
model: ReadyModel,
role: ChartRole,
layout: CartesianLayout,
h: HtmlBuilder<Message>,
): ReadonlyArray<Html> => {
const chart = deriveComparisonChart(model, role, layout),
metric = role === 'errors' ? 'errors' : 'latency';
const colour =
role === 'errors'
? 'var(--chart-series-b, var(--coral))'
: 'var(--chart-series-a, var(--blue))';
const segment = (part: ErrorBarSegment, cap: boolean) =>
h.line(
[
h.Class(cap ? 'signal-error-bar-cap' : 'signal-error-bar-stem'),
h.X1(String(part.start[0])),
h.Y1(String(part.start[1])),
h.X2(String(part.end[0])),
h.Y2(String(part.end[1])),
],
[],
);
const layers: Html[] = chart.errorBars.map(({ purpose, mark }) => {
const estimated = mark.datum[metric]._tag === 'Estimated';
const equal =
mark.lowerCap.start[0] === mark.upperCap.start[0] &&
mark.lowerCap.start[1] === mark.upperCap.start[1];
return h.g(
[
h.Key(`${purpose}-${mark.key}`),
h.Class('signal-comparison-error-bar'),
h.DataAttribute('comparison-purpose', purpose),
h.AriaLabel(
`${purpose === 'inspection' ? 'Inspected interval' : 'Comparison baseline interval'}: ${mark.key} · ${mark.datum[metric]._tag}`,
),
h.Stroke(colour),
h.StrokeWidth('1.5'),
h.StrokeDasharray(purpose === 'inspection' ? (estimated ? '7 4' : '') : '3 3'),
h.Fill('none'),
],
[
segment(mark.stem, false),
segment(mark.lowerCap, true),
...(equal ? [] : [segment(mark.upperCap, true)]),
],
);
});
const reference = chart.baselineReference;
if (reference !== null) {
const x = layout.x(reference.record.time),
y = layout.y(reference.value);
if (role !== 'overview')
layers.push(
h.line(
[
h.Class('signal-baseline-reference'),
h.AriaLabel(
`Comparison baseline: ${reference.record.id} · ${reference.value} ${metric === 'latency' ? 'ms' : '%'}`,
),
h.X1(String(layout.plot.left)),
h.X2(String(layout.plot.right)),
h.Y1(String(y)),
h.Y2(String(y)),
h.Stroke(colour),
h.StrokeWidth('1'),
h.StrokeDasharray('3 3'),
],
[],
),
);
else if (
reference.record.time >= layout.xDomain[0] &&
reference.record.time <= layout.xDomain[1]
)
layers.push(
h.line(
[
h.Class('signal-baseline-time'),
h.AriaLabel(`Baseline time: ${utc(reference.record.time)}`),
h.X1(String(x)),
h.X2(String(x)),
h.Y1(String(layout.plot.top)),
h.Y2(String(layout.plot.bottom)),
h.Stroke(colour),
h.StrokeWidth('1'),
h.StrokeDasharray('3 3'),
],
[],
),
);
if (reference.record.id !== model.inspection.key)
layers.push(
pointSeries(h, {
points: [{ datum: reference.record, key: reference.record.id, seriesKey: role, x, y }],
styleFor: () =>
resolveSeriesStyle(
exampleTheme,
{
stroke: colour,
fill: reference.reading._tag === 'Estimated' ? 'var(--surface)' : colour,
strokeWidth: 1.5,
pointRadius: 3,
symbol: reference.reading._tag === 'Estimated' ? 'diamond' : 'circle',
},
{},
{},
),
labelFor: (d) => `Comparison baseline ${d.id} · ${reference.reading._tag}`,
activeKey: null,
}),
);
}
return layers;
};
export const baselineReferenceList = (
model: ReadyModel,
role: ChartRole,
h: HtmlBuilder<Message>,
): ReadonlyArray<Html> =>
role === 'overview'
? []
: Baseline.match(model.baseline, {
None: () => [],
Captured: ({ record }) => {
const reading = role === 'errors' ? record.errors : record.latency,
value = readingValue(reading);
return value === null
? []
: [
h.li(
[h.Key(`baseline-${role}`), h.Class('signal-baseline-meaning')],
[
`Comparison baseline: ${record.id} / ${utc(record.time)} · ${value} ${role === 'errors' ? '%' : 'ms'} · ${qualityDetails(reading, role === 'errors' ? '%' : 'ms')}`,
],
),
];
},
});
events.ts
import { Option, Schema } from 'effect';
import { defineTaggedUnion } from 'foldkit/schema';
import { SourceSnapshot, validSourceSnapshot } from './quality';
const EventStyle = Schema.Struct({
stroke: Schema.optional(Schema.String),
fill: Schema.optional(Schema.String),
dashPattern: Schema.optional(Schema.String),
symbol: Schema.optional(
Schema.Literals(['circle', 'cross', 'diamond', 'square', 'star', 'triangle', 'wye']),
),
});
export const SignalEvent = Schema.Struct({
id: Schema.String,
time: Schema.Number,
label: Schema.String,
kind: Schema.String,
description: Schema.NullOr(Schema.String),
sourceRef: Schema.NullOr(Schema.String),
style: EventStyle,
});
export type SignalEvent = typeof SignalEvent.Type;
export const EventDataset = Schema.Struct({
records: Schema.Array(SignalEvent),
snapshot: SourceSnapshot,
});
export type EventDataset = typeof EventDataset.Type;
export const EventInspection = defineTaggedUnion({ None: {}, Selected: { key: Schema.String } });
export type EventInspection = typeof EventInspection.Type;
export const EventFeed = defineTaggedUnion({
NotSupplied: {},
Invalid: { error: Schema.String },
Ready: {
records: Schema.Array(SignalEvent),
snapshot: SourceSnapshot,
inspection: EventInspection,
},
});
export type EventFeed = typeof EventFeed.Type;
const nonempty = (text: string): boolean => text.trim().length > 0;
const invalid = (): EventFeed =>
EventFeed.Invalid({
error:
'Provide unique event IDs, exact recorded UTC times, non-empty metadata, valid styles and independent source metadata.',
});
/** Optional context is decoded independently of observation validity. */
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- This is the schema-decoding boundary for optional untrusted event data.
export function normaliseEventFeed(input: unknown): EventFeed {
if (input === undefined) return EventFeed.NotSupplied();
return Option.match(Schema.decodeUnknownOption(EventDataset)(input), {
onNone: invalid,
onSome: ({ records, snapshot }) => {
if (!validSourceSnapshot(snapshot)) return invalid();
const ids = new Set<string>();
for (const event of records) {
if (
!nonempty(event.id) ||
ids.has(event.id) ||
!nonempty(event.label) ||
!nonempty(event.kind) ||
!Number.isSafeInteger(event.time) ||
Math.abs(event.time) > 8.64e15 ||
event.time > snapshot.asOf ||
(event.description !== null && !nonempty(event.description)) ||
(event.sourceRef !== null && !nonempty(event.sourceRef))
)
return invalid();
ids.add(event.id);
}
return EventFeed.Ready({
records: records
.map((event) => ({ ...event, style: { ...event.style } }))
.sort((a, b) => a.time - b.time || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)),
snapshot: { ...snapshot },
inspection: EventInspection.None(),
});
},
});
}
events-data.ts
import type { EventDataset, SignalEvent } from './events';
const t0 = 1700000000000;
const record = (
id: string,
offset: number,
label: string,
kind: string,
description: string | null,
sourceRef: string | null,
style: SignalEvent['style'] = {},
): SignalEvent => ({ id, time: t0 + offset, label, kind, description, sourceRef, style });
/** Caller-authored illustrative context. Temporal proximity does not establish causation. */
export const eventDataset: EventDataset = {
snapshot: {
revision: 'signal-events-v1',
updatedAt: t0 + 129000,
asOf: t0 + 130000,
staleAfterMs: 10000,
},
records: [
record(
'event-before',
-5000,
'Earlier operator note',
'note',
'Recorded before the available observation range.',
'illustrative log 001',
),
record(
'event-missing',
12000,
'Collector note',
'note',
'Context recorded while latency measurements were unavailable.',
'illustrative log 002',
{ symbol: 'square', stroke: 'var(--signal-event-note)' },
),
record(
'event-deploy',
48000,
'Deployment recorded',
'deployment',
'A recorded deployment; no causal relationship to the signal is asserted.',
'illustrative deployment 003',
{
symbol: 'diamond',
stroke: 'var(--signal-event-deploy)',
fill: 'var(--signal-event-deploy)',
},
),
record(
'event-config',
48000,
'Configuration recorded',
'configuration',
'A separate record at the same exact instant.',
'illustrative change 004',
{ symbol: 'triangle', stroke: 'var(--signal-event-config)' },
),
record(
'event-note',
48200,
'Operator follow-up',
'note',
'Exact recorded time between observations.',
null,
{ symbol: 'square', stroke: 'var(--signal-event-note)' },
),
record(
'event-gap',
72000,
'Operator note during gap',
'note',
'No observation was recorded at this event timestamp.',
'illustrative log 006',
),
record('event-boundary', 119000, 'Window review', 'review', null, 'illustrative review 007', {
symbol: 'diamond',
}),
record(
'event-after',
125000,
'Later operator note',
'note',
'Recorded after the available observation range.',
null,
),
],
};
event-derive.ts
import type { CartesianLayout } from '@opsydyn/foldkit-viz/chart/cartesian';
import { EventFeed, EventInspection, type SignalEvent } from './events';
import type { ReadyModel, ChartRole } from './model';
export const EVENT_CELL_SIZE = 12;
export type EventGroup = Readonly<{ key: string; x: number; members: ReadonlyArray<SignalEvent> }>;
export type EventPosition = 'InView' | 'OutsideView' | 'OutsideBounds';
export type EventGuide = Readonly<{ x: number; top: number; bottom: number }>;
export function selectedEvent(model: ReadyModel): SignalEvent | null {
return EventFeed.match(model.events, {
NotSupplied: () => null,
Invalid: () => null,
Ready: (feed) =>
EventInspection.match(feed.inspection, {
None: () => null,
Selected: ({ key }) => feed.records.find((event) => event.id === key) ?? null,
}),
});
}
export function eventPosition(model: ReadyModel, event: SignalEvent): EventPosition {
if (event.time < model.bounds[0] || event.time > model.bounds[1]) return 'OutsideBounds';
if (event.time < model.viewport[0] || event.time > model.viewport[1]) return 'OutsideView';
return 'InView';
}
/** Same finite-output contract as Viz's internal layout guard; no scale/math policy is added. */
const projectedX = (layout: CartesianLayout, time: number): number => {
const x = layout.x(time);
if (!Number.isFinite(x)) throw new RangeError('Event coordinate must be finite.');
return x;
};
export function visibleEventGroups(
model: ReadyModel,
layout: CartesianLayout,
): ReadonlyArray<EventGroup> {
return EventFeed.match(model.events, {
NotSupplied: () => [],
Invalid: () => [],
Ready: ({ records }) => {
const cells = new Map<number, { x: number; members: SignalEvent[] }>();
for (const event of records) {
if (event.time < model.viewport[0] || event.time > model.viewport[1]) continue;
const x = projectedX(layout, event.time);
const cell = Math.floor((x - layout.plot.left) / EVENT_CELL_SIZE);
const group = cells.get(cell);
if (group) group.members.push(event);
else cells.set(cell, { x, members: [event] });
}
return Array.from(cells.values(), (group) => ({
...group,
key: JSON.stringify(group.members.map((event) => event.id)),
}));
},
});
}
export function selectedEventGuide(
model: ReadyModel,
role: ChartRole,
layout: CartesianLayout,
): EventGuide | null {
const event = selectedEvent(model);
if (event === null || (role === 'overview' && eventPosition(model, event) === 'OutsideBounds'))
return null;
return { x: projectedX(layout, event.time), top: layout.plot.top, bottom: layout.plot.bottom };
}
event-view.ts
import type { CartesianLayout } from '@opsydyn/foldkit-viz/chart/cartesian';
import { resolveSeriesStyle } from '@opsydyn/foldkit-viz/chart/theme';
import { symbolPath } from '@opsydyn/foldkit-viz/shape/symbol';
import type { Html, HtmlBuilder } from 'foldkit/html';
import { exampleTheme } from '#example/frame';
import { utc } from './derive';
import {
eventPosition,
selectedEvent,
selectedEventGuide,
visibleEventGroups,
type EventPosition,
} from './event-derive';
import { EventFeed, type SignalEvent } from './events';
import { Message } from './message';
import type { ChartRole, ReadyModel } from './model';
import { sourceFreshness } from './quality';
const positionText = {
InView: 'In current view',
OutsideView: 'Outside current view',
OutsideBounds: 'Outside available time range',
} satisfies Readonly<Record<EventPosition, string>>;
const styleFor = (event: SignalEvent) =>
resolveSeriesStyle(
exampleTheme,
{ stroke: exampleTheme.focus, fill: exampleTheme.background, symbol: 'diamond' },
{},
{
stroke: event.style.stroke ?? exampleTheme.focus,
fill: event.style.fill ?? exampleTheme.background,
symbol: event.style.symbol ?? 'diamond',
dashPattern: event.style.dashPattern ?? '',
},
);
export function eventGuideLayers(
model: ReadyModel,
role: ChartRole,
layout: CartesianLayout,
h: HtmlBuilder<Message>,
): ReadonlyArray<Html> {
const event = selectedEvent(model),
guide = selectedEventGuide(model, role, layout);
if (event === null || guide === null) return [];
const style = styleFor(event);
return [
h.line(
[
h.Class('signal-event-guide'),
h.DataAttribute('event-guide', event.id),
h.AriaLabel(`Selected event: ${event.id} / ${utc(event.time)} / ${event.label}`),
h.X1(String(guide.x)),
h.X2(String(guide.x)),
h.Y1(String(guide.top)),
h.Y2(String(guide.bottom)),
h.Stroke(style.stroke),
h.StrokeWidth('1.5'),
h.StrokeDasharray(style.dashPattern || '6 2 1 2'),
],
[],
),
];
}
export function eventPanel(
model: ReadyModel,
layout: CartesianLayout,
h: HtmlBuilder<Message>,
): Html {
const selected = selectedEvent(model),
groups = visibleEventGroups(model, layout);
const detail = (event: SignalEvent): Html => {
const position = eventPosition(model, event);
const observation = model.records.find((record) => record.time === event.time);
return h.article(
[h.Key('selected-event'), h.Class('signal-event-selected')],
[
h.h3([], ['Selected event']),
h.p([h.Class('signal-event-identity')], [`${event.id} / ${utc(event.time)}`]),
h.p([], [`${event.kind} · ${event.label}`]),
h.p([h.Class('signal-reading-detail')], [positionText[position]]),
h.dl(
[],
[
h.div(
[],
[
h.dt([], ['Description']),
h.dd([], [event.description ?? 'Description unspecified']),
],
),
h.div(
[],
[
h.dt([], ['Source reference']),
h.dd([], [event.sourceRef ?? 'Source reference unspecified']),
],
),
],
),
h.p(
[h.Class('signal-reading-detail')],
[
observation
? `Exact observation: ${observation.id}`
: 'No observation at this exact timestamp',
],
),
h.p(
[h.Class('signal-reading-detail')],
[
'Selected-event guide = exact selected event time. Temporal proximity is context, not proof of causation.',
],
),
h.div(
[h.Class('signal-controls')],
[
h.button(
[
h.Type('button'),
h.OnClick(Message.ClickedCentreEvent()),
h.Disabled(position === 'OutsideBounds'),
],
['Centre event'],
),
h.button([h.Type('button'), h.OnClick(Message.ClickedClearEvent())], ['Clear event']),
],
),
],
);
};
return h.section(
[h.Class('signal-events')],
[
h.h2([h.Key('heading')], ['Events']),
...EventFeed.match(model.events, {
NotSupplied: () => [h.p([h.Class('signal-reading-detail')], ['No event feed supplied'])],
Invalid: ({ error }) => [
h.p([], ['Event feed unavailable']),
h.p([h.Class('signal-reading-detail')], [error]),
],
Ready: ({ records, snapshot }) => [
h.p(
[h.Key('count'), h.Class('signal-event-count')],
[
`${groups.reduce((count, group) => count + group.members.length, 0)} in view / ${records.length} recorded`,
],
),
h.p(
[h.Key('viewport'), h.Class('signal-reading-detail')],
[`View: ${utc(model.viewport[0])} → ${utc(model.viewport[1])}`],
),
h.svg(
[
h.Key('lane'),
h.Class('signal-event-lane'),
h.ViewBox(`0 0 ${layout.frame.width} 52`),
h.Width(String(layout.frame.width)),
h.Height('52'),
h.AriaHidden(true),
],
[
h.line(
[
h.X1(String(layout.plot.left)),
h.X2(String(layout.plot.right)),
h.Y1('26'),
h.Y2('26'),
h.Stroke(exampleTheme.axis),
h.StrokeDasharray('1 4'),
],
[],
),
...groups.map((group) => {
const first = group.members[0];
if (!first) return null;
const style = styleFor(first),
bundled = group.members.length > 1;
return h.g(
[
h.Key(group.key),
h.Class(bundled ? 'signal-event-bundle' : 'signal-event-mark'),
h.Transform(`translate(${group.x} 26)`),
h.DataAttribute('event-group', group.key),
],
[
...(bundled
? [
h.path(
[
h.D(symbolPath('square', 64)),
h.Transform('translate(-2 -2)'),
h.Fill(exampleTheme.background),
h.Stroke(exampleTheme.text),
h.StrokeWidth('1.5'),
],
[],
),
h.path(
[
h.D(symbolPath('square', 64)),
h.Transform('translate(2 2)'),
h.Fill(exampleTheme.background),
h.Stroke(exampleTheme.text),
h.StrokeWidth('1.5'),
],
[],
),
]
: [
h.path(
[
h.D(symbolPath(style.symbol, 64)),
h.Fill(style.fill),
h.Stroke(style.stroke),
h.StrokeWidth('1.5'),
h.StrokeDasharray(style.dashPattern),
],
[],
),
]),
],
);
}),
],
),
...(records.length === 0
? [h.p([h.Key('empty-range')], ['No events recorded'])]
: groups.length === 0
? [h.p([h.Key('empty-range')], ['No events in this time range'])]
: []),
h.p(
[h.Key('bundle-legend'), h.Class('signal-reading-detail')],
[
'Overlapping squares = nearby events bundled for display. Each exact record remains individually selectable below.',
],
),
h.p(
[h.Key('source'), h.Class('signal-reading-detail')],
[
`Event source: ${sourceFreshness(snapshot)} · ${snapshot.revision} · as of ${utc(snapshot.asOf)} · updated ${utc(snapshot.updatedAt)} · stale after ${snapshot.staleAfterMs} ms`,
],
),
h.p(
[
h.Key('announcement'),
h.Class('signal-event-announcement'),
h.AriaLive('polite'),
h.Attribute('aria-atomic', 'true'),
],
[
selected
? `${selected.id} / ${utc(selected.time)} / ${selected.label}`
: 'No event selected',
],
),
...(selected ? [detail(selected)] : []),
h.details(
[h.Key('browser'), h.Class('signal-event-browser')],
[
h.summary([], [`Browse events (${records.length})`]),
h.ul(
[h.Class('signal-event-groups'), h.AriaLabel('Visible event bundles')],
groups
.filter((group) => group.members.length > 1)
.map((group) => {
const first = group.members[0],
last = group.members.at(-1);
if (!first || !last) return null;
return h.li(
[h.Key(group.key)],
[`${group.members.length} events · ${utc(first.time)} → ${utc(last.time)}`],
);
}),
),
h.ol(
[h.Class('signal-event-list')],
records.map((event) =>
h.li(
[h.Key(event.id)],
[
h.div(
[h.Class('signal-event-list-copy')],
[
h.p(
[h.Class('signal-event-identity')],
[`${utc(event.time)} / ${event.id}`],
),
h.p([], [`${event.kind} · ${event.label}`]),
h.p(
[h.Class('signal-reading-detail')],
[positionText[eventPosition(model, event)]],
),
],
),
h.button(
[
h.Type('button'),
h.DataAttribute('event-select', event.id),
h.OnClick(Message.ClickedEvent({ key: event.id })),
h.Attribute('aria-pressed', String(selected?.id === event.id)),
h.AriaLabel(
`Select event ${event.id}: ${event.label} / ${event.kind} / ${utc(event.time)}`,
),
],
[selected?.id === event.id ? 'Selected event' : 'Select event'],
),
],
),
),
),
],
),
],
}),
],
);
}
model.ts
import { Option, Schema } from 'effect';
import { defineTaggedUnion } from 'foldkit/schema';
import type { Return } from 'foldkit/update';
import { Props, SignalRecord, SourceSnapshot, SignalThreshold, validSignalProps } from './quality';
export { Props } from './quality';
export type { Props as SignalProps } from './quality';
import { Baseline } from './baseline';
import { EventFeed, normaliseEventFeed } from './events';
import type { Message } from './message';
export const ChartRole = Schema.Literals(['overview', 'latency', 'errors']);
export type ChartRole = typeof ChartRole.Type;
const Domain = Schema.Tuple([Schema.Number, Schema.Number]);
const Selection = defineTaggedUnion({
None: {},
Interval: { axis: Schema.Literals(['x', 'y']), domain: Domain },
Keys: { keys: Schema.Array(Schema.String) },
});
export const Inspection = defineTaggedUnion({
Following: { key: Schema.NullOr(Schema.String) },
Pinned: { key: Schema.String },
});
const active = {
pointerId: Schema.Number,
role: ChartRole,
anchorX: Schema.Number,
anchorTime: Schema.Number,
startViewport: Domain,
startSelection: Selection,
currentTime: Schema.Number,
};
export const Gesture = defineTaggedUnion({ Idle: {}, Brushing: active, Panning: active });
const Status = Schema.Literals(['Ready', 'Unavailable']);
export const Model = defineTaggedUnion({
Empty: { records: Schema.Array(SignalRecord) },
Invalid: { error: Schema.String },
Ready: {
records: Schema.Array(SignalRecord),
snapshot: SourceSnapshot,
baseline: Baseline,
events: EventFeed,
maxGapMs: Schema.Number,
thresholds: Schema.Array(SignalThreshold),
scenarioAsOf: Schema.Struct({ Fresh: Schema.Number, Stale: Schema.Number }),
bounds: Domain,
viewport: Domain,
selection: Selection,
inspection: Inspection,
gesture: Gesture,
widths: Schema.Struct({
overview: Schema.Number,
latency: Schema.Number,
errors: Schema.Number,
}),
inputStatus: Schema.Struct({ overview: Status, latency: Status, errors: Status }),
},
});
export type Model = typeof Model.Type;
export type ReadyModel = Extract<Model, { _tag: 'Ready' }>;
export const init = (props: Props): Return<Model, Message> =>
Option.match(Schema.decodeUnknownOption(Props)(props), {
onNone: () => ({ model: Model.Invalid({ error: 'Provide a valid observation array.' }) }),
onSome: (props) => {
const { data } = props;
if (!validSignalProps(props))
return {
model: Model.Invalid({
error:
'Provide unique IDs/times, valid quality readings, intervals, source metadata and references.',
}),
};
if (data.length === 0) return { model: Model.Empty({ records: [] }) };
const records = [...data].sort(
(a, b) => a.time - b.time || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0),
);
const first = records[0],
last = records.at(-1);
if (!first || !last) return { model: Model.Empty({ records: [] }) };
const padding = Math.max(0, (1000 - (last.time - first.time)) / 2);
const bounds: readonly [number, number] = [first.time - padding, last.time + padding];
if (
!Number.isFinite(bounds[1] - bounds[0]) ||
bounds[1] - bounds[0] < 1000 ||
Math.abs(bounds[0]) > 8.64e15 ||
Math.abs(bounds[1]) > 8.64e15
)
return {
model: Model.Invalid({
error: 'Time extent cannot support a representable UTC viewport.',
}),
};
return {
model: Model.Ready({
records,
snapshot: props.snapshot,
baseline: Baseline.None(),
events: normaliseEventFeed(props.events),
maxGapMs: props.maxGapMs,
thresholds: props.thresholds,
scenarioAsOf: props.scenarioAsOf,
bounds,
viewport: bounds,
selection: Selection.None(),
inspection: Inspection.Following({ key: null }),
gesture: Gesture.Idle(),
widths: { overview: 700, latency: 700, errors: 700 },
inputStatus: { overview: 'Unavailable', latency: 'Unavailable', errors: 'Unavailable' },
}),
};
},
});
message.ts
import { Schema } from 'effect';
import { defineMessageUnion } from 'foldkit/message';
import { ChartRole } from './model';
const pointer = { role: ChartRole, pointerId: Schema.Number, x: Schema.Number, y: Schema.Number };
export const Message = defineMessageUnion({
ClickedEvent: { key: Schema.String },
ClickedClearEvent: {},
CompletedEventBrowserFocus: {},
ClickedCentreEvent: {},
ClickedCaptureBaseline: {},
ClickedClearBaseline: {},
ClickedFreshnessScenario: { scenario: Schema.Literals(['Fresh', 'Stale']) },
ChangedSignalDataset: { props: Schema.Unknown },
RecordedChartWidth: { role: ChartRole, width: Schema.Number },
RecordedInputAvailability: { role: ChartRole, status: Schema.Literals(['Ready', 'Unavailable']) },
RecordedPointerPosition: pointer,
StartedChartPointer: pointer,
MovedChartPointer: pointer,
EndedChartPointer: pointer,
CancelledChartPointer: { role: ChartRole, pointerId: Schema.Number },
ChangedRangeStart: { index: Schema.Number },
ChangedRangeEnd: { index: Schema.Number },
ClickedZoomIn: {},
ClickedZoomOut: {},
ClickedResetView: {},
ClickedClearSelection: {},
ClickedPinInspection: {},
ClickedResumeInspection: {},
PressedInspectionKey: {
role: ChartRole,
key: Schema.Literals(['ArrowLeft', 'ArrowRight', 'Home', 'End', 'Escape']),
},
});
export type Message = typeof Message.Type;
export type ChangedSignalDataset = Omit<typeof Message.ChangedSignalDataset.Type, 'props'> & {
readonly props: import('./quality').Props;
};
update.ts
import { intervalSelection, SELECTION_NONE } from '@opsydyn/foldkit-viz/interaction/selection';
import { constrainDomain, panDomain, zoomDomain } from '@opsydyn/foldkit-viz/interaction/viewport';
import type { Return } from 'foldkit/update';
import { Baseline, captureBaseline } from './baseline';
import { FocusEventBrowser } from './command';
import { deriveSignalChart, nearestVisible, visibleRecords } from './derive';
import { EventFeed, EventInspection } from './events';
import { Message } from './message';
import { Model, Gesture, Inspection, init } from './model';
import type { ReadyModel, ChartRole } from './model';
import type { Props } from './quality';
const cancel = (m: ReadyModel): ReadyModel =>
Gesture.match(m.gesture, {
Idle: () => m,
Brushing: (g) => ({
...m,
viewport: g.startViewport,
selection: g.startSelection,
gesture: Gesture.Idle(),
}),
Panning: (g) => ({
...m,
viewport: g.startViewport,
selection: g.startSelection,
gesture: Gesture.Idle(),
}),
});
const settleInspection = (m: ReadyModel): ReadyModel =>
Inspection.match(m.inspection, {
Pinned: () => m,
Following: ({ key }) => ({
...m,
inspection: Inspection.Following({
key: visibleRecords(m).some((d) => d.id === key) ? key : null,
}),
}),
});
const timeAt = (
m: ReadyModel,
role: ChartRole,
x: number,
domain?: readonly [number, number],
): number => {
const { geometry } = deriveSignalChart(m, role);
const { plot, xDomain } = geometry.layout;
const d = domain ?? xDomain;
return d[0] + Math.max(0, Math.min(1, (x - plot.left) / plot.width)) * (d[1] - d[0]);
};
const inspect = (m: ReadyModel, time: number): ReadyModel =>
Inspection.match(m.inspection, {
Pinned: () => m,
Following: () => ({
...m,
inspection: Inspection.Following({ key: nearestVisible(m, time)?.id ?? null }),
}),
});
type Pointer = Readonly<{ role: ChartRole; pointerId: number; x: number; y: number }>;
const validPointer = (p: Pointer) => [p.x, p.y, p.pointerId].every(Number.isFinite);
const move = (m: ReadyModel, p: Pointer): ReadyModel => {
if (!validPointer(p)) return m;
return Gesture.match(m.gesture, {
Idle: () => inspect(m, timeAt(m, p.role, p.x)),
Brushing: (g) =>
g.pointerId === p.pointerId && g.role === p.role
? { ...m, gesture: { ...g, currentTime: timeAt(m, p.role, p.x) } }
: m,
Panning: (g) => {
if (g.pointerId !== p.pointerId || g.role !== p.role) return m;
const currentTime = timeAt(m, p.role, p.x, g.startViewport);
return {
...m,
viewport: panDomain(g.startViewport, g.anchorTime - currentTime, m.bounds, 1000),
gesture: { ...g, currentTime },
};
},
});
};
const range = (m: ReadyModel, index: number, end: boolean): ReadyModel => {
if (!Number.isInteger(index) || index < 0 || index >= m.records.length || m.records.length < 2)
return m;
const base = cancel(m);
const selected = base.selection._tag === 'Interval' ? base.selection.domain : base.bounds;
let lo = Math.max(
0,
base.records.findIndex((d) => d.time >= selected[0]),
);
let hi = base.records.findLastIndex((d) => d.time <= selected[1]);
if (hi < 0) hi = base.records.length - 1;
if (end) hi = Math.max(lo + 1, index);
else lo = Math.min(hi - 1, index);
const first = base.records[lo],
last = base.records[hi];
if (!first || !last || first.time === last.time) return base;
return settleInspection({
...base,
selection: intervalSelection('x', [first.time, last.time]),
viewport: constrainDomain([first.time, last.time], base.bounds, 1000),
});
};
const zoom = (m: ReadyModel, factor: number): ReadyModel => {
const base = cancel(m);
const record = visibleRecords(base).find((d) => d.id === base.inspection.key);
return settleInspection({
...base,
viewport: zoomDomain(
base.viewport,
factor,
record?.time ?? base.viewport[0] + (base.viewport[1] - base.viewport[0]) / 2,
base.bounds,
1000,
),
});
};
const readyUpdate = (m: ReadyModel, message: Message): ReadyModel =>
Message.match(message, {
CompletedEventBrowserFocus: () => m,
ClickedEvent: ({ key }) =>
EventFeed.match(m.events, {
NotSupplied: () => m,
Invalid: () => m,
Ready: (feed) => {
const event = feed.records.find((record) => record.id === key);
return event === undefined
? m
: {
...cancel(m),
events: { ...feed, inspection: EventInspection.Selected({ key: event.id }) },
};
},
}),
ClickedClearEvent: () =>
EventFeed.match(m.events, {
NotSupplied: () => m,
Invalid: () => m,
Ready: (feed) =>
EventInspection.match(feed.inspection, {
None: () => m,
Selected: () => ({
...cancel(m),
events: { ...feed, inspection: EventInspection.None() },
}),
}),
}),
ClickedCentreEvent: () =>
EventFeed.match(m.events, {
NotSupplied: () => m,
Invalid: () => m,
Ready: (feed) =>
EventInspection.match(feed.inspection, {
None: () => m,
Selected: ({ key }) => {
const event = feed.records.find((record) => record.id === key);
if (event === undefined || event.time < m.bounds[0] || event.time > m.bounds[1])
return m;
const base = cancel(m);
const midpoint = base.viewport[0] + (base.viewport[1] - base.viewport[0]) / 2;
return settleInspection({
...base,
viewport: panDomain(base.viewport, event.time - midpoint, base.bounds, 1000),
});
},
}),
}),
ClickedCaptureBaseline: () => {
const record = m.records.find((d) => d.id === m.inspection.key);
return record === undefined
? m
: { ...cancel(m), baseline: captureBaseline(record, m.snapshot) };
},
ClickedClearBaseline: () => ({ ...cancel(m), baseline: Baseline.None() }),
ClickedFreshnessScenario: ({ scenario }) => ({
...m,
snapshot: { ...m.snapshot, asOf: m.scenarioAsOf[scenario] },
}),
ChangedSignalDataset: () => m,
RecordedChartWidth: ({ role, width }) => {
if (!Number.isFinite(width) || width <= 76)
return { ...cancel(m), inputStatus: { ...m.inputStatus, [role]: 'Unavailable' } };
if (width === m.widths[role]) return m;
return { ...cancel(m), widths: { ...m.widths, [role]: width } };
},
RecordedInputAvailability: ({ role, status }) => ({
...(status === 'Unavailable' ? cancel(m) : m),
inputStatus: { ...m.inputStatus, [role]: status },
}),
RecordedPointerPosition: (p) => (m.gesture._tag === 'Idle' ? move(m, p) : m),
StartedChartPointer: (p) => {
if (!validPointer(p)) return m;
if (m.gesture._tag !== 'Idle') return m.gesture.pointerId !== p.pointerId ? cancel(m) : m;
const time = timeAt(m, p.role, p.x);
const active = {
pointerId: p.pointerId,
role: p.role,
anchorX: p.x,
anchorTime: time,
currentTime: time,
startViewport: m.viewport,
startSelection: m.selection,
};
return {
...m,
gesture: p.role === 'overview' ? Gesture.Brushing(active) : Gesture.Panning(active),
};
},
MovedChartPointer: (p) => move(m, p),
EndedChartPointer: (p) => {
if (
!validPointer(p) ||
m.gesture._tag === 'Idle' ||
m.gesture.pointerId !== p.pointerId ||
m.gesture.role !== p.role
)
return m;
const g = m.gesture;
const moved = move(m, p);
if (g._tag === 'Brushing' && Math.abs(p.x - g.anchorX) >= 4) {
const time = timeAt(m, p.role, p.x);
const selection = intervalSelection('x', [g.anchorTime, time]);
if (selection._tag === 'Interval')
return settleInspection({
...m,
selection,
viewport: constrainDomain(selection.domain, m.bounds, 1000),
gesture: Gesture.Idle(),
});
}
return settleInspection(
inspect({ ...moved, gesture: Gesture.Idle() }, timeAt(moved, p.role, p.x)),
);
},
CancelledChartPointer: ({ role, pointerId }) =>
m.gesture._tag !== 'Idle' && m.gesture.role === role && m.gesture.pointerId === pointerId
? cancel(m)
: m,
ChangedRangeStart: ({ index }) => range(m, index, false),
ChangedRangeEnd: ({ index }) => range(m, index, true),
ClickedZoomIn: () => zoom(m, 0.5),
ClickedZoomOut: () => zoom(m, 2),
ClickedResetView: () => settleInspection({ ...cancel(m), viewport: m.bounds }),
ClickedClearSelection: () => ({ ...cancel(m), selection: SELECTION_NONE }),
ClickedPinInspection: () =>
m.inspection.key === null
? m
: { ...cancel(m), inspection: Inspection.Pinned({ key: m.inspection.key }) },
ClickedResumeInspection: () =>
settleInspection({
...cancel(m),
inspection: Inspection.Following({ key: m.inspection.key }),
}),
PressedInspectionKey: ({ key }) => {
if (key === 'Escape') return cancel(m);
const base = cancel(m);
const records = visibleRecords(base);
if (records.length === 0) return base;
const index = records.findIndex((d) => d.id === base.inspection.key);
const next =
key === 'Home'
? 0
: key === 'End'
? records.length - 1
: key === 'ArrowLeft'
? Math.max(0, index - 1)
: Math.min(records.length - 1, index + 1);
const record = records[next];
if (!record) return base;
return {
...base,
inspection: Inspection.match(base.inspection, {
Pinned: () => Inspection.Pinned({ key: record.id }),
Following: () => Inspection.Following({ key: record.id }),
}),
};
},
});
export const update = (model: Model, message: Message): Return<Model, Message> => {
if (message._tag === 'ChangedSignalDataset') {
// SAFETY: init schema-decodes and validates the payload before accessing any fields.
const props = message.props as Props;
return init(props);
}
const next = Model.match(model, {
Empty: () => model,
Invalid: () => model,
Ready: (m) => readyUpdate(m, message),
});
if (message._tag === 'ClickedClearEvent' && next !== model)
return { model: next, commands: [FocusEventBrowser()] };
return { model: next };
};
command.ts
import { Effect } from 'effect';
import { Command } from 'foldkit';
import { focus } from 'foldkit/dom';
import { Message } from './message';
export const FocusEventBrowser = Command.define('FocusEventBrowser', {
messages: [Message.CompletedEventBrowserFocus],
execute: focus('.signal-event-browser > summary').pipe(
Effect.match({
// A dataset replacement may remove the feed before the queued focus runs.
onFailure: () => Message.CompletedEventBrowserFocus(),
onSuccess: () => Message.CompletedEventBrowserFocus(),
}),
),
});
derive.ts
import { lineGeometry } from '@opsydyn/foldkit-viz/chart/cartesian';
import { intervalBandGeometry } from '@opsydyn/foldkit-viz/chart/intervalBand';
import { contiguousRuns } from '@opsydyn/foldkit-viz/chart/segments';
import { nearestByX } from '@opsydyn/foldkit-viz/interaction/inspection';
import { line } from '@opsydyn/foldkit-viz/shape/line';
import { Baseline } from './baseline';
import { EVENT_CELL_SIZE, selectedEvent } from './event-derive';
import type { ReadyModel, ChartRole } from './model';
import { readingValue, readingBounds, sourceFreshness } from './quality';
import type { SignalRecord } from './quality';
export const deriveSignalChart = (model: ReadyModel, role: ChartRole) => {
const domain = role === 'overview' ? model.bounds : model.viewport;
const frame = {
width: model.widths[role],
height: role === 'overview' ? 174 : 294,
margins: { top: 24, right: 20, bottom: 70, left: 56 },
};
const metric = role === 'errors' ? 'errors' : 'latency';
const reading = (d: SignalRecord) => d[metric];
const value = (d: SignalRecord) => readingValue(reading(d)) ?? NaN;
const thresholds = model.thresholds.filter((t) => t.metric === metric);
const extents = model.records.flatMap((d) => {
const v = readingValue(reading(d)),
b = readingBounds(reading(d));
return v === null ? [] : [v, ...(b === null ? [] : [b.lower, b.upper])];
});
const maximum = Math.max(0, ...extents, ...thresholds.map((t) => t.value));
const upper = maximum > 0 ? maximum * 1.1 : 1;
const visible = model.records.filter((d) => d.time >= domain[0] && d.time <= domain[1]);
const base = lineGeometry(
model.records,
{ datumKey: (d) => d.id, seriesKey: () => role, x: (d) => d.time, y: value },
{
frame,
xDomain: domain,
yDomain: [0, upper],
xTickCount: frame.width < 420 ? 2 : 4,
yTickCount: 4,
},
);
const connect = (a: SignalRecord, b: SignalRecord) =>
b.time - a.time <= model.maxGapMs && reading(a)._tag === reading(b)._tag;
const runs = contiguousRuns(model.records, {
x: (d) => d.time,
defined: (d) => readingValue(reading(d)) !== null,
connect,
}).map((records) => {
const quality =
records[0] && reading(records[0])._tag === 'Estimated'
? ('Estimated' as const)
: ('Observed' as const);
return {
quality,
records,
path: line(
records.map((d) => [base.layout.x(d.time), base.layout.y(value(d))] as const),
{ curve: 'linear' },
),
};
});
const geometry = {
...base,
series: runs.map((run, i) => {
const keys = new Set(run.records.map((d) => d.id));
return {
key: `${role}-${i}`,
path: run.path ?? '',
points: base.points.filter((p) => keys.has(p.key)),
};
}),
};
const bands = contiguousRuns(model.records, {
x: (d) => d.time,
defined: (d) => readingBounds(reading(d)) !== null,
connect,
}).map((records) =>
intervalBandGeometry(
records,
{
x: (d) => d.time,
lower: (d) => readingBounds(reading(d))?.lower ?? NaN,
upper: (d) => readingBounds(reading(d))?.upper ?? NaN,
datumKey: (d) => d.id,
},
base.layout,
),
);
const qualitySpans = contiguousRuns(model.records, {
x: (d) => d.time,
defined: (d) => reading(d)._tag === 'Missing' || reading(d)._tag === 'Invalid',
connect,
}).flatMap((records) => {
const first = records[0],
last = records.at(-1);
if (!first || !last) return [];
const status = reading(first)._tag === 'Missing' ? ('Missing' as const) : ('Invalid' as const);
return [{ status, start: first.time, end: last.time, keys: records.map((d) => d.id) }];
});
const gaps: Array<Readonly<{ start: number; end: number }>> = [];
for (let i = 1; i < model.records.length; i++) {
const a = model.records[i - 1],
b = model.records[i];
if (a && b && b.time - a.time > model.maxGapMs) gaps.push({ start: a.time, end: b.time });
}
const noDrawable = !runs.some((run) => {
const first = run.records[0],
last = run.records.at(-1);
return (
first !== undefined && last !== undefined && first.time <= domain[1] && last.time >= domain[0]
);
});
const inspected = model.records.find((d) => d.id === model.inspection.key) ?? null;
return {
frame,
geometry,
visible,
inspected,
runs,
bands,
qualitySpans,
gaps,
thresholds,
noDrawable,
freshness: sourceFreshness(model.snapshot),
};
};
export const visibleRecords = (model: ReadyModel) =>
model.records.filter((d) => d.time >= model.viewport[0] && d.time <= model.viewport[1]);
export const nearestVisible = (model: ReadyModel, time: number) =>
nearestByX(visibleRecords(model), { key: (d) => d.id, x: (d) => d.time }, time);
export const utc = (time: number) => new Date(time).toISOString();
export const inspectionNotice = (model: ReadyModel): string => {
const record = model.records.find((d) => d.id === model.inspection.key);
if (record)
return `${model.inspection._tag === 'Pinned' ? 'Pinned' : 'Following'} ${record.id}${record.time < model.viewport[0] || record.time > model.viewport[1] ? ' — outside current view' : ''}`;
return visibleRecords(model).length === 0
? 'No observation in current view. Zoom out to find a signal.'
: 'Hover or focus a detail chart to inspect an observation.';
};
export const currentSignalSource = (
model: ReadyModel,
): string => `// Controlled state; all timestamps are UTC milliseconds.
import { zoomDomain, panDomain } from '@opsydyn/foldkit-viz/interaction/viewport';
import { nearestByX } from '@opsydyn/foldkit-viz/interaction/inspection';
const bounds = ${JSON.stringify(model.bounds)};
const viewport = ${JSON.stringify(model.viewport)};
const selection = ${JSON.stringify(model.selection)};
const inspection = ${JSON.stringify(model.inspection)};
const baseline = ${JSON.stringify(model.baseline)};
const events = ${JSON.stringify(model.events)};
const selectedEventKey = ${JSON.stringify(selectedEvent(model)?.id ?? null)};
const eventLaneWidth = ${JSON.stringify(model.widths.latency)};
const eventCellSize = ${EVENT_CELL_SIZE};
const baselineFreshnessAtCapture = ${JSON.stringify(Baseline.match(model.baseline, { None: () => null, Captured: (b) => sourceFreshness(b.snapshot) }))};
const snapshot = ${JSON.stringify(model.snapshot)};
const freshness = ${JSON.stringify(sourceFreshness(model.snapshot))};
const maxGapMs = ${JSON.stringify(model.maxGapMs)};
const thresholds = ${JSON.stringify(model.thresholds)};
const inspectedRecord = ${JSON.stringify(model.records.find((d) => d.id === model.inspection.key) ?? null)};
const minimumSpan = 1000;
// Use viewport to derive both charts; selection and inspection remain independent.
`;
input.ts
import { clientToLocal } from '@opsydyn/foldkit-viz/interaction/coordinates';
import { Effect, Option, Queue, Stream } from 'effect';
import { defineStream } from 'foldkit/mount';
import { Message } from './message';
import { ChartRole } from './model';
type InputFact = Extract<
Message,
{
_tag:
| 'RecordedChartWidth'
| 'RecordedInputAvailability'
| 'StartedChartPointer'
| 'MovedChartPointer'
| 'EndedChartPointer'
| 'CancelledChartPointer';
}
>;
/** Scoped browser resource acquisition; semantic gestures live only in Model. */
export function signalInputFacts(
element: SVGSVGElement,
role: ChartRole,
): Stream.Stream<InputFact> {
return Stream.callback<InputFact>((queue) =>
Effect.acquireRelease(
Effect.sync(() => {
const offer = (message: InputFact) => {
Queue.offerUnsafe(queue, message);
};
const availability = (status: 'Ready' | 'Unavailable') =>
offer(Message.RecordedInputAvailability({ role, status }));
// This set tracks owned native capture resources for teardown, not gesture state.
const captures = new Set<number>();
function release(pointerId: number) {
Effect.runSync(
Effect.try(() => {
if (element.hasPointerCapture(pointerId)) element.releasePointerCapture(pointerId);
}).pipe(Effect.match({ onFailure: () => undefined, onSuccess: () => undefined })),
);
}
const coordinates = (event: PointerEvent) => {
const matrix = element.getScreenCTM();
if (matrix === null) {
availability('Unavailable');
return Option.none();
}
const numeric = {
a: matrix.a,
b: matrix.b,
c: matrix.c,
d: matrix.d,
e: matrix.e,
f: matrix.f,
};
if (![event.clientX, event.clientY, ...Object.values(numeric)].every(Number.isFinite)) {
availability('Unavailable');
return Option.none();
}
const point = clientToLocal({ x: event.clientX, y: event.clientY }, numeric);
if (point === null) {
availability('Unavailable');
return Option.none();
}
availability('Ready');
return Option.some({ ...point, role, pointerId: event.pointerId });
};
const owned = (event: PointerEvent) =>
element.querySelector('[data-signal-gesture]') === event.target;
const cancelled = (event: PointerEvent) => {
offer(Message.CancelledChartPointer({ role, pointerId: event.pointerId }));
release(event.pointerId);
captures.delete(event.pointerId);
};
const down = (event: PointerEvent) => {
if (event.button !== 0 || !owned(event)) return;
const point = coordinates(event);
if (Option.isNone(point)) return;
event.preventDefault();
const captured = Effect.runSync(
Effect.try(() => element.setPointerCapture(event.pointerId)).pipe(
Effect.match({ onFailure: () => false, onSuccess: () => true }),
),
);
if (!captured) {
cancelled(event);
return;
}
captures.add(event.pointerId);
offer(Message.StartedChartPointer(point.value));
};
const move = (event: PointerEvent) => {
if (!owned(event) && !element.hasPointerCapture(event.pointerId)) return;
const point = coordinates(event);
if (Option.isSome(point)) offer(Message.MovedChartPointer(point.value));
};
const up = (event: PointerEvent) => {
if (!captures.has(event.pointerId)) return;
const point = coordinates(event);
if (Option.isSome(point)) offer(Message.EndedChartPointer(point.value));
else offer(Message.CancelledChartPointer({ role, pointerId: event.pointerId }));
release(event.pointerId);
captures.delete(event.pointerId);
};
const listeners: ReadonlyArray<readonly [string, (event: PointerEvent) => void]> = [
['pointerdown', down],
['pointermove', move],
['pointerup', up],
['pointercancel', cancelled],
['lostpointercapture', cancelled],
];
// SAFETY: Only pointer event names are paired with these pointer listeners.
for (const [name, listener] of listeners) {
// SAFETY: The registered names are pointer events with PointerEvent payloads.
element.addEventListener(name, listener as EventListener);
}
const measured = (width: number) => {
offer(Message.RecordedChartWidth({ role, width }));
const matrix = element.getScreenCTM();
const usable =
matrix !== null &&
[matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f].every(Number.isFinite) &&
clientToLocal(
{ x: 0, y: 0 },
{ a: matrix.a, b: matrix.b, c: matrix.c, d: matrix.d, e: matrix.e, f: matrix.f },
) !== null;
availability(Number.isFinite(width) && width > 76 && usable ? 'Ready' : 'Unavailable');
};
const observer = new ResizeObserver((entries) => {
for (const entry of entries) measured(entry.contentRect.width);
});
observer.observe(element);
measured(element.getBoundingClientRect().width);
return () => {
// SAFETY: These are the same pointer event/listener pairs acquired above.
for (const [name, listener] of listeners) {
// SAFETY: Teardown uses the acquired pointer event/listener pairs.
element.removeEventListener(name, listener as EventListener);
}
observer.disconnect();
for (const id of captures) release(id);
captures.clear();
};
}),
(cleanup) => Effect.sync(cleanup),
).pipe(
// The stream owns listeners until Mount scope closes.
// oxlint-disable-next-line linteffect/no-effect-never
Effect.andThen(Effect.never),
),
);
}
export const ObserveSignalInput = defineStream('ObserveSignalInput', {
args: { role: ChartRole },
messages: [
Message.RecordedChartWidth,
Message.RecordedInputAvailability,
Message.StartedChartPointer,
Message.MovedChartPointer,
Message.EndedChartPointer,
Message.CancelledChartPointer,
],
// SAFETY: The tag guard below narrows the actual mounted SVG element.
execute: ({ element, role }) =>
element.tagName.toLowerCase() === 'svg'
? signalInputFacts(element as SVGSVGElement, role)
: Stream.make(Message.RecordedInputAvailability({ role, status: 'Unavailable' })),
});
view.ts
import { resolveSeriesStyle } from '@opsydyn/foldkit-viz/chart/theme';
import { axis, chartFrame, grid, pointSeries } from '@opsydyn/foldkit-viz/foldkit/cartesian';
import { selectionContainsValue } from '@opsydyn/foldkit-viz/interaction/selection';
import { Option, Schema } from 'effect';
import type { Document, Html, HtmlBuilder } from 'foldkit/html';
import { exampleTheme } from '#example/frame';
import { deriveComparisonChart } from './comparison';
import { comparisonPanel, comparisonLayers, baselineReferenceList } from './comparison-view';
import { currentSignalSource, deriveSignalChart, inspectionNotice, utc } from './derive';
import { eventPanel, eventGuideLayers } from './event-view';
import { ObserveSignalInput } from './input';
import { Message } from './message';
import { Model } from './model';
import type { ReadyModel, ChartRole } from './model';
import { readingText, sourceFreshness } from './quality';
import { qualityLayers, qualityLegend, qualityDetails } from './quality-view';
const labels = { overview: 'Latency overview', latency: 'Latency detail', errors: 'Error detail' };
const Key = Schema.Literals(['ArrowLeft', 'ArrowRight', 'Home', 'End', 'Escape']);
const theme = { ...exampleTheme, labelSize: 11 };
function signalChart(m: ReadyModel, role: ChartRole, h: HtmlBuilder<Message>): Html {
const chart = deriveSignalChart(m, role);
const { geometry, inspected } = chart;
const { layout } = geometry;
const { plot } = layout;
const colour =
role === 'errors'
? 'var(--chart-series-b, var(--coral))'
: 'var(--chart-series-a, var(--blue))';
const style = resolveSeriesStyle(
theme,
{ stroke: colour, fill: colour, strokeWidth: 2.5, pointRadius: 3 },
{},
{},
);
const visiblePin =
inspected !== null && inspected.time >= m.viewport[0] && inspected.time <= m.viewport[1];
const point = visiblePin ? geometry.points.filter((p) => p.key === inspected.id) : [];
const brush =
m.gesture._tag === 'Brushing'
? [
Math.min(m.gesture.anchorTime, m.gesture.currentTime),
Math.max(m.gesture.anchorTime, m.gesture.currentTime),
]
: null;
const interval = brush ?? (m.selection._tag === 'Interval' ? m.selection.domain : null);
const rect: readonly [number, number] | null =
interval === null
? null
: [Math.max(plot.left, layout.x(interval[0])), Math.min(plot.right, layout.x(interval[1]))];
const highlight =
rect !== null && rect[1] > rect[0]
? h.rect(
[
h.X(String(rect[0])),
h.Y(String(plot.top)),
h.Width(String(rect[1] - rect[0])),
h.Height(String(plot.height)),
h.Fill(colour),
h.FillOpacity(brush ? '0.1' : '0.16'),
h.Stroke(colour),
h.StrokeDasharray(brush ? '3 3' : '1 4'),
h.AriaHidden(true),
],
[],
)
: null;
const cursor =
visiblePin && inspected !== null
? h.line(
[
h.Class('signal-cursor'),
h.X1(String(layout.x(inspected.time))),
h.X2(String(layout.x(inspected.time))),
h.Y1(String(plot.top)),
h.Y2(String(plot.bottom)),
h.Stroke(theme.text),
h.StrokeDasharray('2 4'),
h.AriaHidden(true),
],
[],
)
: null;
return h.section(
[h.Class(`signal-plot signal-plot-${role}`), h.DataAttribute('chart-role', role)],
[
h.div(
[h.Class('signal-plot-heading')],
[
h.h2([], [labels[role]]),
h.span([], [role === 'errors' ? '% / ERROR RATE' : 'ms / RESPONSE TIME']),
],
),
chartFrame(
h,
{
layout,
title: labels[role],
description:
role === 'overview'
? 'Illustrative static telemetry. Drag to select a time interval.'
: 'Illustrative static telemetry. Drag to pan. Arrow keys inspect; Home and End move to visible boundaries.',
theme,
interactive: true,
onMount: ObserveSignalInput({ role }),
onKeyDown: (key) =>
Schema.is(Key)(key)
? Option.some(Message.PressedInspectionKey({ role, key }))
: Option.none(),
},
[
grid(h, { ...geometry, theme }),
h.defs(
[],
[
h.clipPath(
[h.Id(`signal-quality-clip-${role}`)],
[
h.rect(
[
h.X(String(plot.left)),
h.Y(String(plot.top)),
h.Width(String(plot.width)),
h.Height(String(plot.height)),
],
[],
),
],
),
],
),
...qualityLayers(
m,
role,
chart,
h,
new Set(deriveComparisonChart(m, role, layout).errorBars.map(({ mark }) => mark.key)),
),
h.g(
[h.Attribute('clip-path', `url(#signal-quality-clip-${role})`)],
[
highlight,
...comparisonLayers(m, role, layout, h),
...eventGuideLayers(m, role, layout, h),
// Single observations still have an honest visible mark, even without a line segment.
pointSeries(h, {
points: point,
styleFor: (p) =>
(role === 'errors' ? p.datum.errors : p.datum.latency)._tag === 'Estimated'
? { ...style, symbol: 'diamond', fill: 'var(--surface)' }
: style,
labelFor: (d) =>
`${d.id}, ${utc(d.time)}, ${readingText(d.latency)} ms, ${readingText(d.errors)}%`,
activeKey: m.inspection.key,
}),
cursor,
],
),
axis(h, {
layout,
orientation: 'bottom',
ticks: geometry.xTicks,
label: 'Time (UTC)',
format: (value) => utc(value).slice(11, 19),
theme,
}),
axis(h, {
layout,
orientation: 'left',
ticks: geometry.yTicks,
label: role === 'errors' ? 'Errors (%)' : 'Latency (ms)',
format: (value) =>
role === 'errors' ? String(Number(value.toPrecision(3))) : String(Math.round(value)),
theme,
}),
h.rect(
[
h.DataAttribute('signal-gesture', ''),
h.X(String(plot.left)),
h.Y(String(plot.top)),
h.Width(String(plot.width)),
h.Height(String(plot.height)),
h.Fill('transparent'),
h.Style({
'touch-action': 'none',
cursor: role === 'overview' ? 'crosshair' : 'grab',
}),
],
[],
),
],
),
...(chart.noDrawable
? [h.p([h.Class('signal-no-data')], ['No drawable measurements in this view.'])]
: []),
h.ul(
[h.Class('signal-reference-list')],
[
...baselineReferenceList(m, role, h),
...chart.thresholds.map((t) =>
h.li(
[h.Key(t.id), h.DataAttribute('threshold-id', t.id)],
[`Illustrative reference: ${t.label} · ${t.value} ${role === 'errors' ? '%' : 'ms'}`],
),
),
],
),
h.p(
[h.Class('signal-input-status')],
[
m.inputStatus[role] === 'Ready'
? role === 'overview'
? 'Drag a range · tap to inspect'
: 'Drag to pan · ← → inspect · Home / End'
: 'Input unavailable · keyboard and range controls remain available',
],
),
],
);
}
function readyView(m: ReadyModel, h: HtmlBuilder<Message>): Html {
const inspected = m.records.find((d) => d.id === m.inspection.key);
const lastIndex = m.records.length - 1;
const selected = m.selection._tag === 'Interval' ? m.selection.domain : m.bounds;
const lo = Math.max(
0,
m.records.findIndex((d) => d.time >= selected[0]),
);
const hi = Math.max(
lo,
m.records.findLastIndex((d) => d.time <= selected[1]),
);
const button = (label: string, message: Message, disabled = false) =>
h.button([h.Type('button'), h.OnClick(message), h.Disabled(disabled)], [label]);
const range = (id: string, label: string, value: number, toMessage: (index: number) => Message) =>
h.div(
[h.Class('signal-range')],
[
h.label([h.For(id)], [label, h.span([], [`Record ${value}`])]),
h.input([
h.Id(id),
h.Type('range'),
h.Min('0'),
h.Max(String(lastIndex)),
h.Step('1'),
h.Value(String(value)),
h.Disabled(lastIndex < 1),
h.OnInput((value) => toMessage(Number(value))),
]),
],
);
return h.div(
[h.Class('signal-desk')],
[
h.div(
[h.Class('signal-meta')],
[
h.span([], [`${m.records.length} RECORDS · GAPS EXPLICIT`]),
h.span([], [`${utc(m.bounds[0]).slice(0, 10)} / UTC`]),
h.span([], ['ILLUSTRATIVE · STATIC']),
],
),
h.section(
[
h.Class('signal-source-status'),
h.DataAttribute('freshness', sourceFreshness(m.snapshot)),
],
[
h.p([], [`Source: ${sourceFreshness(m.snapshot)} · ${m.snapshot.revision}`]),
h.p(
[],
[
`As of ${utc(m.snapshot.asOf)} · updated ${utc(m.snapshot.updatedAt)} · stale after ${m.snapshot.staleAfterMs} ms`,
],
),
h.div(
[h.Class('signal-controls')],
[
button('Fresh snapshot', Message.ClickedFreshnessScenario({ scenario: 'Fresh' })),
button('Stale snapshot', Message.ClickedFreshnessScenario({ scenario: 'Stale' })),
],
),
],
),
qualityLegend(h),
signalChart(m, 'overview', h),
h.div(
[h.Class('signal-ranges')],
[
range('signal-range-start', 'Range start', lo, (index) =>
Message.ChangedRangeStart({ index }),
),
range('signal-range-end', 'Range end', hi, (index) => Message.ChangedRangeEnd({ index })),
],
),
h.div(
[h.Class('signal-controls')],
[
button('Zoom in', Message.ClickedZoomIn(), m.viewport[1] - m.viewport[0] <= 1000),
button(
'Zoom out',
Message.ClickedZoomOut(),
m.viewport[0] === m.bounds[0] && m.viewport[1] === m.bounds[1],
),
button('Reset view', Message.ClickedResetView()),
button('Clear selection', Message.ClickedClearSelection()),
button(
'Pin inspection',
Message.ClickedPinInspection(),
inspected === undefined || m.inspection._tag === 'Pinned',
),
button(
'Resume inspection',
Message.ClickedResumeInspection(),
m.inspection._tag === 'Following',
),
],
),
h.div(
[h.Class('signal-view-state')],
[
h.p([], [`View: ${utc(m.viewport[0])} → ${utc(m.viewport[1])}`]),
h.p(
[],
[
m.selection._tag === 'Interval'
? `Selection: ${utc(m.selection.domain[0])} → ${utc(m.selection.domain[1])}`
: 'Selection: none',
],
),
],
),
h.section(
[h.Class('signal-inspector'), h.AriaLive('polite')],
[
h.p([h.Class('signal-inspection-notice')], [inspectionNotice(m)]),
h.p(
[h.Class('signal-reading-detail')],
[
`Source: ${sourceFreshness(m.snapshot)} · ${m.snapshot.revision} · as of ${utc(m.snapshot.asOf)} · updated ${utc(m.snapshot.updatedAt)} · stale after ${m.snapshot.staleAfterMs} ms`,
],
),
h.dl(
[],
[
h.div(
[],
[
h.dt([], ['Observation / UTC']),
h.dd([], [inspected ? `${inspected.id} / ${utc(inspected.time)}` : '—']),
],
),
h.div(
[],
[
h.dt([], ['Latency (ms)']),
h.dd([], [inspected ? readingText(inspected.latency) : '—']),
...(inspected
? [
h.p(
[h.Class('signal-reading-detail')],
[qualityDetails(inspected.latency, 'ms')],
),
]
: []),
],
),
h.div(
[],
[
h.dt([], ['Errors (%)']),
h.dd([], [inspected ? readingText(inspected.errors) : '—']),
...(inspected
? [
h.p(
[h.Class('signal-reading-detail')],
[qualityDetails(inspected.errors, '%')],
),
]
: []),
],
),
],
),
],
),
comparisonPanel(m, h),
eventPanel(m, deriveSignalChart(m, 'latency').geometry.layout, h),
signalChart(m, 'latency', h),
signalChart(m, 'errors', h),
h.details(
[h.Class('signal-records')],
[
h.summary([], [`Raw observations (${m.records.length})`]),
h.div(
[h.Class('signal-table-scroll')],
[
h.table(
[],
[
h.caption(
[],
[
`Illustrative system telemetry. Source: ${sourceFreshness(m.snapshot)} · ${m.snapshot.revision}. All records, including those outside the current view.`,
],
),
h.thead(
[],
[
h.tr(
[],
[
'Observation',
'Time (UTC)',
'Latency (ms)',
'Errors (%)',
'Latency quality',
'Error quality',
'State',
].map((label) => h.th([h.Scope('col')], [label])),
),
],
),
h.tbody(
[],
m.records.map((d) => {
const active = d.id === m.inspection.key;
const selected = selectionContainsValue(m.selection, 'x', d.time);
return h.tr(
[
h.Key(d.id),
h.DataAttribute('record-id', d.id),
h.Class(
active ? 'signal-row-inspected' : selected ? 'signal-row-selected' : '',
),
],
[
h.th([h.Scope('row')], [d.id]),
h.td([], [utc(d.time)]),
h.td([], [readingText(d.latency)]),
h.td([], [readingText(d.errors)]),
h.td([], [qualityDetails(d.latency, 'ms')]),
h.td([], [qualityDetails(d.errors, '%')]),
h.td(
[],
[
`${active ? 'Inspected' : ''}${active && selected ? ' · ' : ''}${selected ? 'Selected' : ''}${!active && !selected ? '—' : ''}`,
],
),
],
);
}),
),
],
),
],
),
],
),
h.details(
[h.Class('signal-source')],
[
h.summary([], ['Controlled state / source']),
h.pre([], [h.code([], [currentSignalSource(m)])]),
],
),
],
);
}
export const view = (model: Model, h: HtmlBuilder<Message>): Document => ({
title: 'Signal desk — Foldkit Viz',
body: Model.match(model, {
Empty: () =>
h.section(
[h.Class('signal-desk')],
[h.h2([], ['No observations']), h.p([], ['Provide records to explore a signal.'])],
),
Invalid: ({ error }) =>
h.section(
[h.Class('signal-desk')],
[h.h2([], ['Cannot display observations']), h.p([], [error])],
),
Ready: (m) => readyView(m, h),
}),
});