@kky42/pievo-server 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/LoopView-DjVHUyrR.js +59 -0
  3. package/.output/public/assets/{Modal-B3xpEtcf.js → Modal-CCRvx959.js} +1 -1
  4. package/.output/public/assets/{SignIn-9vsaKv_p.js → SignIn-Qxw5zDFq.js} +1 -1
  5. package/.output/public/assets/{actionUi-DDDfD0WX.js → actionUi-CXxhzYe2.js} +1 -1
  6. package/.output/public/assets/app-D1cij_NJ.css +2 -0
  7. package/.output/public/assets/{artifactView-BV-yQVrj.js → artifactView-Cd5wWPil.js} +1 -1
  8. package/.output/public/assets/index-BRN_CQ8U.js +12 -0
  9. package/.output/public/assets/{invite._token-5zhVSA26.js → invite._token-BIxr8DlP.js} +1 -1
  10. package/.output/public/assets/{invite._token-C3t4JfAY.js → invite._token-j2BTCeG1.js} +1 -1
  11. package/.output/public/assets/lifecycleUi-Qy_8beQK.js +1 -0
  12. package/.output/public/assets/{loopApi-C_Dtw-nL.js → loopApi-CT1mMhlb.js} +1 -1
  13. package/.output/public/assets/loops._loopId-BRBQzioy.js +4 -0
  14. package/.output/public/assets/loops._loopId_.runs._runId-CMnOJkem.js +3 -0
  15. package/.output/public/assets/{notifyFns-CF12Yboc.js → notifyFns-DYD4EKaG.js} +1 -1
  16. package/.output/public/assets/{preload-helper--VCqDT5m.js → preload-helper-DFkkpP6W.js} +1 -1
  17. package/.output/public/assets/{routes-DsdcURfk.js → routes-3nuEJGdo.js} +1 -1
  18. package/.output/public/assets/{routes-WVPuW976.js → routes-DORLim-C.js} +1 -1
  19. package/.output/public/assets/{t._teamId-BdVLnwih.js → t._teamId-F5f4K8kd.js} +1 -1
  20. package/.output/public/assets/{t._teamId-C9uEyAh_.js → t._teamId-XfE_S1Fo.js} +1 -1
  21. package/.output/public/assets/ui-Dhsx6SW0.js +1 -0
  22. package/.output/server/_libs/d3-shape.mjs +130 -1
  23. package/.output/server/_libs/react-is.mjs +48 -0
  24. package/.output/server/_libs/recharts+victory-vendor.mjs +2210 -782
  25. package/.output/server/_ssr/{DashboardView-DJMpoucm.mjs → DashboardView-BF7PciP7.mjs} +11 -8
  26. package/.output/server/_ssr/{LoopPlaybook-WFNer22Y.mjs → LoopPlaybook-DC7QVvPY.mjs} +2 -2
  27. package/.output/server/_ssr/{LoopView-Dsc6oik9.mjs → LoopView-DwSrm9Ja.mjs} +418 -224
  28. package/.output/server/_ssr/{Modal-Bc_5TYGr.mjs → Modal-MaCG52Pv.mjs} +2 -2
  29. package/.output/server/_ssr/{SignIn-2qr-vtR5.mjs → SignIn-RmpWm9uy.mjs} +3 -3
  30. package/.output/server/_ssr/{actionUi-BKF6xI3d.mjs → actionUi-BN55TNYE.mjs} +2 -2
  31. package/.output/server/_ssr/{adapters-Bwuh6uek.mjs → adapters-BoLkOf1T.mjs} +12 -6
  32. package/.output/server/_ssr/{artifactFiles-Cp1IBHne.mjs → artifactFiles-BCaIIrrT.mjs} +3 -3
  33. package/.output/server/_ssr/{artifactView-DX4P3Krh.mjs → artifactView-iuPsp5aa.mjs} +2 -2
  34. package/.output/server/_ssr/artifacts-jQ-bfdhI.mjs +2 -0
  35. package/.output/server/_ssr/{auth-uQ19WsD1.mjs → auth-30Z4lV1w.mjs} +9 -9
  36. package/.output/server/_ssr/{boot-DPoPmSLh.mjs → boot-3BNKujyp.mjs} +392 -193
  37. package/.output/server/_ssr/boot-CsEFT53K.mjs +2 -0
  38. package/.output/server/_ssr/{bun-sqlite-dialect-BW9W1_Ps-CqcLCApi.mjs → bun-sqlite-dialect-BW9W1_Ps-_7tfuNRi.mjs} +2 -2
  39. package/.output/server/_ssr/{d1-sqlite-dialect-BLC8LXE6-AuWb61y8.mjs → d1-sqlite-dialect-BLC8LXE6-ucDUbeBk.mjs} +2 -2
  40. package/.output/server/_ssr/db-CXownd1H.mjs +2 -0
  41. package/.output/server/_ssr/{dist-CtYTvPCD.mjs → dist-Cd6nlLlO.mjs} +2 -2
  42. package/.output/server/_ssr/{factory-BtqxMS8a.mjs → factory-CWWwV7XZ.mjs} +2 -2
  43. package/.output/server/_ssr/{invite._token-DtAfQyfP.mjs → invite._token-BKlz0R2f.mjs} +5 -5
  44. package/.output/server/_ssr/{invite._token-BlbVjz-o.mjs → invite._token-CWl6kipD.mjs} +2 -2
  45. package/.output/server/_ssr/{invite._token-DBttmaDb.mjs → invite._token-SdWXemIc.mjs} +6 -6
  46. package/.output/server/_ssr/{kysely-adapter-bSpTlW7Z.mjs → kysely-adapter-C8iS51DB.mjs} +1 -1
  47. package/.output/server/_ssr/{lifecycleUi-Djb44Aj3.mjs → lifecycleUi-DgBQAYzp.mjs} +2 -1
  48. package/.output/server/_ssr/logger-Gy-47z3k.mjs +2 -0
  49. package/.output/server/_ssr/{loopApi-7MTSs6hy.mjs → loopApi-8_KgBapr.mjs} +6 -2
  50. package/.output/server/_ssr/{loopApi-BXRBFGWh.mjs → loopApi-ABi9SgWS.mjs} +23 -13
  51. package/.output/server/_ssr/{loops._loopId-6Q0DMXU0.mjs → loops._loopId-SON_t43g.mjs} +4 -4
  52. package/.output/server/_ssr/{loops._loopId-C0x0SE8F.mjs → loops._loopId-p1KWsvMM.mjs} +13 -18
  53. package/.output/server/_ssr/{loops._loopId_.runs._runId-CkM0-ckn.mjs → loops._loopId_.runs._runId-PUGBrD5q.mjs} +22 -16
  54. package/.output/server/_ssr/{loops._loopId_.runs._runId-BOJBJu_a.mjs → loops._loopId_.runs._runId-Ymy4lwee.mjs} +4 -4
  55. package/.output/server/_ssr/{machineFns-CQuvvdXx.mjs → machineFns-BZ9puxDt.mjs} +3 -3
  56. package/.output/server/_ssr/{node-sqlite-dialect-ZOyD6D7F.mjs → node-sqlite-dialect-D_3JmkMo.mjs} +2 -2
  57. package/.output/server/_ssr/{notifyFns-BOeYZDRp.mjs → notifyFns-BUCePuF3.mjs} +3 -3
  58. package/.output/server/_ssr/{notifyFns-UFZ2j7Fv.mjs → notifyFns-DuPR1zd7.mjs} +6 -6
  59. package/.output/server/_ssr/{router-jnPelPNh.mjs → router-Czxvra6l.mjs} +44 -37
  60. package/.output/server/_ssr/{routes-CXnbFjB_.mjs → routes-ClZEdsnB.mjs} +6 -6
  61. package/.output/server/_ssr/{routes-Bx0bRxt9.mjs → routes-ComqHvCL.mjs} +2 -2
  62. package/.output/server/_ssr/{routes-CMAUcUsZ.mjs → routes-D5jJJ-30.mjs} +5 -5
  63. package/.output/server/_ssr/runDiff--qLtU4FI.mjs +2 -0
  64. package/.output/server/_ssr/ssr.mjs +52 -48
  65. package/.output/server/_ssr/store-40eVzW1S.mjs +2 -0
  66. package/.output/server/_ssr/{t._teamId-Bsm4WrJl.mjs → t._teamId-BgHV_KFP.mjs} +6 -6
  67. package/.output/server/_ssr/{t._teamId-D9nCEa2h.mjs → t._teamId-DDERNgfn.mjs} +2 -2
  68. package/.output/server/_ssr/{t._teamId-36z-mmyw.mjs → t._teamId-DRJvKHZn.mjs} +5 -5
  69. package/.output/server/_ssr/{teamFns-DAx4a1z4.mjs → teamFns-Bp81UO-x.mjs} +3 -3
  70. package/.output/server/_ssr/{teamFns-BBIbatdZ.mjs → teamFns-bm82twd5.mjs} +2 -2
  71. package/.output/server/_ssr/tokens-CbfoRC_s.mjs +2 -0
  72. package/.output/server/_ssr/{ui-B6mE5-5v.mjs → ui-Ww2tTM2u.mjs} +9 -2
  73. package/.output/server/{_tanstack-start-manifest_v-5iQOchGB.mjs → _tanstack-start-manifest_v-DwKENYCN.mjs} +28 -28
  74. package/.output/server/index.mjs +114 -114
  75. package/package.json +1 -1
  76. package/.output/public/assets/LoopView-Pbp2YtUu.js +0 -55
  77. package/.output/public/assets/app-CStTBRt2.css +0 -2
  78. package/.output/public/assets/index-DT_reRxT.js +0 -12
  79. package/.output/public/assets/lifecycleUi-CMFw5Nlj.js +0 -1
  80. package/.output/public/assets/loops._loopId-6JlPOCsY.js +0 -4
  81. package/.output/public/assets/loops._loopId_.runs._runId-wFUMHN9w.js +0 -3
  82. package/.output/public/assets/ui-Gb1413-F.js +0 -1
  83. package/.output/server/_ssr/artifacts-9G_Ai7vO.mjs +0 -2
  84. package/.output/server/_ssr/boot-Zz5AJgfy.mjs +0 -2
  85. package/.output/server/_ssr/db-BMANvbZu.mjs +0 -2
  86. package/.output/server/_ssr/logger-CR1a0flR.mjs +0 -2
  87. package/.output/server/_ssr/runDiff-CDPvl_cY.mjs +0 -2
  88. package/.output/server/_ssr/store-DDOk2Ty_.mjs +0 -2
  89. package/.output/server/_ssr/tokens-7KEKpLnu.mjs +0 -2
