--- title: Progress --- # Progress Log *Append-only per constitution §5 — no deletions.* ```js const events = await FileAttachment("data/progress.json").json(); const data = Array.isArray(events) ? events : []; ``` ```js const authorFilter = view(Inputs.select( ["(all)", ...new Set(data.map(e => e.author ?? "unknown"))], { label: "Author" } )); const typeFilter = view(Inputs.select( ["(all)", ...new Set(data.map(e => e.event_type))], { label: "Event type" } )); const sinceFilter = view(Inputs.date({ label: "Since" })); ``` ```js const filtered = data.filter(e => (authorFilter === "(all)" || (e.author ?? "unknown") === authorFilter) && (typeFilter === "(all)" || e.event_type === typeFilter) && (!sinceFilter || new Date(e.created_at) >= sinceFilter) ); display(html`

${filtered.length} events shown (append-only, no deletions).

`); display(Inputs.table(filtered.map(e => ({ Time: new Date(e.created_at).toLocaleString(), Type: e.event_type, Author: e.author ?? "—", Summary: e.summary, })), { rows: 50 })); ``` ## Event Volume (Last 30 Days) ```js import * as Plot from "npm:@observablehq/plot"; const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - 30); const byDay = data .filter(e => new Date(e.created_at) >= cutoff) .reduce((acc, e) => { const day = e.created_at.slice(0, 10); acc[day] = (acc[day] ?? 0) + 1; return acc; }, {}); display(Plot.plot({ title: "Progress Events per Day (30-day window)", x: { label: "Date", tickRotate: -30 }, y: { label: "Events", grid: true }, marks: [ Plot.areaY( Object.entries(byDay).sort().map(([day, count]) => ({ day, count })), { x: "day", y: "count", fill: "steelblue", fillOpacity: 0.3 } ), Plot.lineY( Object.entries(byDay).sort().map(([day, count]) => ({ day, count })), { x: "day", y: "count", stroke: "steelblue" } ), Plot.ruleY([0]), ], marginBottom: 60, width: 750, })); ```