@kky42/pievo-server 2.3.0 → 2.4.2
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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/MachinesModal-CSbnrUER.js +1 -0
- package/.output/public/assets/SignIn-0Tjqs-Ma.js +1 -0
- package/.output/public/assets/actionUi-DpwGIGpw.js +1 -0
- package/.output/public/assets/app-CaZpuwOS.css +2 -0
- package/.output/public/assets/index-CaS_SmDq.js +10 -0
- package/.output/public/assets/invite._token-Btg_0aLp.js +1 -0
- package/.output/public/assets/invite._token-KMD2JAgE.js +1 -0
- package/.output/public/assets/lifecycleUi-C7dwHtbw.js +1 -0
- package/.output/public/assets/loops._loopId-D5usF3DO.js +67 -0
- package/.output/public/assets/loops._loopId_.runs._runId-DRCgXaCq.js +3 -0
- package/.output/public/assets/preload-helper-DsxECZIS.js +5 -0
- package/.output/public/assets/routes-CtFgwdgW.js +1 -0
- package/.output/public/assets/routes-LDbnnQ3f.js +1 -0
- package/.output/public/assets/t._teamId-D41xcbsF.js +1 -0
- package/.output/public/assets/t._teamId-DUena1pV.js +1 -0
- package/.output/public/assets/ui-Do4gqMyP.js +1 -0
- package/.output/server/_libs/@base-ui/react+[...].mjs +4130 -4130
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
- package/.output/server/_libs/drizzle-orm+postgres.mjs +28 -1
- package/.output/server/_ssr/DashboardView-C2LdV6J0.mjs +914 -0
- package/.output/server/_ssr/{notifyFns-DuPR1zd7.mjs → MachinesModal-BjkcVjjx.mjs} +55 -35
- package/.output/server/_ssr/{SignIn-RmpWm9uy.mjs → SignIn-DEGrvJvb.mjs} +10 -24
- package/.output/server/_ssr/SocialLinks-Cm4H8pUJ.mjs +1044 -0
- package/.output/server/_ssr/{actionUi-BN55TNYE.mjs → actionUi-DJBWuNwr.mjs} +3 -3
- package/.output/server/_ssr/{artifactFiles-BCaIIrrT.mjs → artifactFiles-DtpRidFZ.mjs} +7 -8
- package/.output/server/_ssr/{artifactKind-BW0isV8L.mjs → artifactKind-FBYbgSuf.mjs} +27 -2
- package/.output/server/_ssr/artifacts-BR3jj0NC.mjs +2 -0
- package/.output/server/_ssr/{auth-30Z4lV1w.mjs → auth-C0zoZCGe.mjs} +16 -19
- package/.output/server/_ssr/boot-CdOO4xOo.mjs +2 -0
- package/.output/server/_ssr/{boot-3BNKujyp.mjs → boot-sNlIkoLe.mjs} +1331 -3927
- package/.output/server/_ssr/{bun-sqlite-dialect-BW9W1_Ps-_7tfuNRi.mjs → bun-sqlite-dialect-BW9W1_Ps-DmlZWqO7.mjs} +2 -2
- package/.output/server/_ssr/{d1-sqlite-dialect-BLC8LXE6-ucDUbeBk.mjs → d1-sqlite-dialect-BLC8LXE6-CCNzqZwZ.mjs} +2 -2
- package/.output/server/_ssr/db-CwLD-5w1.mjs +2 -0
- package/.output/server/_ssr/{dist-Cd6nlLlO.mjs → dist-B3SxDOpW.mjs} +2 -2
- package/.output/server/_ssr/{factory-CWWwV7XZ.mjs → factory-Dv3Zopis.mjs} +2 -2
- package/.output/server/_ssr/invite._token-CJ6-H-s3.mjs +20 -0
- package/.output/server/_ssr/{invite._token-SdWXemIc.mjs → invite._token-dLKDJKVs.mjs} +9 -17
- package/.output/server/_ssr/{invite._token-BKlz0R2f.mjs → invite._token-l1SjouDz.mjs} +6 -15
- package/.output/server/_ssr/{kysely-adapter-C8iS51DB.mjs → kysely-adapter-nL7WKSgw.mjs} +1 -1
- package/.output/server/_ssr/{lifecycleUi-DgBQAYzp.mjs → lifecycleUi-Dad3hvhm.mjs} +11 -11
- package/.output/server/_ssr/logger-B8m8F1Qu.mjs +2 -0
- package/.output/server/_ssr/loopApi-BKyFB4KM.mjs +342 -0
- package/.output/server/_ssr/{adapters-BoLkOf1T.mjs → loopProjection-B8a829ij.mjs} +25 -55
- package/.output/server/_ssr/loops._loopId-DS16Phmq.mjs +1271 -0
- package/.output/server/_ssr/{loops._loopId_.runs._runId-PUGBrD5q.mjs → loops._loopId_.runs._runId-BEJI6iK5.mjs} +23 -74
- package/.output/server/_ssr/{loops._loopId_.runs._runId-Ymy4lwee.mjs → loops._loopId_.runs._runId-CduETLqH.mjs} +4 -5
- package/.output/server/_ssr/{machineFns-BZ9puxDt.mjs → machineFns-ByMsgG12.mjs} +3 -5
- package/.output/server/_ssr/{node-sqlite-dialect-D_3JmkMo.mjs → node-sqlite-dialect-CjNineno.mjs} +2 -2
- package/.output/server/_ssr/router-CXyrnvx-.mjs +565 -0
- package/.output/server/_ssr/{routes-ClZEdsnB.mjs → routes-BulNEUEE.mjs} +6 -10
- package/.output/server/_ssr/{routes-ComqHvCL.mjs → routes-DD94baie.mjs} +3 -3
- package/.output/server/_ssr/{routes-D5jJJ-30.mjs → routes-Wjexi35W.mjs} +6 -6
- package/.output/server/_ssr/runDiff-Ba8Lg1vg.mjs +2 -0
- package/.output/server/_ssr/ssr.mjs +60 -100
- package/.output/server/_ssr/store-Cujtg2QE.mjs +2 -0
- package/.output/server/_ssr/{t._teamId-BgHV_KFP.mjs → t._teamId-1CreIlPV.mjs} +7 -11
- package/.output/server/_ssr/{t._teamId-DDERNgfn.mjs → t._teamId-Bd6-zZsy.mjs} +4 -4
- package/.output/server/_ssr/{t._teamId-DRJvKHZn.mjs → t._teamId-COuMKQ4r.mjs} +7 -7
- package/.output/server/_ssr/{teamFns-Bp81UO-x.mjs → teamFns-H438WnZC.mjs} +12 -30
- package/.output/server/_ssr/{teamFns-bm82twd5.mjs → teamFns-j2JKjorZ.mjs} +2 -2
- package/.output/server/_ssr/tokens-DgG_Syq3.mjs +2 -0
- package/.output/server/_ssr/{ui-Ww2tTM2u.mjs → ui-D5KWstlb.mjs} +26 -48
- package/.output/server/{_tanstack-start-manifest_v-DwKENYCN.mjs → _tanstack-start-manifest_v-3H7yBKLz.mjs} +25 -35
- package/{drizzle/0000_round_marauders.sql → .output/server/drizzle/0000_baseline.sql} +102 -44
- package/.output/server/drizzle/meta/_journal.json +3 -115
- package/.output/server/index.mjs +83 -132
- package/{.output/server/drizzle/0000_round_marauders.sql → drizzle/0000_baseline.sql} +102 -44
- package/drizzle/meta/_journal.json +3 -115
- package/package.json +2 -11
- package/scripts/pievo-server.mjs +3 -1
- package/scripts/server-cli-lib.mjs +23 -16
- package/.output/public/assets/CodeField-dOzyFQNH.js +0 -34
- package/.output/public/assets/LoopView-DjVHUyrR.js +0 -59
- package/.output/public/assets/Modal-CCRvx959.js +0 -1
- package/.output/public/assets/SignIn-Qxw5zDFq.js +0 -1
- package/.output/public/assets/actionUi-CXxhzYe2.js +0 -1
- package/.output/public/assets/app-D1cij_NJ.css +0 -2
- package/.output/public/assets/artifactView-Cd5wWPil.js +0 -66
- package/.output/public/assets/index-BRN_CQ8U.js +0 -12
- package/.output/public/assets/invite._token-BIxr8DlP.js +0 -1
- package/.output/public/assets/invite._token-j2BTCeG1.js +0 -1
- package/.output/public/assets/jsx-runtime-BseJUIpC.js +0 -1
- package/.output/public/assets/lifecycleUi-Qy_8beQK.js +0 -1
- package/.output/public/assets/loopApi-CT1mMhlb.js +0 -3
- package/.output/public/assets/loops._loopId-BRBQzioy.js +0 -4
- package/.output/public/assets/loops._loopId_.runs._runId-CMnOJkem.js +0 -3
- package/.output/public/assets/notifyFns-DYD4EKaG.js +0 -1
- package/.output/public/assets/preload-helper-DFkkpP6W.js +0 -3
- package/.output/public/assets/routes-3nuEJGdo.js +0 -1
- package/.output/public/assets/routes-DORLim-C.js +0 -1
- package/.output/public/assets/t._teamId-F5f4K8kd.js +0 -1
- package/.output/public/assets/t._teamId-XfE_S1Fo.js +0 -1
- package/.output/public/assets/ui-Dhsx6SW0.js +0 -1
- package/.output/public/assets/useRouter-DsjH8G0p.js +0 -1
- package/.output/server/_libs/@codemirror/autocomplete+[...].mjs +0 -19713
- package/.output/server/_libs/@codemirror/lang-css+[...].mjs +0 -2636
- package/.output/server/_libs/@codemirror/lang-html+[...].mjs +0 -2247
- package/.output/server/_libs/@codemirror/lang-json+[...].mjs +0 -79
- package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +0 -1736
- package/.output/server/_libs/@uiw/codemirror-extensions-basic-setup+[...].mjs +0 -76
- package/.output/server/_libs/babel__runtime.mjs +0 -30
- package/.output/server/_libs/clsx.mjs +0 -16
- package/.output/server/_libs/codemirror__commands.mjs +0 -1725
- package/.output/server/_libs/codemirror__lint.mjs +0 -565
- package/.output/server/_libs/codemirror__search.mjs +0 -1138
- package/.output/server/_libs/codemirror__theme-one-dark.mjs +0 -161
- package/.output/server/_libs/d3-array.mjs +0 -231
- package/.output/server/_libs/d3-color.mjs +0 -330
- package/.output/server/_libs/d3-format.mjs +0 -252
- package/.output/server/_libs/d3-interpolate.mjs +0 -163
- package/.output/server/_libs/d3-path.mjs +0 -77
- package/.output/server/_libs/d3-scale+[...].mjs +0 -1907
- package/.output/server/_libs/d3-shape.mjs +0 -818
- package/.output/server/_libs/decimal.js-light.mjs +0 -884
- package/.output/server/_libs/dom-serializer+[...].mjs +0 -708
- package/.output/server/_libs/domhandler.mjs +0 -461
- package/.output/server/_libs/domutils.mjs +0 -868
- package/.output/server/_libs/es-toolkit.mjs +0 -1737
- package/.output/server/_libs/eventemitter3.mjs +0 -236
- package/.output/server/_libs/html-dom-parser+htmlparser2.mjs +0 -1560
- package/.output/server/_libs/html-react-parser+[...].mjs +0 -1360
- package/.output/server/_libs/react-is.mjs +0 -48
- package/.output/server/_libs/react-redux+[...].mjs +0 -205
- package/.output/server/_libs/recharts+victory-vendor.mjs +0 -17303
- package/.output/server/_libs/uiw__codemirror-themes.mjs +0 -32
- package/.output/server/_libs/uiw__react-codemirror.mjs +0 -354
- package/.output/server/_ssr/CodeField-B7oOmmwH.mjs +0 -143
- package/.output/server/_ssr/DashboardView-BF7PciP7.mjs +0 -2736
- package/.output/server/_ssr/LoopPlaybook-DC7QVvPY.mjs +0 -2105
- package/.output/server/_ssr/LoopView-DwSrm9Ja.mjs +0 -1300
- package/.output/server/_ssr/Modal-MaCG52Pv.mjs +0 -49
- package/.output/server/_ssr/artifactView-iuPsp5aa.mjs +0 -304
- package/.output/server/_ssr/artifacts-jQ-bfdhI.mjs +0 -2
- package/.output/server/_ssr/boot-CsEFT53K.mjs +0 -2
- package/.output/server/_ssr/db-CXownd1H.mjs +0 -2
- package/.output/server/_ssr/invite._token-CWl6kipD.mjs +0 -28
- package/.output/server/_ssr/logger-Gy-47z3k.mjs +0 -2
- package/.output/server/_ssr/loopApi-8_KgBapr.mjs +0 -109
- package/.output/server/_ssr/loopApi-ABi9SgWS.mjs +0 -564
- package/.output/server/_ssr/loops._loopId-SON_t43g.mjs +0 -30
- package/.output/server/_ssr/loops._loopId-p1KWsvMM.mjs +0 -1477
- package/.output/server/_ssr/notifyFns-BUCePuF3.mjs +0 -118
- package/.output/server/_ssr/router-Czxvra6l.mjs +0 -685
- package/.output/server/_ssr/runDiff--qLtU4FI.mjs +0 -2
- package/.output/server/_ssr/store-40eVzW1S.mjs +0 -2
- package/.output/server/_ssr/tokens-CbfoRC_s.mjs +0 -2
- package/.output/server/drizzle/0001_naive_silver_fox.sql +0 -25
- package/.output/server/drizzle/0002_funny_sage.sql +0 -12
- package/.output/server/drizzle/0003_optimal_slapstick.sql +0 -102
- package/.output/server/drizzle/0004_remove_grok_agent.sql +0 -5
- package/.output/server/drizzle/0005_heavy_gauntlet.sql +0 -8
- package/.output/server/drizzle/0006_tranquil_sharon_carter.sql +0 -1
- package/.output/server/drizzle/0007_faulty_starbolt.sql +0 -30
- package/.output/server/drizzle/0008_durable_lifecycle_evidence.sql +0 -15
- package/.output/server/drizzle/0009_ordinary_electro.sql +0 -1
- package/.output/server/drizzle/0010_workable_rage.sql +0 -14
- package/.output/server/drizzle/0011_steep_marvel_zombies.sql +0 -3
- package/.output/server/drizzle/0012_blocked_run_status.sql +0 -12
- package/.output/server/drizzle/0013_drop_run_outcome.sql +0 -2
- package/.output/server/drizzle/0014_luxuriant_scrambler.sql +0 -5
- package/.output/server/drizzle/0015_run_history_foundation.sql +0 -43
- package/.output/server/drizzle/0016_plural_machine_runs.sql +0 -4
- package/.output/server/drizzle/meta/0000_snapshot.json +0 -1333
- package/.output/server/drizzle/meta/0001_snapshot.json +0 -1498
- package/.output/server/drizzle/meta/0002_snapshot.json +0 -1576
- package/.output/server/drizzle/meta/0003_snapshot.json +0 -1666
- package/.output/server/drizzle/meta/0005_snapshot.json +0 -1666
- package/.output/server/drizzle/meta/0006_snapshot.json +0 -1672
- package/.output/server/drizzle/meta/0007_snapshot.json +0 -1787
- package/.output/server/drizzle/meta/0009_snapshot.json +0 -1793
- package/.output/server/drizzle/meta/0010_snapshot.json +0 -1891
- package/.output/server/drizzle/meta/0011_snapshot.json +0 -1872
- package/drizzle/0001_naive_silver_fox.sql +0 -25
- package/drizzle/0002_funny_sage.sql +0 -12
- package/drizzle/0003_optimal_slapstick.sql +0 -102
- package/drizzle/0004_remove_grok_agent.sql +0 -5
- package/drizzle/0005_heavy_gauntlet.sql +0 -8
- package/drizzle/0006_tranquil_sharon_carter.sql +0 -1
- package/drizzle/0007_faulty_starbolt.sql +0 -30
- package/drizzle/0008_durable_lifecycle_evidence.sql +0 -15
- package/drizzle/0009_ordinary_electro.sql +0 -1
- package/drizzle/0010_workable_rage.sql +0 -14
- package/drizzle/0011_steep_marvel_zombies.sql +0 -3
- package/drizzle/0012_blocked_run_status.sql +0 -12
- package/drizzle/0013_drop_run_outcome.sql +0 -2
- package/drizzle/0014_luxuriant_scrambler.sql +0 -5
- package/drizzle/0015_run_history_foundation.sql +0 -43
- package/drizzle/0016_plural_machine_runs.sql +0 -4
- package/drizzle/meta/0000_snapshot.json +0 -1333
- package/drizzle/meta/0001_snapshot.json +0 -1498
- package/drizzle/meta/0002_snapshot.json +0 -1576
- package/drizzle/meta/0003_snapshot.json +0 -1666
- package/drizzle/meta/0005_snapshot.json +0 -1666
- package/drizzle/meta/0006_snapshot.json +0 -1672
- package/drizzle/meta/0007_snapshot.json +0 -1787
- package/drizzle/meta/0009_snapshot.json +0 -1793
- package/drizzle/meta/0010_snapshot.json +0 -1891
- package/drizzle/meta/0011_snapshot.json +0 -1872
|
@@ -1,1300 +0,0 @@
|
|
|
1
|
-
import { o as __toESM } from "../_runtime.mjs";
|
|
2
|
-
import { g as require_jsx_runtime, h as require_react } from "../_libs/@base-ui/react+[...].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
|
-
import { t as artifactKind } from "./artifactKind-BW0isV8L.mjs";
|
|
7
|
-
import { t as purify } from "../_libs/dompurify.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
|
-
import { n as import_lib, t as esm_default } from "../_libs/html-react-parser+[...].mjs";
|
|
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
|
-
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
14
|
-
var import_jsx_runtime = require_jsx_runtime();
|
|
15
|
-
function buildBindingContext(runs) {
|
|
16
|
-
const latest = {};
|
|
17
|
-
const seen = /* @__PURE__ */ new Set();
|
|
18
|
-
for (const r of runs) if (r.metrics && typeof r.metrics === "object" && !Array.isArray(r.metrics)) for (const k in r.metrics) {
|
|
19
|
-
if (seen.has(k)) continue;
|
|
20
|
-
seen.add(k);
|
|
21
|
-
const v = r.metrics[k];
|
|
22
|
-
if (typeof v === "number") latest[k] = fnum(v);
|
|
23
|
-
}
|
|
24
|
-
return { latest };
|
|
25
|
-
}
|
|
26
|
-
var TOKEN = /\{\{\s*([a-zA-Z0-9_.]+)\s*\}\}/g;
|
|
27
|
-
var escapeHtml = (s) => s.replace(/[&<>"]/g, (c) => ({
|
|
28
|
-
"&": "&",
|
|
29
|
-
"<": "<",
|
|
30
|
-
">": ">",
|
|
31
|
-
"\"": """
|
|
32
|
-
})[c] ?? c);
|
|
33
|
-
/** Interpolate `{{...}}` tokens. Values are HTML-escaped (they are agent-controlled data). */
|
|
34
|
-
function resolveBindings(html, ctx) {
|
|
35
|
-
return html.replace(TOKEN, (_m, path) => {
|
|
36
|
-
const v = lookup(ctx, path);
|
|
37
|
-
return v == null ? "—" : escapeHtml(v);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
function lookup(ctx, path) {
|
|
41
|
-
const parts = path.split(".");
|
|
42
|
-
if (parts[0] === "latest" && parts[1]) return ctx.latest[parts[1]];
|
|
43
|
-
}
|
|
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
|
-
});
|
|
97
|
-
}
|
|
98
|
-
return out;
|
|
99
|
-
}
|
|
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];
|
|
109
|
-
}
|
|
110
|
-
var STROKES = [
|
|
111
|
-
"var(--color-chart-1)",
|
|
112
|
-
"var(--color-chart-2)",
|
|
113
|
-
"var(--color-chart-3)",
|
|
114
|
-
"var(--color-chart-4)",
|
|
115
|
-
"var(--color-chart-5)"
|
|
116
|
-
];
|
|
117
|
-
var HEIGHT = 190;
|
|
118
|
-
var INITIAL = {
|
|
119
|
-
width: 640,
|
|
120
|
-
height: HEIGHT
|
|
121
|
-
};
|
|
122
|
-
var TICK = {
|
|
123
|
-
fontSize: 10,
|
|
124
|
-
fontFamily: "var(--font-mono)",
|
|
125
|
-
fill: "var(--color-secondary)"
|
|
126
|
-
};
|
|
127
|
-
var TOOLTIP_STYLE = {
|
|
128
|
-
background: "var(--color-surface)",
|
|
129
|
-
border: "1px solid var(--color-wire)",
|
|
130
|
-
borderRadius: 8,
|
|
131
|
-
boxShadow: "none",
|
|
132
|
-
padding: "6px 10px",
|
|
133
|
-
fontFamily: "var(--font-mono)",
|
|
134
|
-
fontSize: 11
|
|
135
|
-
};
|
|
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 }) {
|
|
146
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("figure", {
|
|
147
|
-
className: "my-2 min-w-0",
|
|
148
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
149
|
-
className: "min-w-0",
|
|
150
|
-
style: { height: HEIGHT },
|
|
151
|
-
children
|
|
152
|
-
}), caption ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("figcaption", {
|
|
153
|
-
className: "mt-1 flex flex-wrap gap-x-4 gap-y-1 text-label text-[var(--color-secondary)]",
|
|
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
|
-
]
|
|
276
|
-
})
|
|
277
|
-
})
|
|
278
|
-
});
|
|
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
|
-
};
|
|
457
|
-
var basename$2 = (path) => path.split("/").pop() || path;
|
|
458
|
-
var pad = (n) => String(n).padStart(2, "0");
|
|
459
|
-
/** A real calendar date within a sane range (no 2026-13-40, no year 10521). */
|
|
460
|
-
function validDate(y, m, d) {
|
|
461
|
-
if (y < 1970 || y > 2100 || m < 1 || m > 12 || d < 1 || d > 31) return false;
|
|
462
|
-
const dt = new Date(Date.UTC(y, m - 1, d));
|
|
463
|
-
return dt.getUTCFullYear() === y && dt.getUTCMonth() === m - 1 && dt.getUTCDate() === d;
|
|
464
|
-
}
|
|
465
|
-
var DATE_RE = /(\d{4})(?:([-_])(\d{2})\2(\d{2})|(\d{2})(\d{2}))/g;
|
|
466
|
-
/** First valid date found in the file's basename, else null. */
|
|
467
|
-
function parseFilenameDate(path) {
|
|
468
|
-
const name = basename$2(path);
|
|
469
|
-
DATE_RE.lastIndex = 0;
|
|
470
|
-
for (let m = DATE_RE.exec(name); m; m = DATE_RE.exec(name)) {
|
|
471
|
-
const before = name[m.index - 1];
|
|
472
|
-
const after = name[m.index + m[0].length];
|
|
473
|
-
if (before && /\d/.test(before) || after && /\d/.test(after)) continue;
|
|
474
|
-
const y = Number(m[1]);
|
|
475
|
-
const mo = Number(m[3] ?? m[5]);
|
|
476
|
-
const d = Number(m[4] ?? m[6]);
|
|
477
|
-
if (validDate(y, mo, d)) return `${m[1]}-${pad(mo)}-${pad(d)}`;
|
|
478
|
-
}
|
|
479
|
-
return null;
|
|
480
|
-
}
|
|
481
|
-
/** Local calendar day of an ISO timestamp (the calendar renders user-local). */
|
|
482
|
-
function localDay(iso) {
|
|
483
|
-
const d = new Date(iso);
|
|
484
|
-
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* A calendar day from a front-matter `date:` value (stored RAW — validity is
|
|
488
|
-
* decided here). Forgiving about the shape: takes the leading `YYYY-MM-DD` (or
|
|
489
|
-
* `/`/`_` separators, or a full ISO timestamp's date part) and validates it's a
|
|
490
|
-
* real day. Returns null for anything unparseable/invalid (fall through to
|
|
491
|
-
* filename/sync).
|
|
492
|
-
*/
|
|
493
|
-
function parseMetaDate(raw) {
|
|
494
|
-
if (typeof raw !== "string") return null;
|
|
495
|
-
const m = /^\s*(\d{4})[-/_](\d{2})[-/_](\d{2})/.exec(raw);
|
|
496
|
-
if (!m) return null;
|
|
497
|
-
const y = Number(m[1]);
|
|
498
|
-
const mo = Number(m[2]);
|
|
499
|
-
const d = Number(m[3]);
|
|
500
|
-
return validDate(y, mo, d) ? `${m[1]}-${pad(mo)}-${pad(d)}` : null;
|
|
501
|
-
}
|
|
502
|
-
/** Date one artifact: front-matter `date:` first (authoritative), filename next,
|
|
503
|
-
* sync time as the marked fallback. */
|
|
504
|
-
function productDate(file) {
|
|
505
|
-
const fromMeta = parseMetaDate(file.meta?.date);
|
|
506
|
-
if (fromMeta) return {
|
|
507
|
-
date: fromMeta,
|
|
508
|
-
source: "frontmatter"
|
|
509
|
-
};
|
|
510
|
-
const fromName = parseFilenameDate(file.path);
|
|
511
|
-
return fromName ? {
|
|
512
|
-
date: fromName,
|
|
513
|
-
source: "filename"
|
|
514
|
-
} : {
|
|
515
|
-
date: localDay(file.updatedAt),
|
|
516
|
-
source: "sync"
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
/**
|
|
520
|
-
* `match` glob → RegExp. `*` matches within a path segment (never `/`); all
|
|
521
|
-
* other characters are literal. A pattern with no `/` matches against the
|
|
522
|
-
* BASENAME (so `*.md` means "any markdown file, anywhere"), a pattern with
|
|
523
|
-
* one matches the whole loop-relative path.
|
|
524
|
-
*/
|
|
525
|
-
function globToRegExp(pattern) {
|
|
526
|
-
const rx = pattern.split("*").map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*");
|
|
527
|
-
return new RegExp(`^${rx}$`);
|
|
528
|
-
}
|
|
529
|
-
/** The artifacts a `match` pattern selects (no pattern ⇒ all). */
|
|
530
|
-
function matchArtifacts(files, pattern) {
|
|
531
|
-
if (!pattern) return files;
|
|
532
|
-
const rx = globToRegExp(pattern);
|
|
533
|
-
const onBase = !pattern.includes("/");
|
|
534
|
-
return files.filter((f) => rx.test(onBase ? basename$2(f.path) : f.path));
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* The newest artifact a pattern selects - by product date (filename first)
|
|
538
|
-
* descending, sync time as the tiebreak. This keeps "the newest digest"
|
|
539
|
-
* correct even when an OLD file happens to re-sync last.
|
|
540
|
-
*/
|
|
541
|
-
function newestMatch(files, pattern) {
|
|
542
|
-
const matched = matchArtifacts(files, pattern);
|
|
543
|
-
let best;
|
|
544
|
-
let bestKey = "";
|
|
545
|
-
for (const f of matched) {
|
|
546
|
-
const key = `${productDate(f).date}|${f.updatedAt}`;
|
|
547
|
-
if (!best || key > bestKey) {
|
|
548
|
-
best = f;
|
|
549
|
-
bestKey = key;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
return best;
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* `<loop-embed file="reports/digest-2026-07-01.md">` /
|
|
556
|
-
* `<loop-embed match="reports/digest-*.md">` - a loop's own produced file
|
|
557
|
-
* embedded in its dashboard. `file` targets one exact path; `match` resolves to
|
|
558
|
-
* the NEWEST matching artifact (filename date first, sync time as tiebreak), so
|
|
559
|
-
* "embed the latest digest" keeps working without the agent editing the
|
|
560
|
-
* template every run. `match` never selects the loop's task file - the spec is
|
|
561
|
-
* not a product (same rule as the calendar's default set), and its frequent
|
|
562
|
-
* edit-syncs would pin it as "newest" under a broad glob like `*.md`; an exact
|
|
563
|
-
* `file=` path may still target it. Content renders through the shared
|
|
564
|
-
* artifact viewer (markdown via .taskmd, mono pre for other text, download
|
|
565
|
-
* notice for binary), collapsed at 300px with a fade + "show all" toggle; the
|
|
566
|
-
* `full` attribute opts out of the collapse.
|
|
567
|
-
*/
|
|
568
|
-
/** Collapsed preview height. A pixel cap, not a line clamp - markdown blocks
|
|
569
|
-
* (tables, code) have uneven line heights, so pixels are the only stable cut. */
|
|
570
|
-
var COLLAPSE_PX = 300;
|
|
571
|
-
var norm = (p) => p.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
572
|
-
function LoopEmbed({ loopId, artifacts, file, match, full, taskFile }) {
|
|
573
|
-
const requested = file ?? match;
|
|
574
|
-
const shell = "min-w-0 overflow-hidden rounded-card border border-hairline bg-surface shadow-card";
|
|
575
|
-
if (!requested) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
576
|
-
className: shell,
|
|
577
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
578
|
-
className: "px-5 py-5 font-mono text-label text-disabled",
|
|
579
|
-
children: "<loop-embed> needs file=\"<path>\" or match=\"<glob>\""
|
|
580
|
-
})
|
|
581
|
-
});
|
|
582
|
-
if (artifacts == null) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
583
|
-
className: shell,
|
|
584
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewerHead, { path: requested }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
585
|
-
className: "px-5 py-6 text-label text-secondary",
|
|
586
|
-
children: "Loading…"
|
|
587
|
-
})]
|
|
588
|
-
});
|
|
589
|
-
const matchable = file ? artifacts : artifacts.filter((a) => !isTaskPath(taskFile, a.path));
|
|
590
|
-
const target = file ? artifacts.find((a) => norm(a.path) === norm(file)) : newestMatch(matchable, match);
|
|
591
|
-
if (!target) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
592
|
-
className: shell,
|
|
593
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewerHead, { path: requested }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
594
|
-
className: "px-5 py-6 text-body text-disabled",
|
|
595
|
-
children: "No synced file matches yet. It appears here after the loop's next run syncs it."
|
|
596
|
-
})]
|
|
597
|
-
});
|
|
598
|
-
const meta = `${match ? `newest of ${matchArtifacts(matchable, match).length} matching ${match} · ` : ""}synced ${fmt(target.updatedAt)}`;
|
|
599
|
-
const kind = artifactKind(target.path);
|
|
600
|
-
const collapse = !full && !(kind === "html" || kind === "image") && !target.binary && !target.oversize;
|
|
601
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
602
|
-
className: shell,
|
|
603
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewerHead, {
|
|
604
|
-
path: target.path,
|
|
605
|
-
meta,
|
|
606
|
-
action: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
|
|
607
|
-
href: "#files",
|
|
608
|
-
className: "text-caption font-medium text-interactive transition-colors hover:text-display",
|
|
609
|
-
children: "Open in files →"
|
|
610
|
-
})
|
|
611
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollapsibleBody, {
|
|
612
|
-
collapse,
|
|
613
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactBody, {
|
|
614
|
-
loopId,
|
|
615
|
-
file: target
|
|
616
|
-
})
|
|
617
|
-
})]
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* The collapse wrapper: clipping happens on THIS wrapper (overflow-hidden +
|
|
622
|
-
* max-height), never on the text nodes, with a fade overlay above the cut and
|
|
623
|
-
* the toggle in its own footer. The toggle appears only when the content
|
|
624
|
-
* actually overflows the cap (measured post-render, re-checked as the lazy
|
|
625
|
-
* content lands).
|
|
626
|
-
*/
|
|
627
|
-
function CollapsibleBody({ collapse, children }) {
|
|
628
|
-
const ref = (0, import_react.useRef)(null);
|
|
629
|
-
const [expanded, setExpanded] = (0, import_react.useState)(false);
|
|
630
|
-
const [overflowing, setOverflowing] = (0, import_react.useState)(false);
|
|
631
|
-
(0, import_react.useLayoutEffect)(() => {
|
|
632
|
-
const el = ref.current;
|
|
633
|
-
if (!el) return;
|
|
634
|
-
const over = el.scrollHeight > 301;
|
|
635
|
-
if (over !== overflowing) setOverflowing(over);
|
|
636
|
-
});
|
|
637
|
-
const collapsed = collapse && !expanded && overflowing;
|
|
638
|
-
if (!collapse) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
639
|
-
className: "min-w-0",
|
|
640
|
-
children
|
|
641
|
-
});
|
|
642
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
643
|
-
className: "relative min-w-0",
|
|
644
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
645
|
-
ref,
|
|
646
|
-
className: collapsed ? "overflow-hidden" : "",
|
|
647
|
-
style: collapsed ? { maxHeight: COLLAPSE_PX } : void 0,
|
|
648
|
-
children
|
|
649
|
-
}), collapsed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
650
|
-
"aria-hidden": true,
|
|
651
|
-
className: "pointer-events-none absolute inset-x-0 bottom-0 h-16",
|
|
652
|
-
style: { background: "linear-gradient(to bottom, transparent, var(--color-surface))" }
|
|
653
|
-
})]
|
|
654
|
-
}), overflowing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
655
|
-
className: "flex border-t border-hairline px-3.5 py-1.5",
|
|
656
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
657
|
-
type: "button",
|
|
658
|
-
onClick: () => setExpanded(!expanded),
|
|
659
|
-
className: "cursor-pointer border-none bg-transparent p-0 text-caption font-medium text-secondary transition-colors hover:text-display",
|
|
660
|
-
children: expanded ? "Collapse ▴" : "Show all ▾"
|
|
661
|
-
})
|
|
662
|
-
})] });
|
|
663
|
-
}
|
|
664
|
-
/**
|
|
665
|
-
* `<loop-calendar match="reports/*.md">` - the loop's products on a month
|
|
666
|
-
* grid. Each product lands on the day parsed from its FILENAME (ISO-ish
|
|
667
|
-
* patterns), falling back to its sync time (marked with a dashed chip - sync
|
|
668
|
-
* time can misattribute a re-synced old file, so the fallback is visible, not
|
|
669
|
-
* silent). Clicking a product reviews its full body in the shared Modal
|
|
670
|
-
* (focus trap, Esc, scroll lock come from Base UI Dialog - the kanban card
|
|
671
|
-
* pattern); close returns to the grid. `match` is optional - the default is
|
|
672
|
-
* every synced artifact except the task file.
|
|
673
|
-
*
|
|
674
|
-
* Monday-start (ISO), mono day numbers top-right, today inverted. Chips carry
|
|
675
|
-
* the product's basename (max 2 per day + a "+N" overflow); under ~620px of
|
|
676
|
-
* CONTAINER width they collapse to plain dots (measured, not a viewport media
|
|
677
|
-
* query - the dashboard box can be narrow on a wide screen).
|
|
678
|
-
*/
|
|
679
|
-
var DOW = [
|
|
680
|
-
"Mon",
|
|
681
|
-
"Tue",
|
|
682
|
-
"Wed",
|
|
683
|
-
"Thu",
|
|
684
|
-
"Fri",
|
|
685
|
-
"Sat",
|
|
686
|
-
"Sun"
|
|
687
|
-
];
|
|
688
|
-
var DOT_MODE_BELOW = 620;
|
|
689
|
-
var CHIPS_PER_DAY = 2;
|
|
690
|
-
var monthOf = (day) => day.slice(0, 7);
|
|
691
|
-
var basename$1 = (path) => path.split("/").pop() || path;
|
|
692
|
-
/** Human label for how a product got its day (front matter is authoritative). */
|
|
693
|
-
function dateSourceLabel(source) {
|
|
694
|
-
if (source === "frontmatter") return "dated by front matter";
|
|
695
|
-
if (source === "filename") return "dated by filename";
|
|
696
|
-
return "dated by sync time (no date in front matter/filename)";
|
|
697
|
-
}
|
|
698
|
-
var monthIndex = (ym) => {
|
|
699
|
-
const [y, m] = ym.split("-").map(Number);
|
|
700
|
-
return y * 12 + m;
|
|
701
|
-
};
|
|
702
|
-
/**
|
|
703
|
-
* The shown month clamped back into the data: an exact hit stays; a month
|
|
704
|
-
* whose products all vanished (poll refresh) snaps to the nearest remaining
|
|
705
|
-
* one; no pick defaults to the newest month. `months` must be non-empty.
|
|
706
|
-
*/
|
|
707
|
-
function clampMonth(months, shown) {
|
|
708
|
-
if (shown && months.includes(shown)) return shown;
|
|
709
|
-
let best = months[months.length - 1];
|
|
710
|
-
if (shown) {
|
|
711
|
-
const target = monthIndex(shown);
|
|
712
|
-
for (const m of months) if (Math.abs(monthIndex(m) - target) < Math.abs(monthIndex(best) - target)) best = m;
|
|
713
|
-
}
|
|
714
|
-
return best;
|
|
715
|
-
}
|
|
716
|
-
var monthLabel = (ym) => {
|
|
717
|
-
const [y, m] = ym.split("-").map(Number);
|
|
718
|
-
return `${new Date(Date.UTC(y, m - 1, 1)).toLocaleString("en-US", {
|
|
719
|
-
month: "long",
|
|
720
|
-
timeZone: "UTC"
|
|
721
|
-
})} ${y}`;
|
|
722
|
-
};
|
|
723
|
-
/** Chip text: basename without extension or a trailing date (digest-2026-07-01.md → "digest"). */
|
|
724
|
-
function chipName(path) {
|
|
725
|
-
const noExt = (path.split("/").pop() || path).replace(/\.[a-z0-9]+$/i, "");
|
|
726
|
-
return noExt.replace(/[-_]?\d{4}([-_]?)\d{2}\1\d{2}$/, "") || noExt;
|
|
727
|
-
}
|
|
728
|
-
/** Measured width of a container element (0 until first measure). */
|
|
729
|
-
function useContainerWidth(ref) {
|
|
730
|
-
const [width, setWidth] = (0, import_react.useState)(0);
|
|
731
|
-
(0, import_react.useEffect)(() => {
|
|
732
|
-
const el = ref.current;
|
|
733
|
-
if (!el || typeof ResizeObserver === "undefined") return;
|
|
734
|
-
const ro = new ResizeObserver((entries) => {
|
|
735
|
-
const w = entries[0]?.contentRect.width;
|
|
736
|
-
if (w != null) setWidth(w);
|
|
737
|
-
});
|
|
738
|
-
ro.observe(el);
|
|
739
|
-
return () => ro.disconnect();
|
|
740
|
-
}, [ref]);
|
|
741
|
-
return width;
|
|
742
|
-
}
|
|
743
|
-
function LoopCalendar({ loopId, artifacts, match, taskFile }) {
|
|
744
|
-
const rootRef = (0, import_react.useRef)(null);
|
|
745
|
-
const width = useContainerWidth(rootRef);
|
|
746
|
-
const dotMode = width > 0 && width < DOT_MODE_BELOW;
|
|
747
|
-
const products = (0, import_react.useMemo)(() => {
|
|
748
|
-
const files = matchArtifacts(artifacts ?? [], match);
|
|
749
|
-
return (match ? files : files.filter((f) => !isTaskPath(taskFile, f.path))).map((f) => ({
|
|
750
|
-
file: f,
|
|
751
|
-
...productDate(f)
|
|
752
|
-
}));
|
|
753
|
-
}, [
|
|
754
|
-
artifacts,
|
|
755
|
-
match,
|
|
756
|
-
taskFile
|
|
757
|
-
]);
|
|
758
|
-
const byDay = (0, import_react.useMemo)(() => {
|
|
759
|
-
const m = /* @__PURE__ */ new Map();
|
|
760
|
-
for (const p of products) {
|
|
761
|
-
const list = m.get(p.date) ?? [];
|
|
762
|
-
list.push(p);
|
|
763
|
-
m.set(p.date, list);
|
|
764
|
-
}
|
|
765
|
-
for (const list of m.values()) list.sort((a, b) => a.file.path < b.file.path ? -1 : 1);
|
|
766
|
-
return m;
|
|
767
|
-
}, [products]);
|
|
768
|
-
const months = (0, import_react.useMemo)(() => [...new Set(products.map((p) => monthOf(p.date)))].sort(), [products]);
|
|
769
|
-
const [shown, setShown] = (0, import_react.useState)(null);
|
|
770
|
-
const [reviewing, setReviewing] = (0, import_react.useState)(null);
|
|
771
|
-
const activeReview = reviewing && products.some((p) => p.file.path === reviewing.file.path) ? reviewing : null;
|
|
772
|
-
if (artifacts == null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
773
|
-
ref: rootRef,
|
|
774
|
-
className: "min-w-0",
|
|
775
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
776
|
-
className: "py-4 text-label text-secondary",
|
|
777
|
-
children: "Loading…"
|
|
778
|
-
})
|
|
779
|
-
});
|
|
780
|
-
if (!products.length) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
781
|
-
ref: rootRef,
|
|
782
|
-
className: "min-w-0",
|
|
783
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
784
|
-
className: "rounded-card border border-hairline px-5 py-8 text-center text-body text-disabled",
|
|
785
|
-
children: "No products yet. Files the loop writes appear here after its next run syncs them."
|
|
786
|
-
})
|
|
787
|
-
});
|
|
788
|
-
const month = clampMonth(months, shown);
|
|
789
|
-
const mi = months.indexOf(month);
|
|
790
|
-
const today = localDay((/* @__PURE__ */ new Date()).toISOString());
|
|
791
|
-
const [y, m] = month.split("-").map(Number);
|
|
792
|
-
const lead = (new Date(y, m - 1, 1).getDay() + 6) % 7;
|
|
793
|
-
const dayCount = new Date(y, m, 0).getDate();
|
|
794
|
-
const trail = (7 - (lead + dayCount) % 7) % 7;
|
|
795
|
-
const monthCount = products.filter((p) => monthOf(p.date) === month).length;
|
|
796
|
-
const navBtn = "inline-flex size-7 cursor-pointer items-center justify-center rounded-control border border-hairline bg-surface text-body leading-none text-secondary transition-colors hover:border-wire hover:bg-raised hover:text-display disabled:cursor-default disabled:opacity-30 disabled:hover:border-hairline disabled:hover:bg-surface";
|
|
797
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
798
|
-
ref: rootRef,
|
|
799
|
-
className: "min-w-0",
|
|
800
|
-
children: [
|
|
801
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
802
|
-
className: "mb-3 flex flex-wrap items-center gap-x-3 gap-y-2",
|
|
803
|
-
children: [
|
|
804
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
805
|
-
className: "flex items-center gap-1.5",
|
|
806
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
807
|
-
type: "button",
|
|
808
|
-
className: navBtn,
|
|
809
|
-
disabled: mi <= 0,
|
|
810
|
-
onClick: () => setShown(months[mi - 1]),
|
|
811
|
-
"aria-label": "previous month",
|
|
812
|
-
children: "‹"
|
|
813
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
814
|
-
type: "button",
|
|
815
|
-
className: navBtn,
|
|
816
|
-
disabled: mi >= months.length - 1,
|
|
817
|
-
onClick: () => setShown(months[mi + 1]),
|
|
818
|
-
"aria-label": "next month",
|
|
819
|
-
children: "›"
|
|
820
|
-
})]
|
|
821
|
-
}),
|
|
822
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
823
|
-
className: "text-body font-semibold tracking-tight text-display",
|
|
824
|
-
children: monthLabel(month)
|
|
825
|
-
}),
|
|
826
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
827
|
-
className: "ml-auto rounded-full bg-raised px-2.5 py-0.5 text-caption font-medium text-secondary",
|
|
828
|
-
children: [
|
|
829
|
-
monthCount,
|
|
830
|
-
" product",
|
|
831
|
-
monthCount === 1 ? "" : "s"
|
|
832
|
-
]
|
|
833
|
-
})
|
|
834
|
-
]
|
|
835
|
-
}),
|
|
836
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
837
|
-
className: "grid min-w-0 grid-cols-7 overflow-hidden rounded-card border-l border-t border-hairline",
|
|
838
|
-
children: [
|
|
839
|
-
DOW.map((d) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
840
|
-
className: "border-b border-r border-hairline bg-raised/50 px-2 py-1.5 text-right text-micro font-semibold uppercase tracking-wide text-disabled",
|
|
841
|
-
children: d
|
|
842
|
-
}, d)),
|
|
843
|
-
Array.from({ length: lead }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "border-b border-r border-hairline bg-raised/20" }, `lead${i}`)),
|
|
844
|
-
Array.from({ length: dayCount }, (_, i) => {
|
|
845
|
-
const day = `${month}-${String(i + 1).padStart(2, "0")}`;
|
|
846
|
-
const fs = byDay.get(day) ?? [];
|
|
847
|
-
const isToday = day === today;
|
|
848
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
849
|
-
className: `relative min-w-0 border-b border-r border-hairline px-1.5 pb-1.5 transition-colors ${dotMode ? "min-h-[48px] pt-5" : "min-h-[76px] pt-6"} ${isToday ? "bg-interactive-soft/40" : "bg-surface"}`,
|
|
850
|
-
children: [
|
|
851
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
852
|
-
className: `absolute right-1.5 top-1 inline-flex h-[15px] min-w-[15px] items-center justify-center font-mono text-micro ${isToday ? "rounded-full bg-display px-1 font-semibold text-paper" : fs.length ? "text-secondary" : "text-disabled"}`,
|
|
853
|
-
children: i + 1
|
|
854
|
-
}),
|
|
855
|
-
fs.slice(0, CHIPS_PER_DAY).map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProductChip, {
|
|
856
|
-
product: p,
|
|
857
|
-
dot: dotMode,
|
|
858
|
-
onPick: () => setReviewing(p)
|
|
859
|
-
}, p.file.path)),
|
|
860
|
-
fs.length > CHIPS_PER_DAY && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
861
|
-
type: "button",
|
|
862
|
-
onClick: () => setReviewing(fs[CHIPS_PER_DAY]),
|
|
863
|
-
className: "mt-1 block cursor-pointer border-none bg-transparent p-0 px-1 font-mono text-[9.5px] font-medium text-disabled transition-colors hover:text-display",
|
|
864
|
-
children: [
|
|
865
|
-
"+",
|
|
866
|
-
fs.length - CHIPS_PER_DAY,
|
|
867
|
-
" more"
|
|
868
|
-
]
|
|
869
|
-
})
|
|
870
|
-
]
|
|
871
|
-
}, day);
|
|
872
|
-
}),
|
|
873
|
-
Array.from({ length: trail }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "border-b border-r border-hairline bg-raised/20" }, `trail${i}`))
|
|
874
|
-
]
|
|
875
|
-
}),
|
|
876
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
877
|
-
className: "mt-3 flex flex-wrap gap-x-4 gap-y-1.5",
|
|
878
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
879
|
-
className: "inline-flex items-center gap-1.5 text-caption font-medium text-secondary",
|
|
880
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-block h-2.5 w-3.5 rounded-[3px] border border-hairline bg-raised" }), " Dated by filename"]
|
|
881
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
882
|
-
className: "inline-flex items-center gap-1.5 text-caption font-medium text-secondary",
|
|
883
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-block h-2.5 w-3.5 rounded-[3px] border border-dashed border-wire bg-raised" }), " Fallback: dated by sync time"]
|
|
884
|
-
})]
|
|
885
|
-
}),
|
|
886
|
-
activeReview && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Modal, {
|
|
887
|
-
open: true,
|
|
888
|
-
onClose: () => setReviewing(null),
|
|
889
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHead, {
|
|
890
|
-
title: activeReview.file.meta?.title?.trim() || basename$1(activeReview.file.path),
|
|
891
|
-
sub: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
892
|
-
className: "font-mono",
|
|
893
|
-
children: [
|
|
894
|
-
activeReview.file.path,
|
|
895
|
-
" · ",
|
|
896
|
-
dateSourceLabel(activeReview.source),
|
|
897
|
-
" · synced",
|
|
898
|
-
" ",
|
|
899
|
-
fmt(activeReview.file.updatedAt)
|
|
900
|
-
]
|
|
901
|
-
})
|
|
902
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
903
|
-
className: "mt-4 min-w-0",
|
|
904
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactBody, {
|
|
905
|
-
loopId,
|
|
906
|
-
file: activeReview.file
|
|
907
|
-
})
|
|
908
|
-
})]
|
|
909
|
-
})
|
|
910
|
-
]
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
/** One product on a day cell: a named chip, or a plain dot in narrow containers. */
|
|
914
|
-
function ProductChip({ product, dot, onPick }) {
|
|
915
|
-
const fallback = product.source === "sync";
|
|
916
|
-
const title = product.file.path + (fallback ? " · dated by sync time" : "");
|
|
917
|
-
if (dot) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
918
|
-
type: "button",
|
|
919
|
-
onClick: onPick,
|
|
920
|
-
title,
|
|
921
|
-
"aria-label": product.file.path,
|
|
922
|
-
className: `mr-1 mt-1 inline-block size-2 cursor-pointer rounded-full border-none p-0 transition-transform hover:scale-125 ${fallback ? "bg-disabled" : "bg-interactive"}`
|
|
923
|
-
});
|
|
924
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
925
|
-
type: "button",
|
|
926
|
-
onClick: onPick,
|
|
927
|
-
title,
|
|
928
|
-
className: `mt-1 block w-full min-w-0 cursor-pointer truncate rounded-full border border-hairline bg-raised px-2 py-0.5 text-left font-mono text-micro text-secondary transition-colors hover:border-wire hover:bg-surface hover:text-display ${fallback ? "border-dashed" : ""}`,
|
|
929
|
-
children: chipName(product.file.path)
|
|
930
|
-
});
|
|
931
|
-
}
|
|
932
|
-
/**
|
|
933
|
-
* `<loop-kanban columns="research,in-progress,done" match="notes/*.md">` - a
|
|
934
|
-
* COLLECTION VIEW over the loop's front-matter-typed markdown products, rendered
|
|
935
|
-
* as a board. There is NO board file: each artifact IS a card, grouped into the
|
|
936
|
-
* column whose name equals the artifact's front-matter `type`. A card "moves"
|
|
937
|
-
* when a run edits that artifact's `type` in place (the per-run diff shows the
|
|
938
|
-
* move).
|
|
939
|
-
*
|
|
940
|
-
* - `columns` (REQUIRED): comma-separated, explicit order = flow direction, one
|
|
941
|
-
* board column each.
|
|
942
|
-
* - Overflow: any typed artifact whose `type` matches no declared column lands
|
|
943
|
-
* in an automatic trailing "Other" column, so a typo stays visible and a card
|
|
944
|
-
* is never silently dropped. The overflow column is omitted when empty.
|
|
945
|
-
* - `match` (optional glob, same convention as loop-embed/loop-calendar): scopes
|
|
946
|
-
* which artifacts participate; default = every markdown product with a `type`.
|
|
947
|
-
* Untyped artifacts never appear, and the task file is always excluded (its
|
|
948
|
-
* status is the TASK chip, not a kanban type).
|
|
949
|
-
* - Cards: minimal - title (fallback filename) + a small date when the product
|
|
950
|
-
* is actually dated. Sorted date desc, then sync-time desc.
|
|
951
|
-
* - Click a card to review its full body in the shared Modal (focus trap, Esc,
|
|
952
|
-
* scroll lock come from Base UI Dialog); close returns to the board.
|
|
953
|
-
*
|
|
954
|
-
* Layout: the board row scrolls INSIDE its own pane (`min-w-0 overflow-x-auto`),
|
|
955
|
-
* columns are `shrink-0` fixed-width - a wide board never widens the dashboard
|
|
956
|
-
* box or forces a page-level horizontal scrollbar (the Timeline strip rule).
|
|
957
|
-
*/
|
|
958
|
-
var OVERFLOW_LABEL = "Other";
|
|
959
|
-
/** Bucket key for the automatic overflow column. A unique Symbol (not the display
|
|
960
|
-
* string) so it can never collide with an author-declared column named 'Other'. */
|
|
961
|
-
var OVERFLOW_KEY = Symbol("kanban-overflow");
|
|
962
|
-
/** React key for the overflow row. Column names are comma-split, so a name can
|
|
963
|
-
* never contain a comma - this sentinel cannot collide with a declared column. */
|
|
964
|
-
var OVERFLOW_REACT_KEY = "overflow,";
|
|
965
|
-
var basename = (path) => path.split("/").pop() || path;
|
|
966
|
-
/** Sort key: product date desc, then sync time desc (newest work first). Both
|
|
967
|
-
* parts are lexically ordered (YYYY-MM-DD / ISO), so a plain string compare on
|
|
968
|
-
* the descending key ranks them. */
|
|
969
|
-
var sortKey = (c) => `${c.date.date}|${c.file.updatedAt}`;
|
|
970
|
-
function LoopKanban({ loopId, artifacts, columns, match, taskFile }) {
|
|
971
|
-
const [reviewing, setReviewing] = (0, import_react.useState)(null);
|
|
972
|
-
const cols = [...new Set((columns ?? "").split(",").map((s) => s.trim()).filter(Boolean))];
|
|
973
|
-
const shell = "min-w-0";
|
|
974
|
-
if (!cols.length) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
975
|
-
className: `${shell} rounded-card border border-hairline bg-surface shadow-card`,
|
|
976
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
977
|
-
className: "px-5 py-5 font-mono text-label text-disabled",
|
|
978
|
-
children: "<loop-kanban> needs columns=\"<a,b,c>\""
|
|
979
|
-
})
|
|
980
|
-
});
|
|
981
|
-
if (artifacts == null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
982
|
-
className: shell,
|
|
983
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
984
|
-
className: "py-4 text-label text-secondary",
|
|
985
|
-
children: "Loading…"
|
|
986
|
-
})
|
|
987
|
-
});
|
|
988
|
-
const cards = matchArtifacts(artifacts, match).filter((a) => isMarkdown(a.path) && !!a.meta?.type && !isTaskPath(taskFile, a.path)).map((file) => ({
|
|
989
|
-
file,
|
|
990
|
-
title: file.meta?.title?.trim() || basename(file.path),
|
|
991
|
-
date: productDate(file)
|
|
992
|
-
}));
|
|
993
|
-
const declared = new Set(cols);
|
|
994
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
995
|
-
for (const card of cards) {
|
|
996
|
-
const type = card.file.meta.type.trim();
|
|
997
|
-
const key = declared.has(type) ? type : OVERFLOW_KEY;
|
|
998
|
-
const list = grouped.get(key) ?? [];
|
|
999
|
-
list.push(card);
|
|
1000
|
-
grouped.set(key, list);
|
|
1001
|
-
}
|
|
1002
|
-
for (const list of grouped.values()) list.sort((a, b) => sortKey(a) < sortKey(b) ? 1 : -1);
|
|
1003
|
-
const board = cols.map((name) => ({
|
|
1004
|
-
name,
|
|
1005
|
-
cards: grouped.get(name) ?? [],
|
|
1006
|
-
overflow: false
|
|
1007
|
-
}));
|
|
1008
|
-
const overflow = grouped.get(OVERFLOW_KEY) ?? [];
|
|
1009
|
-
if (overflow.length) board.push({
|
|
1010
|
-
name: OVERFLOW_LABEL,
|
|
1011
|
-
cards: overflow,
|
|
1012
|
-
overflow: true
|
|
1013
|
-
});
|
|
1014
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1015
|
-
className: `${shell} flex max-h-[420px] gap-3 overflow-x-auto pb-1`,
|
|
1016
|
-
children: [board.map((col) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1017
|
-
className: "flex min-h-0 w-[248px] shrink-0 flex-col",
|
|
1018
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1019
|
-
className: "mb-2 flex shrink-0 items-center gap-2 border-b border-hairline pb-1.5",
|
|
1020
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1021
|
-
className: `text-label font-semibold ${col.overflow ? "text-disabled" : "text-primary"}`,
|
|
1022
|
-
children: col.name
|
|
1023
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1024
|
-
className: "text-caption text-disabled",
|
|
1025
|
-
children: col.cards.length
|
|
1026
|
-
})]
|
|
1027
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ColumnCards, {
|
|
1028
|
-
count: col.cards.length,
|
|
1029
|
-
children: col.cards.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1030
|
-
className: "shrink-0 rounded-control border border-dashed border-hairline px-2.5 py-3 text-center text-caption text-disabled",
|
|
1031
|
-
children: "Empty"
|
|
1032
|
-
}) : col.cards.map((card) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KanbanCard, {
|
|
1033
|
-
card,
|
|
1034
|
-
onOpen: () => setReviewing(card)
|
|
1035
|
-
}, card.file.path))
|
|
1036
|
-
})]
|
|
1037
|
-
}, col.overflow ? OVERFLOW_REACT_KEY : col.name)), reviewing && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Modal, {
|
|
1038
|
-
open: true,
|
|
1039
|
-
onClose: () => setReviewing(null),
|
|
1040
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHead, {
|
|
1041
|
-
title: reviewing.title,
|
|
1042
|
-
sub: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1043
|
-
className: "font-mono",
|
|
1044
|
-
children: [
|
|
1045
|
-
reviewing.file.path,
|
|
1046
|
-
reviewing.date.source !== "sync" && ` · ${reviewing.date.date}`,
|
|
1047
|
-
" · synced",
|
|
1048
|
-
" ",
|
|
1049
|
-
fmt(reviewing.file.updatedAt)
|
|
1050
|
-
]
|
|
1051
|
-
})
|
|
1052
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1053
|
-
className: "mt-4 min-w-0",
|
|
1054
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArtifactBody, {
|
|
1055
|
-
loopId,
|
|
1056
|
-
file: reviewing.file
|
|
1057
|
-
})
|
|
1058
|
-
})]
|
|
1059
|
-
})]
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
/**
|
|
1063
|
-
* A column's scrollable card list. Cards are shrink-0 (they must overflow the
|
|
1064
|
-
* height-capped board, never compress to fit it); when there IS overflow below
|
|
1065
|
-
* the fold, a bottom fade + "scroll" pill signals it, disappearing at the end.
|
|
1066
|
-
*/
|
|
1067
|
-
function ColumnCards({ count, children }) {
|
|
1068
|
-
const ref = (0, import_react.useRef)(null);
|
|
1069
|
-
const [below, setBelow] = (0, import_react.useState)(false);
|
|
1070
|
-
const update = () => {
|
|
1071
|
-
const el = ref.current;
|
|
1072
|
-
if (el) setBelow(el.scrollTop + el.clientHeight < el.scrollHeight - 4);
|
|
1073
|
-
};
|
|
1074
|
-
(0, import_react.useEffect)(update, [count]);
|
|
1075
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1076
|
-
className: "relative flex min-h-0 flex-col",
|
|
1077
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1078
|
-
ref,
|
|
1079
|
-
onScroll: update,
|
|
1080
|
-
className: "flex min-h-0 flex-col gap-2 overflow-y-auto pb-1 pr-0.5",
|
|
1081
|
-
children
|
|
1082
|
-
}), below && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1083
|
-
className: "pointer-events-none absolute inset-x-0 bottom-0 flex h-12 items-end justify-center bg-gradient-to-t from-surface to-transparent",
|
|
1084
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1085
|
-
className: "mb-1 rounded-full border border-hairline bg-surface px-2 py-0.5 text-micro text-secondary shadow-card",
|
|
1086
|
-
children: "↓ scroll"
|
|
1087
|
-
})
|
|
1088
|
-
})]
|
|
1089
|
-
});
|
|
1090
|
-
}
|
|
1091
|
-
/** One artifact as a card: title + small date, click to review it in the modal. */
|
|
1092
|
-
function KanbanCard({ card, onOpen }) {
|
|
1093
|
-
const dated = card.date.source !== "sync";
|
|
1094
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1095
|
-
className: "min-w-0 shrink-0 overflow-hidden rounded-control border border-hairline bg-surface shadow-card transition-colors hover:border-wire",
|
|
1096
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
1097
|
-
type: "button",
|
|
1098
|
-
onClick: onOpen,
|
|
1099
|
-
className: "flex w-full min-w-0 cursor-pointer flex-col items-start gap-1 border-none bg-transparent px-2.5 py-2 text-left",
|
|
1100
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1101
|
-
className: "w-full min-w-0 truncate text-meta text-primary",
|
|
1102
|
-
title: card.file.path,
|
|
1103
|
-
children: card.title
|
|
1104
|
-
}), dated && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1105
|
-
className: "font-mono text-micro text-disabled",
|
|
1106
|
-
children: card.date.date
|
|
1107
|
-
})]
|
|
1108
|
-
})
|
|
1109
|
-
});
|
|
1110
|
-
}
|
|
1111
|
-
/**
|
|
1112
|
-
* Renders a loop's generative-UI template (agent-authored HTML on `Job.ui`).
|
|
1113
|
-
*
|
|
1114
|
-
* Pipeline: interpolate `{{ ... }}` scalar bindings with live run data → DOMPurify
|
|
1115
|
-
* sanitize (allowlisted HTML subset; NO script/handlers/raw-svg) → parse to React,
|
|
1116
|
-
* swapping the irreducible data primitives for their renderers. Everything
|
|
1117
|
-
* else (A/B panels, stat tiles, layout, text) is the agent's own HTML — there are
|
|
1118
|
-
* NO opinionated panel components.
|
|
1119
|
-
*
|
|
1120
|
-
* <loop-chart type="line" x="runIndex" series="mrr:MRR:$"></loop-chart>
|
|
1121
|
-
* <loop-embed match="reports/digest-*.md"></loop-embed> newest matching artifact, embedded
|
|
1122
|
-
* <loop-calendar match="reports/*.md"></loop-calendar> month calendar of produced files
|
|
1123
|
-
* <loop-kanban columns="a,b,c" match="notes/*.md"> typed products as a board, columns = type
|
|
1124
|
-
*
|
|
1125
|
-
* Registering a new primitive means moving three things together: LOOP_TAGS +
|
|
1126
|
-
* the sanitizer config below, the parser swap in `options`, and the skill's
|
|
1127
|
-
* authoring docs (references/dashboard.md plus concise run-prompt examples) - the sanitizer
|
|
1128
|
-
* allowlist and the skill prose must never drift apart.
|
|
1129
|
-
*/
|
|
1130
|
-
var LOOP_TAGS = [
|
|
1131
|
-
"loop-chart",
|
|
1132
|
-
"loop-embed",
|
|
1133
|
-
"loop-calendar",
|
|
1134
|
-
"loop-kanban"
|
|
1135
|
-
];
|
|
1136
|
-
/** Data-bearing attributes on the loop-* primitives (all parsed by us, never markup). */
|
|
1137
|
-
var LOOP_ATTRS = [
|
|
1138
|
-
...CHART_ATTRS,
|
|
1139
|
-
"file",
|
|
1140
|
-
"match",
|
|
1141
|
-
"full",
|
|
1142
|
-
"columns"
|
|
1143
|
-
];
|
|
1144
|
-
var ARTIFACT_RETRY_MAX = 3;
|
|
1145
|
-
var ARTIFACT_RETRY_MS = 4e3;
|
|
1146
|
-
var SANITIZE_CONFIG = {
|
|
1147
|
-
ALLOWED_TAGS: [
|
|
1148
|
-
"h1",
|
|
1149
|
-
"h2",
|
|
1150
|
-
"h3",
|
|
1151
|
-
"h4",
|
|
1152
|
-
"h5",
|
|
1153
|
-
"p",
|
|
1154
|
-
"b",
|
|
1155
|
-
"strong",
|
|
1156
|
-
"i",
|
|
1157
|
-
"em",
|
|
1158
|
-
"u",
|
|
1159
|
-
"s",
|
|
1160
|
-
"span",
|
|
1161
|
-
"div",
|
|
1162
|
-
"ul",
|
|
1163
|
-
"ol",
|
|
1164
|
-
"li",
|
|
1165
|
-
"table",
|
|
1166
|
-
"thead",
|
|
1167
|
-
"tbody",
|
|
1168
|
-
"tr",
|
|
1169
|
-
"td",
|
|
1170
|
-
"th",
|
|
1171
|
-
"code",
|
|
1172
|
-
"pre",
|
|
1173
|
-
"br",
|
|
1174
|
-
"hr",
|
|
1175
|
-
"small",
|
|
1176
|
-
"section",
|
|
1177
|
-
"header",
|
|
1178
|
-
"footer",
|
|
1179
|
-
"a",
|
|
1180
|
-
"figure",
|
|
1181
|
-
"figcaption",
|
|
1182
|
-
"mark",
|
|
1183
|
-
...LOOP_TAGS
|
|
1184
|
-
],
|
|
1185
|
-
ALLOWED_ATTR: [
|
|
1186
|
-
"style",
|
|
1187
|
-
"class",
|
|
1188
|
-
"href",
|
|
1189
|
-
"title",
|
|
1190
|
-
"target",
|
|
1191
|
-
"rel",
|
|
1192
|
-
...LOOP_ATTRS
|
|
1193
|
-
],
|
|
1194
|
-
ADD_TAGS: LOOP_TAGS,
|
|
1195
|
-
CUSTOM_ELEMENT_HANDLING: {
|
|
1196
|
-
tagNameCheck: new RegExp(`^(?:${LOOP_TAGS.join("|")})$`),
|
|
1197
|
-
attributeNameCheck: new RegExp(`^(?:${LOOP_ATTRS.join("|")})$`),
|
|
1198
|
-
allowCustomizedBuiltInElements: false
|
|
1199
|
-
}
|
|
1200
|
-
};
|
|
1201
|
-
purify.addHook("uponSanitizeAttribute", (node, data) => {
|
|
1202
|
-
const tag = node.nodeName?.toLowerCase();
|
|
1203
|
-
if (tag && LOOP_TAGS.includes(tag) && LOOP_ATTRS.includes(data.attrName)) data.forceKeepAttr = true;
|
|
1204
|
-
});
|
|
1205
|
-
function LoopView({ html, runs, loopId, taskFile }) {
|
|
1206
|
-
const clean = (0, import_react.useMemo)(() => {
|
|
1207
|
-
const ctx = buildBindingContext(runs);
|
|
1208
|
-
return purify.sanitize(resolveBindings(html, ctx), SANITIZE_CONFIG);
|
|
1209
|
-
}, [html, 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;
|
|
1213
|
-
const wantsArtifacts = /<loop-(embed|calendar|kanban)\b/.test(clean);
|
|
1214
|
-
const [artifacts, setArtifacts] = (0, import_react.useState)(null);
|
|
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
|
-
]);
|
|
1233
|
-
(0, import_react.useEffect)(() => {
|
|
1234
|
-
if (!wantsArtifacts) return;
|
|
1235
|
-
let alive = true;
|
|
1236
|
-
let retries = 0;
|
|
1237
|
-
let timer;
|
|
1238
|
-
const load = () => {
|
|
1239
|
-
getArtifacts({ data: { loopId } }).then((list) => alive && setArtifacts(list)).catch(() => {
|
|
1240
|
-
if (alive && retries < ARTIFACT_RETRY_MAX) timer = setTimeout(load, ARTIFACT_RETRY_MS * ++retries);
|
|
1241
|
-
});
|
|
1242
|
-
};
|
|
1243
|
-
load();
|
|
1244
|
-
return () => {
|
|
1245
|
-
alive = false;
|
|
1246
|
-
clearTimeout(timer);
|
|
1247
|
-
};
|
|
1248
|
-
}, [
|
|
1249
|
-
wantsArtifacts,
|
|
1250
|
-
loopId,
|
|
1251
|
-
newestRunId,
|
|
1252
|
-
newestRunLive
|
|
1253
|
-
]);
|
|
1254
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1255
|
-
className: "loopview text-[14px] leading-relaxed",
|
|
1256
|
-
children: esm_default(clean, (0, import_react.useMemo)(() => ({ replace: (node) => {
|
|
1257
|
-
if (!(node instanceof import_lib.Element)) return void 0;
|
|
1258
|
-
const a = node.attribs ?? {};
|
|
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
|
-
}
|
|
1270
|
-
if (node.name === "loop-embed") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopEmbed, {
|
|
1271
|
-
loopId,
|
|
1272
|
-
artifacts,
|
|
1273
|
-
file: a.file,
|
|
1274
|
-
match: a.match,
|
|
1275
|
-
full: "full" in a,
|
|
1276
|
-
taskFile
|
|
1277
|
-
});
|
|
1278
|
-
if (node.name === "loop-calendar") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopCalendar, {
|
|
1279
|
-
loopId,
|
|
1280
|
-
artifacts,
|
|
1281
|
-
match: a.match,
|
|
1282
|
-
taskFile
|
|
1283
|
-
});
|
|
1284
|
-
if (node.name === "loop-kanban") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopKanban, {
|
|
1285
|
-
loopId,
|
|
1286
|
-
artifacts,
|
|
1287
|
-
columns: a.columns,
|
|
1288
|
-
match: a.match,
|
|
1289
|
-
taskFile
|
|
1290
|
-
});
|
|
1291
|
-
} }), [
|
|
1292
|
-
chartRuns,
|
|
1293
|
-
loopId,
|
|
1294
|
-
taskFile,
|
|
1295
|
-
artifacts
|
|
1296
|
-
]))
|
|
1297
|
-
});
|
|
1298
|
-
}
|
|
1299
|
-
//#endregion
|
|
1300
|
-
export { LoopView };
|