@@ -1,29 +1,17 @@
1
1
  import { o as __toESM } from "../_runtime.mjs";
2
2
  import { g as require_jsx_runtime, h as require_react } from "../_libs/@base-ui/react+[...].mjs";
3
- import { ht as isTaskPath } from "./boot-DPoPmSLh.mjs";
4
- import { A as tsShort, v as fmt, w as md, y as fnum } from "./ui-B6mE5-5v.mjs";
5
- import { s as getArtifacts } from "./loopApi-7MTSs6hy.mjs";
3
+ import { Bt as parseChartSpec, _t as isTaskPath, c as CHART_ATTRS } from "./boot-3BNKujyp.mjs";
4
+ import { A as tsShort, v as fmt, w as md, y as fnum } from "./ui-Ww2tTM2u.mjs";
5
+ import { l as getChartRuns, s as getArtifacts } from "./loopApi-8_KgBapr.mjs";
6
6
  import { t as artifactKind } from "./artifactKind-BW0isV8L.mjs";
7
7
  import { t as purify } from "../_libs/dompurify.mjs";
8
- import { i as isMarkdown, r as ViewerHead, t as ArtifactBody } from "./artifactView-DX4P3Krh.mjs";
9
- import { n as ModalHead, t as Modal } from "./Modal-Bc_5TYGr.mjs";
8
+ import { i as isMarkdown, r as ViewerHead, t as ArtifactBody } from "./artifactView-iuPsp5aa.mjs";
9
+ import { n as ModalHead, t as Modal } from "./Modal-MaCG52Pv.mjs";
10
10
  import { n as import_lib, t as esm_default } from "../_libs/html-react-parser+[...].mjs";
