@@ -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.
220234const 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
244283function parseIsoDuration ( s ) {
@@ -260,16 +299,27 @@ function previewFreshness(ann, nowMs) {
260299}
261300
262301function 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