Skip to content

Commit d27c7b7

Browse files
committed
Enhance annotation freshness demos & visuals
Revamp the annotation freshness demos across ConversationArcPage and the talk-track blog entry to make lifecycle states more explicit and annotations easier to read. Key changes: - Replace procedurally generated sine-wave demo data with hand-tuned monthly samples so callouts target visible peaks. - Convert annotation objects to use chart accessors (month/value) and add stable ids, clearer labels and notes. - Introduce per-source base colors, functions to derive muted colors by freshness, and badge colors/suffixes for UI. - Update freshness preview logic to compute state objects, filter out expired annotations, and assign color/label/lifecycle metadata before rendering. - Improve UI: increase chart heights/margins, extend slider range and default "now" date, and add a status grid showing each annotation's badge, author, age (days) and TTL; expired items are visually de-emphasized and hidden from the chart. These edits are meant to better demonstrate annotation provenance/freshness and mirror the intended M2 behavior (computeAnnotationFreshness and default expired handling).
1 parent 42a1d47 commit d27c7b7

2 files changed

Lines changed: 254 additions & 77 deletions

File tree

docs/src/blog/entries/talk-track-intelligence.js

Lines changed: 110 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -212,33 +212,72 @@ function ConversationArcLiveDemo() {
212212

213213
// ─── Annotation provenance freshness scrubber ─────────────────────────────
214214

215-
const STALE_DEMO_DATA = Array.from({ length: 12 }, (_, i) => ({
216-
month: i + 1,
217-
value: 250 + Math.sin(i * 0.7) * 80 + i * 12,
218-
}))
215+
// Hand-tuned spikes so annotations sit on visible peaks rather than
216+
// getting lost in noise.
217+
const STALE_DEMO_DATA = [
218+
{ month: 1, value: 280 },
219+
{ month: 2, value: 310 },
220+
{ month: 3, value: 420 }, // alice's spike
221+
{ month: 4, value: 350 },
222+
{ month: 5, value: 360 },
223+
{ month: 6, value: 370 },
224+
{ month: 7, value: 510 }, // AI's anomaly
225+
{ month: 8, value: 390 },
226+
{ month: 9, value: 400 },
227+
{ month: 10, value: 420 },
228+
{ month: 11, value: 450 },
229+
{ month: 12, value: 470 },
230+
]
219231

232+
// Field names match the chart's accessors (`month`/`value`). That's how
233+
// the annotation layer resolves data → screen coordinates.
220234
const RAW_ANNOTATIONS = [
221235
withProvenance(
222-
{ type: "callout", x: 3, y: 280, dx: 30, dy: -40, note: { label: "Hand-placed", title: "Spike" } },
236+
{
237+
type: "callout",
238+
id: "alice-spike",
239+
month: 3,
240+
value: 420,
241+
label: "Hand-placed spike",
242+
note: "Marked when the product launched.",
243+
dx: 50,
244+
dy: -45,
245+
},
223246
{
224247
provenance: { author: "alice", source: "user", createdAt: "2026-02-15T12:00:00Z" },
225248
lifecycle: { ttlHint: "P30D", anchor: "semantic" },
226249
}
227250
),
228251
withProvenance(
229-
{ type: "callout", x: 7, y: 410, dx: -30, dy: -50, note: { label: "AI tag (low conf.)", title: "Anomaly" } },
252+
{
253+
type: "callout",
254+
id: "ai-anomaly",
255+
month: 7,
256+
value: 510,
257+
label: "AI anomaly tag",
258+
note: "Flagged by model-v3 (confidence 0.62).",
259+
dx: -55,
260+
dy: -45,
261+
},
230262
{
231263
provenance: { author: "model-v3", source: "ai", confidence: 0.62, createdAt: "2026-03-10T09:00:00Z" },
232264
lifecycle: { ttlHint: "P14D", anchor: "fixed" },
233265
}
234266
),
235267
]
236268

237-
const FRESHNESS_STYLE = {
238-
fresh: { opacity: 1 },
239-
aging: { opacity: 0.55 },
240-
stale: { opacity: 0.35, strokeDasharray: "4 4" },
241-
expired: { opacity: 0.18, strokeDasharray: "2 4" },
269+
const FRESHNESS_BASE_COLOR = { user: "#3a8eff", ai: "#d49a00" }
270+
const FRESHNESS_COLOR = {
271+
fresh: (base) => base,
272+
aging: () => "#8a96a3",
273+
stale: () => "#b0b0b0",
274+
}
275+
const FRESHNESS_SUFFIX = { fresh: "", aging: " · aging", stale: " · stale" }
276+
const FRESHNESS_BADGE = {
277+
fresh: "#2d8a4a",
278+
aging: "#d49a00",
279+
stale: "#a0a0a0",
280+
expired: "#c43d3d",
242281
}
243282

244283
function parseIsoDuration(s) {
@@ -260,16 +299,27 @@ function previewFreshness(ann, nowMs) {
260299
}
261300

262301
function FreshnessLiveDemo() {
263-
const [nowIso, setNowIso] = useState("2026-04-01T00:00:00Z")
302+
const [nowIso, setNowIso] = useState("2026-03-10T00:00:00Z")
264303
const nowMs = Date.parse(nowIso)
265-
const annotated = RAW_ANNOTATIONS.map((a) => {
266-
const freshness = previewFreshness(a, nowMs)
267-
return {
268-
...a,
269-
lifecycle: { ...a.lifecycle, freshness },
270-
...FRESHNESS_STYLE[freshness],
271-
}
272-
})
304+
305+
const states = RAW_ANNOTATIONS.map((a) => ({
306+
raw: a,
307+
freshness: previewFreshness(a, nowMs),
308+
}))
309+
310+
// Expired annotations drop out of the array — mirrors M2's default
311+
// of hiding them unless `showExpiredAnnotations` is on.
312+
const visible = states
313+
.filter((s) => s.freshness !== "expired")
314+
.map(({ raw, freshness }) => {
315+
const base = FRESHNESS_BASE_COLOR[raw.provenance.source] ?? "#5a5a5a"
316+
return {
317+
...raw,
318+
label: raw.label + FRESHNESS_SUFFIX[freshness],
319+
color: FRESHNESS_COLOR[freshness](base),
320+
lifecycle: { ...raw.lifecycle, freshness },
321+
}
322+
})
273323

274324
return (
275325
<div style={card}>
@@ -278,9 +328,10 @@ function FreshnessLiveDemo() {
278328
xAccessor="month"
279329
yAccessor="value"
280330
showPoints
281-
height={240}
331+
height={300}
332+
margin={{ top: 28, right: 28, bottom: 36, left: 52 }}
282333
title='"Now" scrubber: watch annotations age'
283-
annotations={annotated}
334+
annotations={visible}
284335
/>
285336
<div style={{ marginTop: 10 }}>
286337
<label style={{ display: "block", fontSize: 12, color: "var(--text-secondary)" }}>
@@ -289,18 +340,48 @@ function FreshnessLiveDemo() {
289340
<input
290341
type="range"
291342
min={Date.parse("2026-02-15T00:00:00Z")}
292-
max={Date.parse("2026-07-01T00:00:00Z")}
343+
max={Date.parse("2026-08-15T00:00:00Z")}
293344
step={86_400_000}
294345
value={nowMs}
295346
onChange={(e) => setNowIso(new Date(parseInt(e.target.value, 10)).toISOString())}
296347
style={{ width: "100%" }}
297348
/>
298-
<div style={{ fontSize: 12, color: "var(--text-secondary)", marginTop: 4 }}>
299-
{annotated.map((a) => (
300-
<span key={a.note.title} style={{ marginRight: 14 }}>
301-
<strong>{a.note.title}</strong>: {a.lifecycle.freshness}
302-
</span>
303-
))}
349+
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginTop: 10 }}>
350+
{states.map(({ raw, freshness }) => {
351+
const ageDays = Math.max(
352+
0,
353+
Math.floor((nowMs - Date.parse(raw.provenance.createdAt)) / (24 * 60 * 60 * 1000))
354+
)
355+
return (
356+
<div
357+
key={raw.id}
358+
style={{
359+
background: "var(--surface-3)",
360+
borderRadius: 6,
361+
padding: "8px 10px",
362+
fontSize: 12,
363+
opacity: freshness === "expired" ? 0.55 : 1,
364+
}}
365+
>
366+
<div style={{ display: "flex", justifyContent: "space-between", gap: 8, marginBottom: 2 }}>
367+
<strong>{raw.label}</strong>
368+
<span style={{
369+
background: FRESHNESS_BADGE[freshness],
370+
color: "white",
371+
padding: "1px 7px",
372+
borderRadius: 999,
373+
fontSize: 11,
374+
fontWeight: 600,
375+
}}>{freshness}</span>
376+
</div>
377+
<div style={{ color: "var(--text-secondary)" }}>
378+
by <code>{raw.provenance.author}</code>
379+
{" · "}{ageDays} day{ageDays === 1 ? "" : "s"} old
380+
{" · "}TTL <code>{raw.lifecycle.ttlHint}</code>
381+
</div>
382+
</div>
383+
)
384+
})}
304385
</div>
305386
</div>
306387
</div>

0 commit comments

Comments
 (0)