11
- import { a as Area, c as Tooltip, i as XAxis, l as ResponsiveContainer, n as LineChart, o as Line, r as YAxis, s as CartesianGrid, t as AreaChart } from "../_libs/recharts+victory-vendor.mjs";
12
- //#region node_modules/.nitro/vite/services/ssr/assets/LoopView-Dsc6oik9.js
11
+ import { a as Scatter, c as CartesianGrid, d as ResponsiveContainer, i as XAxis, l as Tooltip, n as ScatterChart, o as Area, r as YAxis, s as Line, t as ComposedChart, u as Legend } from "../_libs/recharts+victory-vendor.mjs";
12
+ //#region node_modules/.nitro/vite/services/ssr/assets/LoopView-DwSrm9Ja.js
13
13
  var import_react = /* @__PURE__ */ __toESM(require_react());
14
14
  var import_jsx_runtime = require_jsx_runtime();
15
- /** Parse a `<loop-chart series="key:label:unit, ...">` attribute into MetricField[]. */
16
- function parseSeries(attr) {
17
- if (!attr) return [];
18
- return attr.split(",").map((s) => {
19
- const [key, label, unit] = s.split(":").map((x) => x.trim());
20
- return {
21
- key: key ?? "",
22
- ...label ? { label } : {},
23
- ...unit ? { unit } : {}
24
- };
25
- }).filter((f) => f.key);
26
- }
27
15
  function buildBindingContext(runs) {
28
16
  const latest = {};
29
17
  const seen = /* @__PURE__ */ new Set();
@@ -53,49 +41,72 @@ function lookup(ctx, path) {
53
41
  const parts = path.split(".");
54
42
  if (parts[0] === "latest" && parts[1]) return ctx.latest[parts[1]];
55
43
  }
56
- /** Per-run numeric metrics snapshots → one series per numeric field (chronological). */
57
- function numericSeries(runsNewestFirst) {
58
- const chron = (runsNewestFirst ?? []).slice().reverse();
59
- const series = {};
60
- for (const r of chron) {
61
- const obj = r.metrics && typeof r.metrics === "object" && !Array.isArray(r.metrics) ? r.metrics : {};
62
- for (const k in obj) {
63
- const v = obj[k];
64
- if (typeof v === "number" && isFinite(v)) (series[k] ??= []).push({
65
- t: r.ts,
66
- v
67
- });
68
- }
44
+ var finiteMetric = (run, key) => {
45
+ const value = run.metrics?.[key];
46
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
47
+ };
48
+ var chronological = (runs) => runs.slice().sort((a, b) => a.runIndex - b.runIndex);
49
+ function seriesChartRows(runs, keys, x) {
50
+ return chronological(runs).flatMap((run) => {
51
+ const values = keys.map((key) => [key, finiteMetric(run, key)]);
52
+ if (!values.some(([, value]) => value != null)) return [];
53
+ const row = {
54
+ __x: x === "time" ? run.ts : run.runIndex,
55
+ __runIndex: run.runIndex,
56
+ __status: run.status
57
+ };
58
+ for (const [key, value] of values) if (value != null) row[key] = value;
59
+ return [row];
60
+ });
61
+ }
62
+ function scatterChartRows(runs, xKey, yKey) {
63
+ return chronological(runs).flatMap((run) => {
64
+ const x = finiteMetric(run, xKey);
65
+ const y = finiteMetric(run, yKey);
66
+ return x == null || y == null ? [] : [{
67
+ x,
68
+ y,
69
+ runIndex: run.runIndex,
70
+ status: run.status,
71
+ ts: run.ts
72
+ }];
73
+ });
74
+ }
75
+ function progressChartRows(runs, yKey) {
76
+ return chronological(runs).flatMap((run) => {
77
+ const y = finiteMetric(run, yKey);
78
+ return y == null ? [] : [{
79
+ x: run.runIndex,
80
+ y,
81
+ runIndex: run.runIndex,
82
+ status: run.status,
83
+ ts: run.ts
84
+ }];
85
+ });
86
+ }
87
+ /** Running best follows persisted kept decisions only. There is no renderer-side keep threshold. */
88
+ function runningBest(rows, direction) {
89
+ let best = null;
90
+ const out = [];
91
+ for (const row of rows) {
92
+ if (row.status === "kept") best = best == null ? row.y : direction === "min" ? Math.min(best, row.y) : Math.max(best, row.y);
93
+ if (best != null) out.push({
94
+ x: row.x,
95
+ y: best
96
+ });
69
97
  }
70
- return series;
98
+ return out;
71
99
  }
72
- /**
73
- * Merge per-key series into the row-oriented array Recharts consumes
74
- * (`[{__t, k1, k2}, …]`, chronological). Rows are the union of the requested
75
- * keys' timestamps - a run that reported only some keys leaves the others
76
- * absent on that row (the chart bridges the gap via `connectNulls`).
77
- */
78
- function seriesRows(data, keys) {
79
- const ts = /* @__PURE__ */ new Set();
80
- for (const k of keys) for (const p of data[k] ?? []) ts.add(p.t);
81
- const sorted = [...ts].sort();
82
- const idx = new Map(sorted.map((t, i) => [t, i]));
83
- const rows = sorted.map((t) => ({ __t: t }));
84
- for (const k of keys) for (const p of data[k] ?? []) rows[idx.get(p.t)][k] = p.v;
85
- return rows;
100
+ function chartDomain(values, requested) {
101
+ if (Array.isArray(requested)) return requested;
102
+ if (!values.length) return ["auto", "auto"];
103
+ const min = Math.min(...values);
104
+ const max = Math.max(...values);
105
+ if (requested === "zero") return [Math.min(0, min), Math.max(0, max)];
106
+ const span = max - min;
107
+ const pad = span > 0 ? span * .1 : Math.max(Math.abs(max) * .05, 1);
108
+ return [min - pad, max + pad];
86
109
  }
87
- /**
88
- * `<loop-chart series="mrr:MRR:$, paid:Paid">` - the loop's run-history trend,
89
- * rendered by Recharts in the shadcn/ui-charts grammar, themed entirely by the
90
- * theme tokens (`--color-chart-1..5` ramp, hairline grid, mono ticks,
91
- * flat tooltip - no shadows, no decorative color). One series renders as a
92
- * gradient area; multiple series render as plain lines (overlapping translucent
93
- * fills go muddy in a monochrome palette). A single-point series renders a dot
94
- * instead of nothing, so a young loop's dashboard is never blank.
95
- *
96
- * `data` is the shared `numericSeries(runs)` map computed once by LoopView.
97
- */
98
- /** Distinct strokes for multiple series - display ink first, then the signal colors. */
99
110
  var STROKES = [
100
111
  "var(--color-chart-1)",
101
112
  "var(--color-chart-2)",
@@ -103,13 +114,6 @@ var STROKES = [
103
114
  "var(--color-chart-4)",
104
115
  "var(--color-chart-5)"
105
116
  ];
106
- /**
107
- * Fixed pixel height: ResponsiveContainer tracks the CONTAINER width while the
108
- * height stays constant - the fix for the old stretched-viewBox renderer, which
109
- * scaled like an image (≈4px-fat strokes and a ~340px-tall chart at the loop
110
- * page's full dashboard width). Also the initial render size before the
111
- * ResizeObserver measures (and the only size in a non-browser render).
112
- */
113
117
  var HEIGHT = 190;
114
118
  var INITIAL = {
115
119
  width: 640,
@@ -129,163 +133,327 @@ var TOOLTIP_STYLE = {
129
133
  fontFamily: "var(--font-mono)",
130
134
  fontSize: 11
131
135
  };
132
- /** "$12.4k" / "40℃" / "7" - the unit placement the legend has always used. */
133
- var withUnit = (v, unit) => unit === "$" ? `$${fnum(v)}` : `${fnum(v)}${unit}`;
134
- function LoopChart({ data, series }) {
135
- const gradientId = (0, import_react.useId)();
136
- if (!series.length) return null;
137
- const plotted = series.map((f) => ({
138
- field: f,
139
- pts: data[f.key] ?? []
140
- })).filter((s) => s.pts.length >= 1);
141
- if (!plotted.length) return null;
142
- const rows = seriesRows(data, plotted.map((s) => s.field.key));
143
- const single = plotted.length === 1;
144
- const unitOf = new Map(plotted.map((s) => [s.field.key, s.field.unit ?? ""]));
145
- const xAxis = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XAxis, {
146
- dataKey: "__t",
147
- tickLine: false,
148
- axisLine: false,
149
- tickMargin: 8,
150
- minTickGap: 28,
151
- interval: "preserveStartEnd",
152
- tick: TICK,
153
- tickFormatter: (t) => md(t)
154
- });
155
- const yAxis = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(YAxis, {
156
- width: 34,
157
- tickCount: 4,
158
- tickLine: false,
159
- axisLine: false,
160
- tick: TICK,
161
- tickFormatter: (v) => fnum(v)
162
- });
163
- const grid = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CartesianGrid, {
164
- vertical: false,
165
- stroke: "var(--color-hairline)"
166
- });
167
- const tooltip = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
168
- isAnimationActive: false,
169
- cursor: {
170
- stroke: "var(--color-wire)",
171
- strokeDasharray: "3 3"
172
- },
173
- contentStyle: TOOLTIP_STYLE,
174
- labelStyle: {
175
- color: "var(--color-secondary)",
176
- marginBottom: 2
177
- },
178
- itemStyle: {
179
- color: "var(--color-primary)",
180
- padding: "1px 0"
181
- },
182
- labelFormatter: (t) => tsShort(String(t)),
183
- formatter: (v, name, item) => [withUnit(Number(v), unitOf.get(String(item?.dataKey)) ?? ""), String(name)]
184
- });
185
- const seriesProps = (s, idx) => {
186
- const color = STROKES[idx % STROKES.length];
187
- return {
188
- dataKey: s.field.key,
189
- name: s.field.label ?? s.field.key,
190
- type: "monotone",
191
- stroke: color,
192
- strokeWidth: 1.5,
193
- dot: s.pts.length === 1 ? {
194
- r: 3.5,
195
- strokeWidth: 0,
196
- fill: color
197
- } : false,
198
- activeDot: {
199
- r: 3,
200
- strokeWidth: 0,
201
- fill: color
202
- },
203
- connectNulls: true,
204
- isAnimationActive: false
205
- };
206
- };
207
- const margin = {
208
- top: 6,
209
- right: 12,
210
- left: 0,
211
- bottom: 0
212
- };
136
+ var withUnit = (value, unit = "") => unit === "$" ? `$${fnum(value)}` : `${fnum(value)}${unit}`;
137
+ var metricValues = (runs, keys) => runs.flatMap((run) => keys.map((key) => finiteMetric(run, key)).filter((value) => value != null));
138
+ var axisDomain = (runs, keys, spec) => chartDomain(metricValues(runs, keys), spec.yDomain);
139
+ var axisValue = (value, domain) => {
140
+ if (typeof domain[0] !== "number" || typeof domain[1] !== "number") return fnum(value);
141
+ const span = Math.abs(domain[1] - domain[0]);
142
+ const digits = span < .001 ? 6 : span < .01 ? 5 : span < .1 ? 4 : span < 1 ? 3 : span < 10 ? 2 : span < 100 ? 1 : 0;
143
+ return value.toFixed(digits);
144
+ };
145
+ function ChartFrame({ children, caption }) {
213
146
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("figure", {
214
147
  className: "my-2 min-w-0",
215
148
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
216
149
  className: "min-w-0",
217
150
  style: { height: HEIGHT },
218
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ResponsiveContainer, {
219
- width: "100%",
220
- height: "100%",
221
- initialDimension: INITIAL,
222
- children: single ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AreaChart, {
223
- data: rows,
224
- margin,
225
- children: [
226
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("linearGradient", {
227
- id: gradientId,
228
- x1: "0",
229
- y1: "0",
230
- x2: "0",
231
- y2: "1",
232
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
233
- offset: "5%",
234
- stopColor: STROKES[0],
235
- stopOpacity: .28
236
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
237
- offset: "95%",
238
- stopColor: STROKES[0],
239
- stopOpacity: .02
240
- })]
241
- }) }),
242
- grid,
243
- xAxis,
244
- yAxis,
245
- tooltip,
246
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Area, {
247
- ...seriesProps(plotted[0], 0),
248
- fill: `url(#${gradientId})`
249
- })
250
- ]
251
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(LineChart, {
252
- data: rows,
253
- margin,
254
- children: [
255
- grid,
256
- xAxis,
257
- yAxis,
258
- tooltip,
259
- plotted.map((s, idx) => /* @__PURE__ */ (0, import_react.createElement)(Line, {
260
- ...seriesProps(s, idx),
261
- key: s.field.key
262
- }))
263
- ]
264
- })
265
- })
266
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("figcaption", {
151
+ children
152
+ }), caption ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("figcaption", {
267
153
  className: "mt-1 flex flex-wrap gap-x-4 gap-y-1 text-label text-[var(--color-secondary)]",
268
- children: plotted.map((s, idx) => {
269
- const last = s.pts[s.pts.length - 1];
270
- if (!last) return null;
271
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
272
- className: "inline-flex items-center gap-1.5",
273
- children: [
274
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
275
- className: "inline-block h-[2px] w-3 align-middle",
276
- style: { background: STROKES[idx % STROKES.length] }
277
- }),
278
- s.field.label ?? s.field.key,
279
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
280
- className: "text-[var(--color-display)]",
281
- children: withUnit(last.v, s.field.unit ?? "")
282
- })
283
- ]
284
- }, s.field.key);
154
+ children: caption
155
+ }) : null]
156
+ });
157
+ }
158
+ function LoopChart({ runs, spec }) {
159
+ if (spec.type === "scatter") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScatterPlot, {
160
+ runs,
161
+ spec
162
+ });
163
+ if (spec.type === "progress") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProgressPlot, {
164
+ runs,
165
+ spec
166
+ });
167
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SeriesPlot, {
168
+ runs,
169
+ spec
170
+ });
171
+ }
172
+ function SeriesPlot({ runs, spec }) {
173
+ const gradientId = (0, import_react.useId)();
174
+ const keys = spec.series.map((field) => field.key);
175
+ const rows = seriesChartRows(runs, keys, spec.x);
176
+ if (!rows.length) return null;
177
+ const units = new Map(spec.series.map((field) => [field.key, field.unit ?? ""]));
178
+ const domain = axisDomain(runs, keys, spec);
179
+ const latest = /* @__PURE__ */ new Map();
180
+ for (const row of rows) for (const key of keys) if (typeof row[key] === "number") latest.set(key, row[key]);
181
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartFrame, {
182
+ caption: spec.series.map((field, index) => {
183
+ const value = latest.get(field.key);
184
+ return value == null ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
185
+ className: "inline-flex items-center gap-1.5",
186
+ children: [
187
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
188
+ className: "inline-block h-[2px] w-3",
189
+ style: { background: STROKES[index % STROKES.length] }
190
+ }),
191
+ field.label ?? field.key,
192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
193
+ className: "text-[var(--color-display)]",
194
+ children: withUnit(value, field.unit)
195
+ })
196
+ ]
197
+ }, field.key);
198
+ }),
199
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ResponsiveContainer, {
200
+ width: "100%",
201
+ height: "100%",
202
+ initialDimension: INITIAL,
203
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ComposedChart, {
204
+ data: rows,
205
+ margin: {
206
+ top: 6,
207
+ right: 12,
208
+ left: 0,
209
+ bottom: 0
210
+ },
211
+ children: [
212
+ spec.type === "area" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("linearGradient", {
213
+ id: gradientId,
214
+ x1: "0",
215
+ y1: "0",
216
+ x2: "0",
217
+ y2: "1",
218
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
219
+ offset: "5%",
220
+ stopColor: STROKES[0],
221
+ stopOpacity: .28
222
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", {
223
+ offset: "95%",
224
+ stopColor: STROKES[0],
225
+ stopOpacity: .02
226
+ })]
227
+ }) }) : null,
228
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CartesianGrid, {
229
+ vertical: false,
230
+ stroke: "var(--color-hairline)"
231
+ }),
232
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XAxis, {
233
+ dataKey: "__x",
234
+ type: spec.x === "runIndex" ? "number" : "category",
235
+ domain: spec.x === "runIndex" ? ["dataMin", "dataMax"] : void 0,
236
+ tick: TICK,
237
+ tickLine: false,
238
+ axisLine: false,
239
+ tickMargin: 8,
240
+ minTickGap: 28,
241
+ tickFormatter: (value) => spec.x === "time" ? md(String(value)) : String(value)
242
+ }),
243
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(YAxis, {
244
+ width: 60,
245
+ domain,
246
+ allowDataOverflow: Array.isArray(spec.yDomain),
247
+ tick: TICK,
248
+ tickLine: false,
249
+ axisLine: false,
250
+ tickFormatter: (value) => axisValue(value, domain)
251
+ }),
252
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
253
+ isAnimationActive: false,
254
+ contentStyle: TOOLTIP_STYLE,
255
+ labelFormatter: (value) => spec.x === "time" ? tsShort(String(value)) : `run #${value}`,
256
+ formatter: (value, name, item) => [withUnit(Number(value), units.get(String(item?.dataKey))), String(name)]
257
+ }),
258
+ spec.type === "area" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Area, {
259
+ ...seriesProps(spec.series[0], 0),
260
+ connectNulls: true,
261
+ dot: rows.length === 1 ? {
262
+ r: 3.5,
263
+ strokeWidth: 0
264
+ } : false,
265
+ fill: `url(#${gradientId})`
266
+ }) : spec.series.map((field, index) => /* @__PURE__ */ (0, import_react.createElement)(Line, {
267
+ ...seriesProps(field, index),
268
+ key: field.key,
269
+ connectNulls: true,
270
+ dot: spec.colorBy === "status" ? statusDot(field.key) : rows.length === 1 ? {
271
+ r: 3.5,
272
+ strokeWidth: 0
273
+ } : false
274
+ }))
275
+ ]
285
276
  })
