diff --git a/README.md b/README.md
index a7c6d14..9f58159 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,8 @@ Deployed at https://meadows.apoena.dev
- [ADR-0004](./docs/adr/0004-rate-rules-not-formulas.md) — simulation behaviour
comes from a small fixed vocabulary of rules over Information Links, not
free-form formulas.
+ - [ADR-0005](./docs/adr/0005-uplot-behaviour-chart.md) — uPlot draws the
+ behaviour-over-time chart; a charting dependency that earns its keep.
## Stack
diff --git a/docs/adr/0005-uplot-behaviour-chart.md b/docs/adr/0005-uplot-behaviour-chart.md
new file mode 100644
index 0000000..730b7a1
--- /dev/null
+++ b/docs/adr/0005-uplot-behaviour-chart.md
@@ -0,0 +1,55 @@
+# uPlot draws the behaviour-over-time chart
+
+_Part of [meadows](../../README.md) · see [DESIGN.md](../../DESIGN.md)._
+
+The Results panel (ADR-0004's phase 2) traces each Stock's value over time. It
+began as a **hand-built SVG** — a deliberate lean-substrate choice, no charting
+dependency. But a polyline is all that substrate could draw: no time axis, no
+gridlines, and a legend that could only show a Stock's _final_ value, never the
+value under the cursor. For a tool whose whole point is reading _behaviour over
+time_, "what is this Stock at t = 40?" is the question you most want to answer by
+pointing at the curve.
+
+## Decision
+
+Adopt **uPlot** (`uplot`, ~15 KB gzip) — a small, fast canvas time-series
+library — for the behaviour-over-time chart. It earns the dependency with the
+three things the SVG could not give: a real **time axis** with ticks and
+gridlines, and a live legend that **reads each Stock's value at the hovered
+time**.
+
+uPlot is imperative and canvas-based, so it is wrapped in **`SimChart.vue`**,
+which owns its lifecycle (build on mount, `setData` on recompute, rebuild on a
+track-set change, `ResizeObserver` for width, destroy on unmount). `ResultsPanel`
+stays declarative: it assembles plain aligned data (Stocks only — the system's
+memory, per ADR-0004) and hands it down.
+
+## Considered Options
+
+- **Keep the hand-built SVG** (zero deps) — rejected: re-implementing axes,
+ tick placement, a hover cursor, and hit-testing _is_ rebuilding a charting
+ library, badly. The lean-substrate note was a code comment, not an ADR; nothing
+ prior is contradicted by spending one dependency where it clearly pays.
+- **`@gouvfr/dsfr-chart`** (the original prompt) — rejected: it requires the
+ entire DSFR design system (CSS + JS API) and embeds its _own_ Vue and Chart.js,
+ so we would ship Vue twice and a second design language clashing with DaisyUI.
+ It is built for French-government dashboards (region maps, gauges), not generic
+ time series.
+- **Chart.js / vue-chartjs** — viable and same engine class, but heavier; uPlot
+ is lighter and faster for many series and live-updating data, which is exactly
+ the simulation's shape.
+- **uPlot (chosen)** — lightest credible option that gives the axis + hover.
+
+## Consequences
+
+- This is the project's **first runtime charting dependency**. The price, as with
+ Vue Flow (ADR-0002), is a thin wrapper layer: `SimChart.vue` translates between
+ reactive props and uPlot's imperative API.
+- The x scale runs with **`time: false`** — x is _simulation_ time (`start…stop`),
+ not wall-clock, which uPlot would otherwise format as calendar dates.
+- Track colours are read from the **DaisyUI theme variables**. The app ships a
+ single `light` theme, so they are resolved once at build time; a future dark
+ theme would need the plot re-initialised on theme change.
+- Swapping charting libraries later means rewriting only `SimChart.vue` —
+ `ResultsPanel` passes plain `[times, ...tracks]` aligned data and knows nothing
+ of uPlot.
diff --git a/package.json b/package.json
index 6e9b107..f77a7a9 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"daisyui": "^5.5.23",
"idb": "^8.0.3",
"pinia": "^3.0.4",
+ "uplot": "^1.6.32",
"vue": "^3.5.34"
},
"devDependencies": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 04146de..7a19826 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,6 +26,9 @@ importers:
pinia:
specifier: ^3.0.4
version: 3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3))
+ uplot:
+ specifier: ^1.6.32
+ version: 1.6.32
vue:
specifier: ^3.5.34
version: 3.5.38(typescript@6.0.3)
@@ -939,6 +942,9 @@ packages:
undici-types@7.18.2:
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
+ uplot@1.6.32:
+ resolution: {integrity: sha512-KIMVnG68zvu5XXUbC4LQEPnhwOxBuLyW1AHtpm6IKTXImkbLgkMy+jabjLgSLMasNuGGzQm/ep3tOkyTxpiQIw==}
+
vite@8.0.16:
resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -1709,6 +1715,8 @@ snapshots:
undici-types@7.18.2: {}
+ uplot@1.6.32: {}
+
vite@8.0.16(@types/node@24.13.2)(jiti@2.7.0):
dependencies:
lightningcss: 1.32.0
diff --git a/src/components/SimChart.vue b/src/components/SimChart.vue
new file mode 100644
index 0000000..94e226b
--- /dev/null
+++ b/src/components/SimChart.vue
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+