286
- })]
277
+ })
287
278
  });
288
279
  }
280
+ function ScatterPlot({ runs, spec }) {
281
+ const rows = scatterChartRows(runs, spec.xKey, spec.yKey);
282
+ if (!rows.length) return null;
283
+ const domain = chartDomain(rows.map((row) => row.y), spec.yDomain);
284
+ const xDomain = chartDomain(rows.map((row) => row.x), "auto");
285
+ const kept = rows.filter((row) => row.status === "kept");
286
+ const other = rows.filter((row) => row.status !== "kept");
287
+ const groups = spec.colorBy === "status" ? [{
288
+ name: "Other",
289
+ rows: other,
290
+ fill: "var(--color-disabled)"
291
+ }, {
292
+ name: "Kept",
293
+ rows: kept,
294
+ fill: "var(--color-success)"
295
+ }] : [{
296
+ name: spec.yLabel ?? spec.yKey,
297
+ rows,
298
+ fill: STROKES[0]
299
+ }];
300
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartFrame, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ResponsiveContainer, {
301
+ width: "100%",
302
+ height: "100%",
303
+ initialDimension: INITIAL,
304
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ScatterChart, {
305
+ margin: {
306
+ top: 8,
307
+ right: 12,
308
+ left: 0,
309
+ bottom: 8
310
+ },
311
+ children: [
312
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CartesianGrid, { stroke: "var(--color-hairline)" }),
313
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XAxis, {
314
+ type: "number",
315
+ dataKey: "x",
316
+ name: spec.xLabel ?? spec.xKey,
317
+ domain: xDomain,
318
+ tickFormatter: (value) => axisValue(value, xDomain),
319
+ tick: TICK,
320
+ tickLine: false,
321
+ axisLine: false
322
+ }),
323
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(YAxis, {
324
+ type: "number",
325
+ dataKey: "y",
326
+ name: spec.yLabel ?? spec.yKey,
327
+ domain,
328
+ allowDataOverflow: Array.isArray(spec.yDomain),
329
+ width: 60,
330
+ tickFormatter: (value) => axisValue(value, domain),
331
+ tick: TICK,
332
+ tickLine: false,
333
+ axisLine: false
334
+ }),
335
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
336
+ isAnimationActive: false,
337
+ contentStyle: TOOLTIP_STYLE,
338
+ cursor: {
339
+ stroke: "var(--color-wire)",
340
+ strokeDasharray: "3 3"
341
+ },
342
+ labelFormatter: (_, payload) => payload?.[0]?.payload ? `run #${payload[0].payload.runIndex}` : "",
343
+ formatter: (value, name, item) => [withUnit(Number(value), String(item?.dataKey) === "x" ? spec.xUnit : spec.yUnit), String(name)]
344
+ }),
345
+ groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Scatter, {
346
+ name: group.name,
347
+ data: group.rows,
348
+ fill: group.fill,
349
+ isAnimationActive: false
350
+ }, group.name)),
351
+ spec.colorBy ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Legend, {
352
+ verticalAlign: "top",
353
+ align: "right"
354
+ }) : null
355
+ ]
356
+ })
357
+ }) });
358
+ }
359
+ function ProgressPlot({ runs, spec }) {
360
+ const rows = progressChartRows(runs, spec.yKey);
361
+ if (!rows.length) return null;
362
+ const kept = rows.filter((row) => row.status === "kept");
363
+ const other = rows.filter((row) => row.status !== "kept");
364
+ const best = runningBest(rows, spec.direction);
365
+ const domain = chartDomain(rows.map((row) => row.y), spec.yDomain);
366
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChartFrame, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ResponsiveContainer, {
367
+ width: "100%",
368
+ height: "100%",
369
+ initialDimension: INITIAL,
370
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ComposedChart, {
371
+ margin: {
372
+ top: 8,
373
+ right: 12,
374
+ left: 0,
375
+ bottom: 8
376
+ },
377
+ children: [
378
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CartesianGrid, {
379
+ vertical: false,
380
+ stroke: "var(--color-hairline)"
381
+ }),
382
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XAxis, {
383
+ type: "number",
384
+ dataKey: "x",
385
+ domain: ["dataMin", "dataMax"],
386
+ tick: TICK,
387
+ tickLine: false,
388
+ axisLine: false
389
+ }),
390
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(YAxis, {
391
+ type: "number",
392
+ dataKey: "y",
393
+ domain,
394
+ allowDataOverflow: Array.isArray(spec.yDomain),
395
+ width: 60,
396
+ tickFormatter: (value) => axisValue(value, domain),
397
+ tick: TICK,
398
+ tickLine: false,
399
+ axisLine: false
400
+ }),
401
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, {
402
+ isAnimationActive: false,
403
+ contentStyle: TOOLTIP_STYLE,
404
+ labelFormatter: (_, payload) => payload?.[0]?.payload ? `run #${payload[0].payload.runIndex ?? payload[0].payload.x}` : ""
405
+ }),
406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Scatter, {
407
+ name: "Other",
408
+ data: other,
409
+ fill: "var(--color-disabled)",
410
+ isAnimationActive: false
411
+ }),
412
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Scatter, {
413
+ name: "Kept",
414
+ data: kept,
415
+ fill: "var(--color-success)",
416
+ isAnimationActive: false
417
+ }),
418
+ best.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Line, {
419
+ name: "Running best",
420
+ data: best,
421
+ dataKey: "y",
422
+ type: "stepAfter",
423
+ stroke: "var(--color-success)",
424
+ strokeWidth: 2,
425
+ dot: false,
426
+ isAnimationActive: false
427
+ }) : null,
428
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Legend, {
429
+ verticalAlign: "top",
430
+ align: "right"
431
+ })
432
+ ]
433
+ })
434
+ }) });
435
+ }
436
+ var seriesProps = (field, index) => ({
437
+ dataKey: field.key,
438
+ name: field.label ?? field.key,
439
+ type: "linear",
440
+ stroke: STROKES[index % STROKES.length],
441
+ strokeWidth: 1.5,
442
+ activeDot: {
443
+ r: 3,
444
+ strokeWidth: 0
445
+ },
446
+ isAnimationActive: false
447
+ });
448
+ var statusDot = (key) => (props) => {
449
+ if (typeof props?.payload?.[key] !== "number") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", {});
450
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
451
+ cx: props.cx,
452
+ cy: props.cy,
453
+ r: 2.8,
454
+ fill: props.payload.__status === "kept" ? "var(--color-success)" : "var(--color-disabled)"
455
+ });
456
+ };
289
457
  var basename$2 = (path) => path.split("/").pop() || path;
290
458
  var pad = (n) => String(n).padStart(2, "0");
291
459
  /** A real calendar date within a sane range (no 2026-13-40, no year 10521). */
@@ -949,14 +1117,14 @@ function KanbanCard({ card, onOpen }) {
949
1117
  * else (A/B panels, stat tiles, layout, text) is the agent's own HTML — there are
950
1118
  * NO opinionated panel components.
951
1119
  *
952
- * <loop-chart series="mrr:MRR:$, paid:Paid"></loop-chart> multi-series trend chart
1120
+ * <loop-chart type="line" x="runIndex" series="mrr:MRR:$"></loop-chart>
953
1121
  * <loop-embed match="reports/digest-*.md"></loop-embed> newest matching artifact, embedded
954
1122
  * <loop-calendar match="reports/*.md"></loop-calendar> month calendar of produced files
955
1123
  * <loop-kanban columns="a,b,c" match="notes/*.md"> typed products as a board, columns = type
956
1124
  *
957
1125
  * Registering a new primitive means moving three things together: LOOP_TAGS +
958
1126
  * the sanitizer config below, the parser swap in `options`, and the skill's
959
- * authoring docs (references/evolve.md §3, run/steer.md) - the sanitizer
1127
+ * authoring docs (references/dashboard.md plus concise run-prompt examples) - the sanitizer
960
1128
  * allowlist and the skill prose must never drift apart.
961
1129
  */
962
1130
  var LOOP_TAGS = [
@@ -967,7 +1135,7 @@ var LOOP_TAGS = [
967
1135
  ];
968
1136
  /** Data-bearing attributes on the loop-* primitives (all parsed by us, never markup). */
969
1137
  var LOOP_ATTRS = [
970
- "series",
1138
+ ...CHART_ATTRS,
971
1139
  "file",
972
1140
  "match",
973
1141
  "full",
@@ -1039,10 +1207,29 @@ function LoopView({ html, runs, loopId, taskFile }) {
1039
1207
  const ctx = buildBindingContext(runs);
1040
1208
  return purify.sanitize(resolveBindings(html, ctx), SANITIZE_CONFIG);
1041
1209
  }, [html, runs]);
1042
- const data = (0, import_react.useMemo)(() => numericSeries(runs), [runs]);
1210
+ const wantsCharts = /<loop-chart\b/.test(clean);
1211
+ const [chartData, setChartData] = (0, import_react.useState)(null);
1212
+ const chartRuns = chartData?.loopId === loopId ? chartData.runs : null;
1043
1213
  const wantsArtifacts = /<loop-(embed|calendar|kanban)\b/.test(clean);
1044
1214
  const [artifacts, setArtifacts] = (0, import_react.useState)(null);
1045
1215
  const newestRunId = runs[0]?.id;
1216
+ const newestRunLive = runs[0]?.running === true;
1217
+ (0, import_react.useEffect)(() => {
1218
+ if (!wantsCharts) return;
1219
+ let alive = true;
1220
+ getChartRuns({ data: { loopId } }).then((list) => alive && setChartData({
1221
+ loopId,
1222
+ runs: list
1223
+ })).catch(() => {});
1224
+ return () => {
1225
+ alive = false;
1226
+ };
1227
+ }, [
1228
+ wantsCharts,
1229
+ loopId,
1230
+ newestRunId,
1231
+ newestRunLive
1232
+ ]);
1046
1233
  (0, import_react.useEffect)(() => {
1047
1234
  if (!wantsArtifacts) return;
1048
1235
  let alive = true;
@@ -1062,17 +1249,24 @@ function LoopView({ html, runs, loopId, taskFile }) {
1062
1249
  wantsArtifacts,
1063
1250
  loopId,
1064
1251
  newestRunId,
1065
- runs[0]?.running === true
1252
+ newestRunLive
1066
1253
  ]);
1067
1254
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1068
1255
  className: "loopview text-[14px] leading-relaxed",
1069
1256
  children: esm_default(clean, (0, import_react.useMemo)(() => ({ replace: (node) => {
1070
1257
  if (!(node instanceof import_lib.Element)) return void 0;
1071
1258
  const a = node.attribs ?? {};
1072
- if (node.name === "loop-chart") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopChart, {
1073
- data,
1074
- series: parseSeries(a.series)
1075
- });
1259
+ if (node.name === "loop-chart") {
1260
+ const parsed = parseChartSpec(a);
1261
+ if (!parsed.ok) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1262
+ className: "rounded-control border border-[var(--color-wire)] bg-[var(--color-raised)] p-3 text-meta text-[var(--color-secondary)]",
1263
+ children: "This chart uses an outdated or invalid configuration."
1264
+ });
1265
+ return chartRuns ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopChart, {
1266
+ runs: chartRuns,
1267
+ spec: parsed.value
1268
+ }) : null;
1269
+ }
1076
1270
  if (node.name === "loop-embed") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopEmbed, {
1077
1271
  loopId,
1078
1272
  artifacts,
@@ -1095,7 +1289,7 @@ function LoopView({ html, runs, loopId, taskFile }) {
1095
1289
  taskFile
1096
1290
  });
1097
1291
  } }), [
1098
- data,
1292
+ chartRuns,
1099
1293
  loopId,
1100
1294
  taskFile,
1101
1295
  artifacts