@validation-os/dashboard 0.4.0 → 0.5.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.
- package/dist/index.d.ts +209 -28
- package/dist/index.js +730 -357
- package/dist/index.js.map +1 -1
- package/dist/styles.css +971 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
// src/dashboard-app.tsx
|
|
4
|
+
import { useCallback as useCallback4, useEffect as useEffect5, useState as useState8 } from "react";
|
|
5
|
+
|
|
3
6
|
// src/labels.ts
|
|
4
7
|
var REGISTER_LABEL = {
|
|
5
8
|
assumptions: "Assumptions",
|
|
@@ -28,44 +31,78 @@ var REGISTER_ORDER = [
|
|
|
28
31
|
"glossary",
|
|
29
32
|
"people"
|
|
30
33
|
];
|
|
34
|
+
var REGISTER_SUBTITLE = {
|
|
35
|
+
assumptions: "Falsifiable beliefs the plan rests on. Risk = Impact \xD7 (1 \u2212 max(0, Confidence)/100).",
|
|
36
|
+
experiments: "The tests that move Confidence in the beliefs.",
|
|
37
|
+
readings: "The evidence logged against experiments and beliefs.",
|
|
38
|
+
goals: "The outcomes the plan is steering toward.",
|
|
39
|
+
decisions: "The choices made, and what they rest on.",
|
|
40
|
+
glossary: "The shared vocabulary for this venture.",
|
|
41
|
+
people: "The people the plan touches."
|
|
42
|
+
};
|
|
43
|
+
var REGISTER_ICON = {
|
|
44
|
+
assumptions: "\u25CE",
|
|
45
|
+
experiments: "\u2697",
|
|
46
|
+
readings: "\u25A4",
|
|
47
|
+
goals: "\u25C7",
|
|
48
|
+
decisions: "\xA7",
|
|
49
|
+
glossary: "A",
|
|
50
|
+
people: "\u263A"
|
|
51
|
+
};
|
|
52
|
+
var REGISTER_GROUPS = [
|
|
53
|
+
{
|
|
54
|
+
label: "Registers",
|
|
55
|
+
registers: ["assumptions", "experiments", "readings", "goals", "decisions", "glossary"]
|
|
56
|
+
},
|
|
57
|
+
{ label: "Reference", registers: ["people"] }
|
|
58
|
+
];
|
|
31
59
|
|
|
32
|
-
// src/register-
|
|
60
|
+
// src/register-browser.tsx
|
|
61
|
+
import { useState as useState6 } from "react";
|
|
62
|
+
|
|
63
|
+
// src/drawer-shell.tsx
|
|
64
|
+
import { useEffect, useRef } from "react";
|
|
33
65
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4", children: registers.map((register) => /* @__PURE__ */ jsx(
|
|
40
|
-
StatTile,
|
|
41
|
-
{
|
|
42
|
-
label: REGISTER_LABEL[register],
|
|
43
|
-
value: counts[register] ?? 0,
|
|
44
|
-
href: hrefFor?.(register)
|
|
45
|
-
},
|
|
46
|
-
register
|
|
47
|
-
)) }),
|
|
48
|
-
caption ? /* @__PURE__ */ jsx("p", { className: "mt-4 text-sm text-neutral-500 dark:text-neutral-400", children: caption }) : null
|
|
49
|
-
] });
|
|
50
|
-
}
|
|
51
|
-
function StatTile({
|
|
52
|
-
label,
|
|
53
|
-
value,
|
|
54
|
-
href
|
|
66
|
+
function DrawerShell({
|
|
67
|
+
open,
|
|
68
|
+
onClose,
|
|
69
|
+
ariaLabel,
|
|
70
|
+
children
|
|
55
71
|
}) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
const panelRef = useRef(null);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (!open) return;
|
|
75
|
+
const onKey = (e) => {
|
|
76
|
+
if (e.key === "Escape") onClose();
|
|
77
|
+
};
|
|
78
|
+
document.addEventListener("keydown", onKey);
|
|
79
|
+
panelRef.current?.focus();
|
|
80
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
81
|
+
}, [open, onClose]);
|
|
82
|
+
if (!open) return null;
|
|
83
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
84
|
+
/* @__PURE__ */ jsx(
|
|
85
|
+
"button",
|
|
86
|
+
{
|
|
87
|
+
type: "button",
|
|
88
|
+
"aria-label": "Close",
|
|
89
|
+
onClick: onClose,
|
|
90
|
+
className: "vos-scrim"
|
|
91
|
+
}
|
|
92
|
+
),
|
|
93
|
+
/* @__PURE__ */ jsx(
|
|
94
|
+
"aside",
|
|
95
|
+
{
|
|
96
|
+
ref: panelRef,
|
|
97
|
+
role: "dialog",
|
|
98
|
+
"aria-modal": "true",
|
|
99
|
+
"aria-label": ariaLabel,
|
|
100
|
+
tabIndex: -1,
|
|
101
|
+
className: "vos-drawer",
|
|
102
|
+
children
|
|
103
|
+
}
|
|
104
|
+
)
|
|
59
105
|
] });
|
|
60
|
-
const className = "block rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900";
|
|
61
|
-
return href ? /* @__PURE__ */ jsx(
|
|
62
|
-
"a",
|
|
63
|
-
{
|
|
64
|
-
href,
|
|
65
|
-
className: `${className} transition-colors hover:border-neutral-300 hover:bg-neutral-50 dark:hover:border-neutral-700 dark:hover:bg-neutral-800`,
|
|
66
|
-
children: body
|
|
67
|
-
}
|
|
68
|
-
) : /* @__PURE__ */ jsx("div", { className, children: body });
|
|
69
106
|
}
|
|
70
107
|
|
|
71
108
|
// src/columns.ts
|
|
@@ -74,13 +111,19 @@ function derivedField(field) {
|
|
|
74
111
|
}
|
|
75
112
|
var COLUMNS = {
|
|
76
113
|
assumptions: [
|
|
77
|
-
{ key: "Title", header: "
|
|
114
|
+
{ key: "Title", header: "Belief" },
|
|
115
|
+
{
|
|
116
|
+
key: "Status",
|
|
117
|
+
header: "Status",
|
|
118
|
+
kind: "status"
|
|
119
|
+
},
|
|
78
120
|
{ key: "Impact", header: "Impact", align: "right" },
|
|
79
121
|
{
|
|
80
122
|
key: "confidence",
|
|
81
123
|
header: "Confidence",
|
|
82
124
|
align: "right",
|
|
83
125
|
derived: true,
|
|
126
|
+
kind: "confidence",
|
|
84
127
|
accessor: derivedField("confidence")
|
|
85
128
|
},
|
|
86
129
|
{
|
|
@@ -88,13 +131,13 @@ var COLUMNS = {
|
|
|
88
131
|
header: "Risk",
|
|
89
132
|
align: "right",
|
|
90
133
|
derived: true,
|
|
134
|
+
kind: "risk",
|
|
91
135
|
accessor: derivedField("risk")
|
|
92
|
-
}
|
|
93
|
-
{ key: "Status", header: "Status" }
|
|
136
|
+
}
|
|
94
137
|
],
|
|
95
138
|
experiments: [
|
|
96
139
|
{ key: "Title", header: "Experiment" },
|
|
97
|
-
{ key: "Status", header: "Status" },
|
|
140
|
+
{ key: "Status", header: "Status", kind: "status" },
|
|
98
141
|
{ key: "Feasibility", header: "Feasibility" }
|
|
99
142
|
],
|
|
100
143
|
readings: [
|
|
@@ -111,16 +154,16 @@ var COLUMNS = {
|
|
|
111
154
|
],
|
|
112
155
|
goals: [
|
|
113
156
|
{ key: "Title", header: "Goal" },
|
|
114
|
-
{ key: "Status", header: "Status" },
|
|
157
|
+
{ key: "Status", header: "Status", kind: "status" },
|
|
115
158
|
{ key: "Outcome", header: "Outcome" }
|
|
116
159
|
],
|
|
117
160
|
decisions: [
|
|
118
161
|
{ key: "Title", header: "Decision" },
|
|
119
|
-
{ key: "Status", header: "Status" }
|
|
162
|
+
{ key: "Status", header: "Status", kind: "status" }
|
|
120
163
|
],
|
|
121
164
|
glossary: [
|
|
122
165
|
{ key: "Title", header: "Term" },
|
|
123
|
-
{ key: "Status", header: "Status" }
|
|
166
|
+
{ key: "Status", header: "Status", kind: "status" }
|
|
124
167
|
],
|
|
125
168
|
people: [
|
|
126
169
|
{ key: "Name", header: "Name" },
|
|
@@ -186,8 +229,244 @@ function derivedLabel(key) {
|
|
|
186
229
|
return DERIVED_LABEL[key] ?? fieldLabel(key);
|
|
187
230
|
}
|
|
188
231
|
|
|
232
|
+
// src/primitives.ts
|
|
233
|
+
var GOOD_STATUS = /* @__PURE__ */ new Set([
|
|
234
|
+
"live",
|
|
235
|
+
"concluded",
|
|
236
|
+
"closed",
|
|
237
|
+
"done",
|
|
238
|
+
"resolved",
|
|
239
|
+
"accepted",
|
|
240
|
+
"adopted",
|
|
241
|
+
"shipped"
|
|
242
|
+
]);
|
|
243
|
+
var WARN_STATUS = /* @__PURE__ */ new Set([
|
|
244
|
+
"testing",
|
|
245
|
+
"running",
|
|
246
|
+
"in progress",
|
|
247
|
+
"in-progress",
|
|
248
|
+
"draft"
|
|
249
|
+
]);
|
|
250
|
+
var CRIT_STATUS = /* @__PURE__ */ new Set([
|
|
251
|
+
"invalidated",
|
|
252
|
+
"rejected",
|
|
253
|
+
"blocked",
|
|
254
|
+
"failed",
|
|
255
|
+
"at risk"
|
|
256
|
+
]);
|
|
257
|
+
function statusTone(status) {
|
|
258
|
+
if (!status) return "neutral";
|
|
259
|
+
const s = status.trim().toLowerCase();
|
|
260
|
+
if (GOOD_STATUS.has(s)) return "good";
|
|
261
|
+
if (WARN_STATUS.has(s)) return "warn";
|
|
262
|
+
if (CRIT_STATUS.has(s)) return "crit";
|
|
263
|
+
return "neutral";
|
|
264
|
+
}
|
|
265
|
+
var RISK_CRIT = 60;
|
|
266
|
+
var RISK_WARN = 30;
|
|
267
|
+
function riskLevel(risk) {
|
|
268
|
+
if (risk >= RISK_CRIT) return "crit";
|
|
269
|
+
if (risk >= RISK_WARN) return "warn";
|
|
270
|
+
return "good";
|
|
271
|
+
}
|
|
272
|
+
function riskFraction(risk) {
|
|
273
|
+
if (!Number.isFinite(risk)) return 0;
|
|
274
|
+
return Math.max(0, Math.min(100, risk)) / 100;
|
|
275
|
+
}
|
|
276
|
+
function confidenceTone(confidence) {
|
|
277
|
+
return confidence < 0 ? "crit" : "good";
|
|
278
|
+
}
|
|
279
|
+
function derivedTone(field, value) {
|
|
280
|
+
if (field === "confidence") return confidenceTone(value);
|
|
281
|
+
if (field === "risk") return riskLevel(value);
|
|
282
|
+
return "neutral";
|
|
283
|
+
}
|
|
284
|
+
function heroToneClass(tone) {
|
|
285
|
+
if (tone === "crit") return "vos-text-crit";
|
|
286
|
+
if (tone === "warn") return "vos-text-warn";
|
|
287
|
+
return "";
|
|
288
|
+
}
|
|
289
|
+
function formatSigned(n) {
|
|
290
|
+
const r = Math.round(n);
|
|
291
|
+
return r > 0 ? `+${r}` : String(r);
|
|
292
|
+
}
|
|
293
|
+
function formatCount(n) {
|
|
294
|
+
return n.toLocaleString();
|
|
295
|
+
}
|
|
296
|
+
function sparklinePath(values, width, height, min, max) {
|
|
297
|
+
if (values.length < 2) return "";
|
|
298
|
+
const lo = min ?? Math.min(...values, 0);
|
|
299
|
+
const hi = max ?? Math.max(...values, 0);
|
|
300
|
+
const span = hi - lo || 1;
|
|
301
|
+
const inset = 2;
|
|
302
|
+
return values.map((v, i) => {
|
|
303
|
+
const x = i / (values.length - 1) * (width - inset * 2) + inset;
|
|
304
|
+
const y = height - inset - (v - lo) / span * (height - inset * 2);
|
|
305
|
+
return `${i ? "L" : "M"}${x.toFixed(1)} ${y.toFixed(1)}`;
|
|
306
|
+
}).join(" ");
|
|
307
|
+
}
|
|
308
|
+
function sparklineY(value, height, lo, hi) {
|
|
309
|
+
const span = hi - lo || 1;
|
|
310
|
+
const inset = 2;
|
|
311
|
+
return height - inset - (value - lo) / span * (height - inset * 2);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/primitives-view.tsx
|
|
315
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
316
|
+
var PILL_CLASS = {
|
|
317
|
+
good: "vos-pill vos-pill-good",
|
|
318
|
+
warn: "vos-pill vos-pill-warn",
|
|
319
|
+
crit: "vos-pill vos-pill-crit",
|
|
320
|
+
accent: "vos-pill vos-pill-accent",
|
|
321
|
+
neutral: "vos-pill vos-pill-neutral"
|
|
322
|
+
};
|
|
323
|
+
function StatusPill({ status }) {
|
|
324
|
+
if (!status) return /* @__PURE__ */ jsx2("span", { className: "vos-muted", children: "\u2014" });
|
|
325
|
+
return /* @__PURE__ */ jsx2("span", { className: PILL_CLASS[statusTone(status)], children: status });
|
|
326
|
+
}
|
|
327
|
+
var FILL_CLASS = {
|
|
328
|
+
good: "vos-fill-good",
|
|
329
|
+
warn: "vos-fill-warn",
|
|
330
|
+
crit: "vos-fill-crit",
|
|
331
|
+
accent: "vos-fill-good",
|
|
332
|
+
neutral: "vos-fill-good"
|
|
333
|
+
};
|
|
334
|
+
var TEXT_CLASS = {
|
|
335
|
+
good: "vos-text-good",
|
|
336
|
+
warn: "vos-text-warn",
|
|
337
|
+
crit: "vos-text-crit",
|
|
338
|
+
accent: "vos-text-good",
|
|
339
|
+
neutral: ""
|
|
340
|
+
};
|
|
341
|
+
function RiskBar({ risk }) {
|
|
342
|
+
const level = riskLevel(risk);
|
|
343
|
+
const pct = Math.round(riskFraction(risk) * 100);
|
|
344
|
+
return /* @__PURE__ */ jsxs2("span", { className: "vos-metric-cell", children: [
|
|
345
|
+
/* @__PURE__ */ jsx2(
|
|
346
|
+
"span",
|
|
347
|
+
{
|
|
348
|
+
className: "vos-risk-bar",
|
|
349
|
+
role: "img",
|
|
350
|
+
"aria-label": `Risk ${Math.round(risk)} of 100`,
|
|
351
|
+
children: /* @__PURE__ */ jsx2("i", { className: FILL_CLASS[level], style: { width: `${pct}%` } })
|
|
352
|
+
}
|
|
353
|
+
),
|
|
354
|
+
/* @__PURE__ */ jsx2("b", { className: `vos-metric-num ${TEXT_CLASS[level]}`, children: Math.round(risk) })
|
|
355
|
+
] });
|
|
356
|
+
}
|
|
357
|
+
function ConfidenceCell({
|
|
358
|
+
confidence,
|
|
359
|
+
history
|
|
360
|
+
}) {
|
|
361
|
+
const tone = confidenceTone(confidence);
|
|
362
|
+
return /* @__PURE__ */ jsxs2("span", { className: "vos-metric-cell", children: [
|
|
363
|
+
history && history.length >= 2 ? /* @__PURE__ */ jsx2(
|
|
364
|
+
Sparkline,
|
|
365
|
+
{
|
|
366
|
+
values: history,
|
|
367
|
+
width: 46,
|
|
368
|
+
height: 16,
|
|
369
|
+
min: -100,
|
|
370
|
+
max: 100,
|
|
371
|
+
tone
|
|
372
|
+
}
|
|
373
|
+
) : null,
|
|
374
|
+
/* @__PURE__ */ jsx2("b", { className: `vos-metric-num ${TEXT_CLASS[tone]}`, children: formatSigned(confidence) })
|
|
375
|
+
] });
|
|
376
|
+
}
|
|
377
|
+
var STROKE_VAR = {
|
|
378
|
+
good: "var(--vos-good)",
|
|
379
|
+
warn: "var(--vos-warn)",
|
|
380
|
+
crit: "var(--vos-crit)",
|
|
381
|
+
accent: "var(--vos-accent)",
|
|
382
|
+
neutral: "var(--vos-muted)"
|
|
383
|
+
};
|
|
384
|
+
function Sparkline({
|
|
385
|
+
values,
|
|
386
|
+
width = 240,
|
|
387
|
+
height = 44,
|
|
388
|
+
min,
|
|
389
|
+
max,
|
|
390
|
+
tone = "good",
|
|
391
|
+
fill = false,
|
|
392
|
+
ariaLabel
|
|
393
|
+
}) {
|
|
394
|
+
const d = sparklinePath(values, width, height, min, max);
|
|
395
|
+
if (!d) return null;
|
|
396
|
+
const lo = min ?? Math.min(...values, 0);
|
|
397
|
+
const hi = max ?? Math.max(...values, 0);
|
|
398
|
+
const stroke = STROKE_VAR[tone];
|
|
399
|
+
const last = values[values.length - 1];
|
|
400
|
+
const lastX = width - 2;
|
|
401
|
+
const lastY = sparklineY(last, height, lo, hi);
|
|
402
|
+
const zeroY = sparklineY(0, height, lo, hi);
|
|
403
|
+
const showBaseline = lo < 0 && hi > 0;
|
|
404
|
+
return /* @__PURE__ */ jsxs2(
|
|
405
|
+
"svg",
|
|
406
|
+
{
|
|
407
|
+
className: "vos-spark",
|
|
408
|
+
width,
|
|
409
|
+
height,
|
|
410
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
411
|
+
preserveAspectRatio: "none",
|
|
412
|
+
role: "img",
|
|
413
|
+
"aria-label": ariaLabel ?? `Trend, now ${formatSigned(last)}`,
|
|
414
|
+
children: [
|
|
415
|
+
fill ? /* @__PURE__ */ jsx2(
|
|
416
|
+
"path",
|
|
417
|
+
{
|
|
418
|
+
d: `${d} L ${width - 2} ${height} L 2 ${height} Z`,
|
|
419
|
+
fill: stroke,
|
|
420
|
+
opacity: "0.13"
|
|
421
|
+
}
|
|
422
|
+
) : null,
|
|
423
|
+
showBaseline ? /* @__PURE__ */ jsx2(
|
|
424
|
+
"line",
|
|
425
|
+
{
|
|
426
|
+
x1: 2,
|
|
427
|
+
x2: width - 2,
|
|
428
|
+
y1: zeroY,
|
|
429
|
+
y2: zeroY,
|
|
430
|
+
stroke: "var(--vos-border-strong)",
|
|
431
|
+
strokeWidth: 1,
|
|
432
|
+
strokeDasharray: "3 3"
|
|
433
|
+
}
|
|
434
|
+
) : null,
|
|
435
|
+
/* @__PURE__ */ jsx2("path", { d, fill: "none", stroke, strokeWidth: 2 }),
|
|
436
|
+
/* @__PURE__ */ jsx2("circle", { cx: lastX, cy: lastY, r: 3, fill: stroke })
|
|
437
|
+
]
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
function StatTile({
|
|
442
|
+
label,
|
|
443
|
+
value,
|
|
444
|
+
sub,
|
|
445
|
+
onClick,
|
|
446
|
+
active
|
|
447
|
+
}) {
|
|
448
|
+
const body = /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
449
|
+
/* @__PURE__ */ jsx2("div", { className: "vos-tile-label", children: label }),
|
|
450
|
+
/* @__PURE__ */ jsx2("div", { className: "vos-tile-value", children: formatCount(value) }),
|
|
451
|
+
sub ? /* @__PURE__ */ jsx2("div", { className: "vos-tile-sub", children: sub }) : null
|
|
452
|
+
] });
|
|
453
|
+
if (onClick) {
|
|
454
|
+
return /* @__PURE__ */ jsx2(
|
|
455
|
+
"button",
|
|
456
|
+
{
|
|
457
|
+
type: "button",
|
|
458
|
+
className: "vos-tile",
|
|
459
|
+
onClick,
|
|
460
|
+
"aria-pressed": active,
|
|
461
|
+
children: body
|
|
462
|
+
}
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
return /* @__PURE__ */ jsx2("div", { className: "vos-tile", children: body });
|
|
466
|
+
}
|
|
467
|
+
|
|
189
468
|
// src/register-table.tsx
|
|
190
|
-
import { jsx as
|
|
469
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
191
470
|
function RegisterTable({
|
|
192
471
|
register,
|
|
193
472
|
records,
|
|
@@ -196,21 +475,21 @@ function RegisterTable({
|
|
|
196
475
|
}) {
|
|
197
476
|
const columns = columnsFor(register);
|
|
198
477
|
if (records.length === 0) {
|
|
199
|
-
return /* @__PURE__ */
|
|
478
|
+
return /* @__PURE__ */ jsx3("p", { className: "vos-empty", children: "No records yet." });
|
|
200
479
|
}
|
|
201
|
-
return /* @__PURE__ */
|
|
202
|
-
/* @__PURE__ */
|
|
480
|
+
return /* @__PURE__ */ jsx3("div", { className: "vos-card vos-table-scroll", children: /* @__PURE__ */ jsxs3("table", { className: "vos-table", children: [
|
|
481
|
+
/* @__PURE__ */ jsx3("thead", { children: /* @__PURE__ */ jsx3("tr", { children: columns.map((c) => /* @__PURE__ */ jsx3(
|
|
203
482
|
"th",
|
|
204
483
|
{
|
|
205
484
|
scope: "col",
|
|
206
|
-
className:
|
|
485
|
+
className: c.align === "right" ? "vos-r" : void 0,
|
|
207
486
|
children: c.header
|
|
208
487
|
},
|
|
209
488
|
c.key
|
|
210
489
|
)) }) }),
|
|
211
|
-
/* @__PURE__ */
|
|
490
|
+
/* @__PURE__ */ jsx3("tbody", { children: records.map((record) => {
|
|
212
491
|
const isSelected = record.id === selectedId;
|
|
213
|
-
return /* @__PURE__ */
|
|
492
|
+
return /* @__PURE__ */ jsx3(
|
|
214
493
|
"tr",
|
|
215
494
|
{
|
|
216
495
|
onClick: () => onRowClick?.(record.id),
|
|
@@ -222,75 +501,43 @@ function RegisterTable({
|
|
|
222
501
|
} : void 0,
|
|
223
502
|
tabIndex: onRowClick ? 0 : void 0,
|
|
224
503
|
"aria-selected": isSelected,
|
|
225
|
-
className:
|
|
226
|
-
children: columns.map((c, i) =>
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
},
|
|
235
|
-
c.key
|
|
236
|
-
);
|
|
237
|
-
})
|
|
504
|
+
className: isSelected ? "is-selected" : void 0,
|
|
505
|
+
children: columns.map((c, i) => /* @__PURE__ */ jsx3(
|
|
506
|
+
"td",
|
|
507
|
+
{
|
|
508
|
+
className: c.align === "right" ? "vos-r" : void 0,
|
|
509
|
+
children: /* @__PURE__ */ jsx3(Cell, { column: c, record, headline: i === 0 })
|
|
510
|
+
},
|
|
511
|
+
c.key
|
|
512
|
+
))
|
|
238
513
|
},
|
|
239
514
|
record.id
|
|
240
515
|
);
|
|
241
516
|
}) })
|
|
242
517
|
] }) });
|
|
243
518
|
}
|
|
519
|
+
function Cell({
|
|
520
|
+
column,
|
|
521
|
+
record,
|
|
522
|
+
headline
|
|
523
|
+
}) {
|
|
524
|
+
const raw = cellValue(column, record);
|
|
525
|
+
if (column.kind === "status") {
|
|
526
|
+
return /* @__PURE__ */ jsx3(StatusPill, { status: raw == null ? null : String(raw) });
|
|
527
|
+
}
|
|
528
|
+
if (column.kind === "risk") {
|
|
529
|
+
return typeof raw === "number" ? /* @__PURE__ */ jsx3(RiskBar, { risk: raw }) : /* @__PURE__ */ jsx3("span", { className: "vos-muted", children: "\u2014" });
|
|
530
|
+
}
|
|
531
|
+
if (column.kind === "confidence") {
|
|
532
|
+
return typeof raw === "number" ? /* @__PURE__ */ jsx3(ConfidenceCell, { confidence: raw }) : /* @__PURE__ */ jsx3("span", { className: "vos-muted", children: "\u2014" });
|
|
533
|
+
}
|
|
534
|
+
const text = headline && (raw === null || raw === void 0 || raw === "") ? primaryLabel(record) : formatValue(raw);
|
|
535
|
+
return /* @__PURE__ */ jsx3("span", { className: headline ? "vos-ttl" : void 0, children: text });
|
|
536
|
+
}
|
|
244
537
|
|
|
245
538
|
// src/record-drawer.tsx
|
|
246
539
|
import { useEffect as useEffect3, useState as useState2 } from "react";
|
|
247
540
|
|
|
248
|
-
// src/drawer-shell.tsx
|
|
249
|
-
import { useEffect, useRef } from "react";
|
|
250
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
251
|
-
function DrawerShell({
|
|
252
|
-
open,
|
|
253
|
-
onClose,
|
|
254
|
-
ariaLabel,
|
|
255
|
-
scroll = true,
|
|
256
|
-
children
|
|
257
|
-
}) {
|
|
258
|
-
const panelRef = useRef(null);
|
|
259
|
-
useEffect(() => {
|
|
260
|
-
if (!open) return;
|
|
261
|
-
const onKey = (e) => {
|
|
262
|
-
if (e.key === "Escape") onClose();
|
|
263
|
-
};
|
|
264
|
-
document.addEventListener("keydown", onKey);
|
|
265
|
-
panelRef.current?.focus();
|
|
266
|
-
return () => document.removeEventListener("keydown", onKey);
|
|
267
|
-
}, [open, onClose]);
|
|
268
|
-
if (!open) return null;
|
|
269
|
-
return /* @__PURE__ */ jsxs3("div", { className: "fixed inset-0 z-50 flex justify-end", children: [
|
|
270
|
-
/* @__PURE__ */ jsx3(
|
|
271
|
-
"button",
|
|
272
|
-
{
|
|
273
|
-
type: "button",
|
|
274
|
-
"aria-label": "Close",
|
|
275
|
-
onClick: onClose,
|
|
276
|
-
className: "absolute inset-0 bg-neutral-950/30 backdrop-blur-sm"
|
|
277
|
-
}
|
|
278
|
-
),
|
|
279
|
-
/* @__PURE__ */ jsx3(
|
|
280
|
-
"div",
|
|
281
|
-
{
|
|
282
|
-
ref: panelRef,
|
|
283
|
-
role: "dialog",
|
|
284
|
-
"aria-modal": "true",
|
|
285
|
-
"aria-label": ariaLabel,
|
|
286
|
-
tabIndex: -1,
|
|
287
|
-
className: `relative flex h-full w-full max-w-md flex-col border-l border-neutral-200 bg-white shadow-xl outline-none dark:border-neutral-800 dark:bg-neutral-950 ${scroll ? "overflow-y-auto" : "overflow-hidden"}`,
|
|
288
|
-
children
|
|
289
|
-
}
|
|
290
|
-
)
|
|
291
|
-
] });
|
|
292
|
-
}
|
|
293
|
-
|
|
294
541
|
// src/edit.ts
|
|
295
542
|
var CONFLICT_MESSAGE = "Someone edited this while you had it open \u2014 your changes are safe, take a look before saving again.";
|
|
296
543
|
var t = (key, label) => ({ key, label, kind: "text" });
|
|
@@ -573,7 +820,7 @@ function buildUnderstanding(assumption, readings, experiments) {
|
|
|
573
820
|
}
|
|
574
821
|
|
|
575
822
|
// src/understanding-panel.tsx
|
|
576
|
-
import { Fragment as
|
|
823
|
+
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
577
824
|
function UnderstandingPanel({
|
|
578
825
|
assumption,
|
|
579
826
|
basePath
|
|
@@ -599,135 +846,91 @@ function UnderstandingPanel({
|
|
|
599
846
|
...u.otherMovers.map((m) => m.magnitude),
|
|
600
847
|
0.01
|
|
601
848
|
);
|
|
602
|
-
return /* @__PURE__ */ jsxs4(
|
|
849
|
+
return /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
603
850
|
u.experiments.length ? /* @__PURE__ */ jsxs4("section", { children: [
|
|
604
|
-
/* @__PURE__ */ jsx4(
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
{
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
851
|
+
/* @__PURE__ */ jsx4("div", { className: "vos-why-section-title", children: "What's moving Confidence" }),
|
|
852
|
+
u.experiments.map((e) => /* @__PURE__ */ jsx4(
|
|
853
|
+
PushRow,
|
|
854
|
+
{
|
|
855
|
+
label: e.title ?? `Experiment ${e.experimentId}`,
|
|
856
|
+
note: experimentDetail(e),
|
|
857
|
+
contribution: e.contribution,
|
|
858
|
+
magnitude: e.magnitude,
|
|
859
|
+
max: maxMagnitude,
|
|
860
|
+
done: e.done
|
|
861
|
+
},
|
|
862
|
+
e.experimentId
|
|
863
|
+
))
|
|
617
864
|
] }) : null,
|
|
618
865
|
u.otherMovers.length ? /* @__PURE__ */ jsxs4("section", { children: [
|
|
619
|
-
/* @__PURE__ */ jsx4(
|
|
620
|
-
|
|
866
|
+
/* @__PURE__ */ jsx4("div", { className: "vos-why-section-title", children: "Other evidence" }),
|
|
867
|
+
u.otherMovers.map((m) => /* @__PURE__ */ jsx4(
|
|
621
868
|
PushRow,
|
|
622
869
|
{
|
|
623
870
|
label: otherMoverLabel(m),
|
|
624
871
|
contribution: m.contribution,
|
|
625
872
|
magnitude: m.magnitude,
|
|
626
873
|
max: maxMagnitude
|
|
627
|
-
}
|
|
628
|
-
|
|
874
|
+
},
|
|
875
|
+
m.key
|
|
876
|
+
))
|
|
629
877
|
] }) : null,
|
|
630
|
-
/* @__PURE__ */
|
|
631
|
-
/* @__PURE__ */ jsx4(Heading, { children: "Confidence over time" }),
|
|
632
|
-
/* @__PURE__ */ jsx4(Trajectory, { points: u.trajectory })
|
|
633
|
-
] })
|
|
878
|
+
/* @__PURE__ */ jsx4(Trajectory, { points: u.trajectory })
|
|
634
879
|
] });
|
|
635
880
|
}
|
|
636
881
|
function PushRow({
|
|
637
882
|
label,
|
|
883
|
+
note,
|
|
638
884
|
contribution,
|
|
639
885
|
magnitude,
|
|
640
|
-
max
|
|
886
|
+
max,
|
|
887
|
+
done
|
|
641
888
|
}) {
|
|
642
889
|
const up = contribution >= 0;
|
|
643
890
|
const moving = magnitude > 0;
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
up ? "+" : "",
|
|
653
|
-
contribution
|
|
654
|
-
]
|
|
655
|
-
}
|
|
656
|
-
) : /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-xs text-neutral-400", children: "no readings yet" })
|
|
657
|
-
] }),
|
|
658
|
-
/* @__PURE__ */ jsx4("div", { className: "mt-1 h-1.5 rounded-full bg-neutral-200 dark:bg-neutral-800", children: /* @__PURE__ */ jsx4(
|
|
659
|
-
"div",
|
|
891
|
+
const width = Math.round(magnitude / max * 50);
|
|
892
|
+
const fill = up ? { left: "50%", width: `${width}%`, background: "var(--vos-good)" } : { right: "50%", width: `${width}%`, background: "var(--vos-crit)" };
|
|
893
|
+
return /* @__PURE__ */ jsxs4("div", { className: "vos-mover", children: [
|
|
894
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-mover-name", children: label }),
|
|
895
|
+
note ? /* @__PURE__ */ jsx4("span", { className: `vos-mover-note ${done ? "vos-text-good" : "vos-text-warn"}`, children: note }) : null,
|
|
896
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-track vos-signed", children: moving ? /* @__PURE__ */ jsx4("i", { style: fill }) : null }),
|
|
897
|
+
/* @__PURE__ */ jsx4(
|
|
898
|
+
"span",
|
|
660
899
|
{
|
|
661
|
-
className:
|
|
662
|
-
style: {
|
|
900
|
+
className: "vos-mover-val",
|
|
901
|
+
style: { color: up ? "var(--vos-good)" : "var(--vos-crit)" },
|
|
902
|
+
children: moving ? formatSigned(contribution) : "\u2014"
|
|
663
903
|
}
|
|
664
|
-
)
|
|
904
|
+
)
|
|
665
905
|
] });
|
|
666
906
|
}
|
|
667
907
|
function Trajectory({ points }) {
|
|
668
908
|
if (points.length < 2) {
|
|
669
|
-
return /* @__PURE__ */ jsx4("p", { className: "
|
|
909
|
+
return /* @__PURE__ */ jsx4("p", { className: "vos-hint", children: points.length === 1 ? `One dated reading so far \u2014 Confidence ${formatSigned(points[0].confidence)} on ${points[0].date}.` : "No dated readings yet to chart a trajectory." });
|
|
670
910
|
}
|
|
671
|
-
return /* @__PURE__ */ jsx4(Sparkline, { points });
|
|
672
|
-
}
|
|
673
|
-
var SPARK_W = 260;
|
|
674
|
-
var SPARK_H = 44;
|
|
675
|
-
function Sparkline({ points }) {
|
|
676
|
-
const n = points.length;
|
|
677
|
-
const x = (i) => i / (n - 1) * SPARK_W;
|
|
678
|
-
const y = (c) => (100 - c) / 200 * SPARK_H;
|
|
679
|
-
const path = points.map((p, i) => `${x(i)},${y(p.confidence)}`).join(" ");
|
|
680
911
|
const first = points[0];
|
|
681
|
-
const last = points[
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
912
|
+
const last = points[points.length - 1];
|
|
913
|
+
const values = points.map((p) => p.confidence);
|
|
914
|
+
return /* @__PURE__ */ jsxs4("div", { className: "vos-traj", children: [
|
|
915
|
+
/* @__PURE__ */ jsx4("div", { className: "vos-traj-head", children: /* @__PURE__ */ jsx4("span", { className: "vos-lbl", children: "Confidence over time" }) }),
|
|
916
|
+
/* @__PURE__ */ jsx4(
|
|
917
|
+
Sparkline,
|
|
685
918
|
{
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
x1: 0,
|
|
695
|
-
x2: SPARK_W,
|
|
696
|
-
y1: y(0),
|
|
697
|
-
y2: y(0),
|
|
698
|
-
className: "stroke-neutral-300 dark:stroke-neutral-700",
|
|
699
|
-
strokeWidth: 1,
|
|
700
|
-
strokeDasharray: "3 3"
|
|
701
|
-
}
|
|
702
|
-
),
|
|
703
|
-
/* @__PURE__ */ jsx4(
|
|
704
|
-
"polyline",
|
|
705
|
-
{
|
|
706
|
-
points: path,
|
|
707
|
-
fill: "none",
|
|
708
|
-
className: "stroke-neutral-700 dark:stroke-neutral-200",
|
|
709
|
-
strokeWidth: 1.5,
|
|
710
|
-
strokeLinejoin: "round",
|
|
711
|
-
strokeLinecap: "round"
|
|
712
|
-
}
|
|
713
|
-
),
|
|
714
|
-
/* @__PURE__ */ jsx4(
|
|
715
|
-
"circle",
|
|
716
|
-
{
|
|
717
|
-
cx: x(n - 1),
|
|
718
|
-
cy: y(last.confidence),
|
|
719
|
-
r: 2.5,
|
|
720
|
-
className: "fill-neutral-900 dark:fill-neutral-50"
|
|
721
|
-
}
|
|
722
|
-
)
|
|
723
|
-
]
|
|
919
|
+
values,
|
|
920
|
+
width: 260,
|
|
921
|
+
height: 44,
|
|
922
|
+
min: -100,
|
|
923
|
+
max: 100,
|
|
924
|
+
tone: confidenceTone(last.confidence),
|
|
925
|
+
fill: true,
|
|
926
|
+
ariaLabel: `Confidence moved from ${formatSigned(first.confidence)} to ${formatSigned(last.confidence)}`
|
|
724
927
|
}
|
|
725
928
|
),
|
|
726
|
-
/* @__PURE__ */ jsxs4("div", { className: "
|
|
929
|
+
/* @__PURE__ */ jsxs4("div", { className: "vos-traj-foot", children: [
|
|
727
930
|
/* @__PURE__ */ jsx4("span", { children: first.date }),
|
|
728
|
-
/* @__PURE__ */ jsxs4("span", { className: "
|
|
931
|
+
/* @__PURE__ */ jsxs4("span", { className: "vos-num", children: [
|
|
729
932
|
"now ",
|
|
730
|
-
last.confidence
|
|
933
|
+
formatSigned(last.confidence)
|
|
731
934
|
] })
|
|
732
935
|
] })
|
|
733
936
|
] });
|
|
@@ -745,16 +948,21 @@ function otherMoverLabel(m) {
|
|
|
745
948
|
if (m.kind === "goal") return `Goal ${m.goalId ?? ""}`.trim();
|
|
746
949
|
return m.readingCount === 1 ? "A direct reading" : "Direct readings";
|
|
747
950
|
}
|
|
748
|
-
function Heading({ children }) {
|
|
749
|
-
return /* @__PURE__ */ jsx4("h4", { className: "mb-2 text-xs font-medium uppercase tracking-wide text-neutral-400", children });
|
|
750
|
-
}
|
|
751
951
|
function Muted({ children }) {
|
|
752
|
-
return /* @__PURE__ */ jsx4("p", { className: "
|
|
952
|
+
return /* @__PURE__ */ jsx4("p", { className: "vos-hint", children });
|
|
753
953
|
}
|
|
754
954
|
|
|
755
955
|
// src/record-drawer.tsx
|
|
756
|
-
import { Fragment as
|
|
956
|
+
import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
757
957
|
var META_FIELDS = /* @__PURE__ */ new Set(["id", "version", "createdAt", "updatedAt", "derived"]);
|
|
958
|
+
var DERIVED_SUB = {
|
|
959
|
+
confidence: "Signed average of concluded readings",
|
|
960
|
+
risk: "Impact \xD7 (1 \u2212 Confidence\u207A/100)",
|
|
961
|
+
derivedImpact: "Impact re-weighted by links",
|
|
962
|
+
impact: "Impact re-weighted by links",
|
|
963
|
+
strength: "of the evidence base",
|
|
964
|
+
sourceQuality: "of the source"
|
|
965
|
+
};
|
|
758
966
|
function RecordDrawer({
|
|
759
967
|
register,
|
|
760
968
|
record,
|
|
@@ -820,65 +1028,58 @@ function RecordDrawer({
|
|
|
820
1028
|
onClose,
|
|
821
1029
|
ariaLabel: `${REGISTER_LABEL[register]} record`,
|
|
822
1030
|
children: [
|
|
823
|
-
/* @__PURE__ */ jsxs5("header", { className: "
|
|
824
|
-
/* @__PURE__ */ jsxs5("div", { children: [
|
|
825
|
-
/* @__PURE__ */ jsx5("p", { className: "
|
|
826
|
-
/* @__PURE__ */ jsx5("h2", { className: "
|
|
1031
|
+
/* @__PURE__ */ jsxs5("header", { className: "vos-drawer-header", children: [
|
|
1032
|
+
/* @__PURE__ */ jsxs5("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
1033
|
+
/* @__PURE__ */ jsx5("p", { className: "vos-drawer-eyebrow", children: REGISTER_LABEL[register] }),
|
|
1034
|
+
/* @__PURE__ */ jsx5("h2", { className: "vos-drawer-title", children: record ? primaryLabel(record) : loading ? "Loading\u2026" : "\u2014" })
|
|
827
1035
|
] }),
|
|
828
|
-
/* @__PURE__ */ jsxs5("
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
{
|
|
832
|
-
type: "button",
|
|
833
|
-
onClick: startEditing,
|
|
834
|
-
className: "rounded-md border border-neutral-300 px-2.5 py-1 text-sm font-medium text-neutral-700 hover:bg-neutral-100 dark:border-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-900",
|
|
835
|
-
children: "Edit"
|
|
836
|
-
}
|
|
837
|
-
) : null,
|
|
838
|
-
/* @__PURE__ */ jsx5(
|
|
839
|
-
"button",
|
|
840
|
-
{
|
|
841
|
-
type: "button",
|
|
842
|
-
onClick: onClose,
|
|
843
|
-
className: "rounded-md px-2 py-1 text-sm text-neutral-500 hover:bg-neutral-100 dark:hover:bg-neutral-900",
|
|
844
|
-
children: "Close"
|
|
845
|
-
}
|
|
846
|
-
)
|
|
847
|
-
] })
|
|
848
|
-
] }),
|
|
849
|
-
/* @__PURE__ */ jsx5("div", { className: "flex-1 p-5", children: loading ? /* @__PURE__ */ jsx5("p", { className: "text-sm text-neutral-500", children: "Loading record\u2026" }) : error ? /* @__PURE__ */ jsx5("p", { className: "text-sm text-red-600 dark:text-red-400", children: error }) : !record ? /* @__PURE__ */ jsx5("p", { className: "text-sm text-neutral-500", children: "No record." }) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
850
|
-
derived ? /* @__PURE__ */ jsxs5("section", { className: "mb-6", children: [
|
|
851
|
-
/* @__PURE__ */ jsx5("h3", { className: "mb-2 text-xs font-medium uppercase tracking-wide text-neutral-400", children: "Computed \xB7 not editable" }),
|
|
852
|
-
/* @__PURE__ */ jsx5("dl", { className: "grid grid-cols-3 gap-3", children: Object.entries(derived).map(([key, value]) => /* @__PURE__ */ jsxs5(
|
|
853
|
-
"div",
|
|
854
|
-
{
|
|
855
|
-
className: "rounded-lg border border-neutral-200 bg-neutral-50 p-3 dark:border-neutral-800 dark:bg-neutral-900",
|
|
856
|
-
children: [
|
|
857
|
-
/* @__PURE__ */ jsx5("dt", { className: "text-xs text-neutral-500 dark:text-neutral-400", children: derivedLabel(key) }),
|
|
858
|
-
/* @__PURE__ */ jsx5("dd", { className: "mt-1 text-xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-50", children: formatValue(value) })
|
|
859
|
-
]
|
|
860
|
-
},
|
|
861
|
-
key
|
|
862
|
-
)) }),
|
|
863
|
-
"confidence" in derived ? /* @__PURE__ */ jsx5(
|
|
864
|
-
WhyReveal,
|
|
865
|
-
{
|
|
866
|
-
open: why,
|
|
867
|
-
onToggle: () => setWhy((w) => !w),
|
|
868
|
-
assumption: record,
|
|
869
|
-
basePath
|
|
870
|
-
}
|
|
871
|
-
) : null
|
|
1036
|
+
record ? /* @__PURE__ */ jsxs5("span", { className: "vos-verbadge", children: [
|
|
1037
|
+
"v",
|
|
1038
|
+
formatValue(record.version)
|
|
872
1039
|
] }) : null,
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
"p",
|
|
1040
|
+
record && !editing ? /* @__PURE__ */ jsx5(
|
|
1041
|
+
"button",
|
|
876
1042
|
{
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1043
|
+
type: "button",
|
|
1044
|
+
onClick: startEditing,
|
|
1045
|
+
className: "vos-btn vos-btn-ghost vos-btn-sm",
|
|
1046
|
+
children: "Edit"
|
|
880
1047
|
}
|
|
881
1048
|
) : null,
|
|
1049
|
+
/* @__PURE__ */ jsx5(
|
|
1050
|
+
"button",
|
|
1051
|
+
{
|
|
1052
|
+
type: "button",
|
|
1053
|
+
onClick: onClose,
|
|
1054
|
+
className: "vos-iconbtn",
|
|
1055
|
+
"aria-label": "Close",
|
|
1056
|
+
children: "\u2715"
|
|
1057
|
+
}
|
|
1058
|
+
)
|
|
1059
|
+
] }),
|
|
1060
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-drawer-body", children: loading ? /* @__PURE__ */ jsx5("p", { className: "vos-muted", children: "Loading record\u2026" }) : error ? /* @__PURE__ */ jsx5("p", { className: "vos-error", children: error }) : !record ? /* @__PURE__ */ jsx5("p", { className: "vos-muted", children: "No record." }) : /* @__PURE__ */ jsxs5(Fragment4, { children: [
|
|
1061
|
+
derived ? /* @__PURE__ */ jsxs5("div", { children: [
|
|
1062
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-derived", children: [
|
|
1063
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-derived-head", children: [
|
|
1064
|
+
"Derived",
|
|
1065
|
+
/* @__PURE__ */ jsx5("span", { className: "vos-lock", children: "\u{1F512} computed on write \u2014 not editable" })
|
|
1066
|
+
] }),
|
|
1067
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-dgrid", children: Object.entries(derived).map(([key, value]) => /* @__PURE__ */ jsx5(
|
|
1068
|
+
DerivedCell,
|
|
1069
|
+
{
|
|
1070
|
+
field: key,
|
|
1071
|
+
value,
|
|
1072
|
+
showWhy: key === "confidence",
|
|
1073
|
+
whyOpen: why,
|
|
1074
|
+
onWhy: () => setWhy((w) => !w)
|
|
1075
|
+
},
|
|
1076
|
+
key
|
|
1077
|
+
)) })
|
|
1078
|
+
] }),
|
|
1079
|
+
"confidence" in derived && why ? /* @__PURE__ */ jsx5("div", { className: "vos-why-panel", children: /* @__PURE__ */ jsx5(UnderstandingPanel, { assumption: record, basePath }) }) : null
|
|
1080
|
+
] }) : null,
|
|
1081
|
+
conflict ? /* @__PURE__ */ jsx5(ConflictBanner, { message: conflict, onReload: reloadLatest }) : null,
|
|
1082
|
+
saveError ? /* @__PURE__ */ jsx5("p", { role: "alert", className: "vos-banner vos-banner-crit", children: saveError }) : null,
|
|
882
1083
|
editing ? /* @__PURE__ */ jsx5(
|
|
883
1084
|
EditFields,
|
|
884
1085
|
{
|
|
@@ -886,20 +1087,20 @@ function RecordDrawer({
|
|
|
886
1087
|
draft,
|
|
887
1088
|
onField: setField
|
|
888
1089
|
}
|
|
889
|
-
) : /* @__PURE__ */ jsx5("
|
|
890
|
-
/* @__PURE__ */ jsx5("
|
|
891
|
-
/* @__PURE__ */ jsx5("
|
|
1090
|
+
) : /* @__PURE__ */ jsx5("div", { className: "vos-detail-list", children: fields.map((key) => /* @__PURE__ */ jsxs5("div", { className: "vos-detail-row", children: [
|
|
1091
|
+
/* @__PURE__ */ jsx5("span", { className: "vos-detail-k", children: fieldLabel(key) }),
|
|
1092
|
+
/* @__PURE__ */ jsx5("span", { className: "vos-detail-v", children: formatValue(record[key]) })
|
|
892
1093
|
] }, key)) })
|
|
893
1094
|
] }) }),
|
|
894
1095
|
record && !loading && !error && !editing ? children : null,
|
|
895
|
-
record && editing ? /* @__PURE__ */ jsxs5("footer", { className: "
|
|
1096
|
+
record && editing ? /* @__PURE__ */ jsxs5("footer", { className: "vos-drawer-footer", children: [
|
|
896
1097
|
/* @__PURE__ */ jsx5(
|
|
897
1098
|
"button",
|
|
898
1099
|
{
|
|
899
1100
|
type: "button",
|
|
900
1101
|
onClick: cancelEditing,
|
|
901
1102
|
disabled: saving,
|
|
902
|
-
className: "
|
|
1103
|
+
className: "vos-btn vos-btn-ghost vos-btn-sm",
|
|
903
1104
|
children: "Cancel"
|
|
904
1105
|
}
|
|
905
1106
|
),
|
|
@@ -909,11 +1110,11 @@ function RecordDrawer({
|
|
|
909
1110
|
type: "button",
|
|
910
1111
|
onClick: onSave,
|
|
911
1112
|
disabled: saving,
|
|
912
|
-
className: "
|
|
1113
|
+
className: "vos-btn vos-btn-sm",
|
|
913
1114
|
children: saving ? "Saving\u2026" : "Save"
|
|
914
1115
|
}
|
|
915
1116
|
)
|
|
916
|
-
] }) : record ? /* @__PURE__ */ jsxs5("footer", { className: "
|
|
1117
|
+
] }) : record ? /* @__PURE__ */ jsxs5("footer", { className: "vos-drawer-footer", children: [
|
|
917
1118
|
record.id,
|
|
918
1119
|
" \xB7 updated ",
|
|
919
1120
|
formatValue(record.updatedAt)
|
|
@@ -922,56 +1123,56 @@ function RecordDrawer({
|
|
|
922
1123
|
}
|
|
923
1124
|
);
|
|
924
1125
|
}
|
|
925
|
-
function
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1126
|
+
function DerivedCell({
|
|
1127
|
+
field,
|
|
1128
|
+
value,
|
|
1129
|
+
showWhy,
|
|
1130
|
+
whyOpen,
|
|
1131
|
+
onWhy
|
|
930
1132
|
}) {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1133
|
+
const num2 = typeof value === "number" ? value : null;
|
|
1134
|
+
let toneClass = "";
|
|
1135
|
+
let display = formatValue(value);
|
|
1136
|
+
if (num2 !== null) {
|
|
1137
|
+
toneClass = heroToneClass(derivedTone(field, num2));
|
|
1138
|
+
display = field === "confidence" ? formatSigned(num2) : String(Math.round(num2));
|
|
1139
|
+
}
|
|
1140
|
+
return /* @__PURE__ */ jsxs5("div", { className: "vos-dcell", children: [
|
|
1141
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-dcell-k", children: [
|
|
1142
|
+
derivedLabel(field),
|
|
1143
|
+
showWhy ? /* @__PURE__ */ jsxs5(
|
|
1144
|
+
"button",
|
|
1145
|
+
{
|
|
1146
|
+
type: "button",
|
|
1147
|
+
className: "vos-why",
|
|
1148
|
+
onClick: onWhy,
|
|
1149
|
+
"aria-expanded": whyOpen,
|
|
1150
|
+
children: [
|
|
1151
|
+
"Why? ",
|
|
1152
|
+
whyOpen ? "\u25B4" : "\u25BE"
|
|
1153
|
+
]
|
|
1154
|
+
}
|
|
1155
|
+
) : null
|
|
1156
|
+
] }),
|
|
1157
|
+
/* @__PURE__ */ jsx5("div", { className: `vos-dcell-v ${toneClass}`, children: display }),
|
|
1158
|
+
DERIVED_SUB[field] ? /* @__PURE__ */ jsx5("div", { className: "vos-dcell-sub", children: DERIVED_SUB[field] }) : null
|
|
943
1159
|
] });
|
|
944
1160
|
}
|
|
945
1161
|
function ConflictBanner({
|
|
946
1162
|
message,
|
|
947
1163
|
onReload
|
|
948
1164
|
}) {
|
|
949
|
-
return /* @__PURE__ */ jsxs5(
|
|
950
|
-
"div",
|
|
951
|
-
{
|
|
952
|
-
|
|
953
|
-
className: "mb-4 rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-900 dark:bg-amber-950/40",
|
|
954
|
-
children: [
|
|
955
|
-
/* @__PURE__ */ jsx5("p", { className: "text-sm text-amber-800 dark:text-amber-200", children: message }),
|
|
956
|
-
/* @__PURE__ */ jsx5(
|
|
957
|
-
"button",
|
|
958
|
-
{
|
|
959
|
-
type: "button",
|
|
960
|
-
onClick: onReload,
|
|
961
|
-
className: "mt-2 rounded-md border border-amber-300 px-2.5 py-1 text-xs font-medium text-amber-900 hover:bg-amber-100 dark:border-amber-800 dark:text-amber-100 dark:hover:bg-amber-900/40",
|
|
962
|
-
children: "Reload the latest"
|
|
963
|
-
}
|
|
964
|
-
)
|
|
965
|
-
]
|
|
966
|
-
}
|
|
967
|
-
);
|
|
1165
|
+
return /* @__PURE__ */ jsxs5("div", { role: "alert", className: "vos-banner vos-banner-warn", children: [
|
|
1166
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-banner-body", children: /* @__PURE__ */ jsx5("span", { children: message }) }),
|
|
1167
|
+
/* @__PURE__ */ jsx5("button", { type: "button", onClick: onReload, children: "Review the latest" })
|
|
1168
|
+
] });
|
|
968
1169
|
}
|
|
969
1170
|
function EditFields({
|
|
970
1171
|
register,
|
|
971
1172
|
draft,
|
|
972
1173
|
onField
|
|
973
1174
|
}) {
|
|
974
|
-
return /* @__PURE__ */ jsx5("div", { className: "
|
|
1175
|
+
return /* @__PURE__ */ jsx5("div", { className: "vos-field-stack", children: editableFields(register).map((field) => /* @__PURE__ */ jsx5(
|
|
975
1176
|
FieldInput,
|
|
976
1177
|
{
|
|
977
1178
|
field,
|
|
@@ -981,22 +1182,14 @@ function EditFields({
|
|
|
981
1182
|
field.key
|
|
982
1183
|
)) });
|
|
983
1184
|
}
|
|
984
|
-
var INPUT_CLASS = "w-full rounded-md border border-neutral-300 bg-white px-2.5 py-1.5 text-sm text-neutral-900 focus:border-neutral-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-50";
|
|
985
1185
|
function FieldInput({
|
|
986
1186
|
field,
|
|
987
1187
|
value,
|
|
988
1188
|
onChange
|
|
989
1189
|
}) {
|
|
990
1190
|
const id = `field-${field.key.replace(/\s+/g, "-")}`;
|
|
991
|
-
return /* @__PURE__ */ jsxs5("div", { children: [
|
|
992
|
-
/* @__PURE__ */ jsx5(
|
|
993
|
-
"label",
|
|
994
|
-
{
|
|
995
|
-
htmlFor: id,
|
|
996
|
-
className: "mb-1 block text-xs font-medium text-neutral-500 dark:text-neutral-400",
|
|
997
|
-
children: field.label
|
|
998
|
-
}
|
|
999
|
-
),
|
|
1191
|
+
return /* @__PURE__ */ jsxs5("div", { className: "vos-field", children: [
|
|
1192
|
+
/* @__PURE__ */ jsx5("label", { htmlFor: id, children: field.label }),
|
|
1000
1193
|
field.kind === "textarea" ? /* @__PURE__ */ jsx5(
|
|
1001
1194
|
"textarea",
|
|
1002
1195
|
{
|
|
@@ -1004,7 +1197,7 @@ function FieldInput({
|
|
|
1004
1197
|
value: String(value ?? ""),
|
|
1005
1198
|
onChange: (e) => onChange(e.target.value),
|
|
1006
1199
|
rows: 3,
|
|
1007
|
-
className:
|
|
1200
|
+
className: "vos-input"
|
|
1008
1201
|
}
|
|
1009
1202
|
) : field.kind === "select" ? /* @__PURE__ */ jsxs5(
|
|
1010
1203
|
"select",
|
|
@@ -1012,7 +1205,7 @@ function FieldInput({
|
|
|
1012
1205
|
id,
|
|
1013
1206
|
value: String(value ?? ""),
|
|
1014
1207
|
onChange: (e) => onChange(e.target.value),
|
|
1015
|
-
className:
|
|
1208
|
+
className: "vos-input",
|
|
1016
1209
|
children: [
|
|
1017
1210
|
field.nullable ? /* @__PURE__ */ jsx5("option", { value: "", children: "\u2014" }) : null,
|
|
1018
1211
|
field.options?.map((opt) => /* @__PURE__ */ jsx5("option", { value: opt, children: opt }, opt))
|
|
@@ -1025,7 +1218,7 @@ function FieldInput({
|
|
|
1025
1218
|
type: field.kind === "number" ? "number" : "text",
|
|
1026
1219
|
value: String(value ?? ""),
|
|
1027
1220
|
onChange: (e) => onChange(e.target.value),
|
|
1028
|
-
className:
|
|
1221
|
+
className: "vos-input"
|
|
1029
1222
|
}
|
|
1030
1223
|
)
|
|
1031
1224
|
] });
|
|
@@ -1153,8 +1346,8 @@ function toCreatePayload(register, draft) {
|
|
|
1153
1346
|
}
|
|
1154
1347
|
|
|
1155
1348
|
// src/field-styles.ts
|
|
1156
|
-
var FIELD_LABEL_CLASS = "
|
|
1157
|
-
var FIELD_CONTROL_CLASS = "
|
|
1349
|
+
var FIELD_LABEL_CLASS = "vos-field-label";
|
|
1350
|
+
var FIELD_CONTROL_CLASS = "vos-input";
|
|
1158
1351
|
|
|
1159
1352
|
// src/use-mutations.ts
|
|
1160
1353
|
import { useCallback as useCallback2, useState as useState3 } from "react";
|
|
@@ -1238,8 +1431,8 @@ function RecordForm({
|
|
|
1238
1431
|
} catch {
|
|
1239
1432
|
}
|
|
1240
1433
|
};
|
|
1241
|
-
return /* @__PURE__ */ jsxs6("form", { onSubmit, className: "
|
|
1242
|
-
/* @__PURE__ */ jsxs6("div", { className: "
|
|
1434
|
+
return /* @__PURE__ */ jsxs6("form", { onSubmit, className: "vos-form", children: [
|
|
1435
|
+
/* @__PURE__ */ jsxs6("div", { className: "vos-form-body", children: [
|
|
1243
1436
|
fields.map((field) => /* @__PURE__ */ jsx6(
|
|
1244
1437
|
Field,
|
|
1245
1438
|
{
|
|
@@ -1249,25 +1442,17 @@ function RecordForm({
|
|
|
1249
1442
|
},
|
|
1250
1443
|
field.key
|
|
1251
1444
|
)),
|
|
1252
|
-
error ? /* @__PURE__ */ jsx6("p", { className: "
|
|
1445
|
+
error ? /* @__PURE__ */ jsx6("p", { className: "vos-error", children: error }) : null
|
|
1253
1446
|
] }),
|
|
1254
|
-
/* @__PURE__ */ jsxs6("footer", { className: "
|
|
1255
|
-
/* @__PURE__ */ jsx6(
|
|
1256
|
-
"button",
|
|
1257
|
-
{
|
|
1258
|
-
type: "button",
|
|
1259
|
-
onClick: onCancel,
|
|
1260
|
-
className: "rounded-md px-3 py-2 text-sm text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900",
|
|
1261
|
-
children: "Cancel"
|
|
1262
|
-
}
|
|
1263
|
-
),
|
|
1447
|
+
/* @__PURE__ */ jsxs6("footer", { className: "vos-drawer-footer", children: [
|
|
1448
|
+
/* @__PURE__ */ jsx6("button", { type: "button", onClick: onCancel, className: "vos-btn vos-btn-ghost vos-btn-sm", children: "Cancel" }),
|
|
1264
1449
|
/* @__PURE__ */ jsx6(
|
|
1265
1450
|
"button",
|
|
1266
1451
|
{
|
|
1267
1452
|
type: "submit",
|
|
1268
1453
|
disabled: missing.length > 0 || saving,
|
|
1269
1454
|
title: missing.length > 0 ? `Fill in: ${missing.join(", ")}` : void 0,
|
|
1270
|
-
className: "
|
|
1455
|
+
className: "vos-btn vos-btn-sm",
|
|
1271
1456
|
children: saving ? "Creating\u2026" : `Create ${REGISTER_LABEL[register]}`
|
|
1272
1457
|
}
|
|
1273
1458
|
)
|
|
@@ -1280,10 +1465,10 @@ function Field({
|
|
|
1280
1465
|
onChange
|
|
1281
1466
|
}) {
|
|
1282
1467
|
const id = `field-${field.key}`;
|
|
1283
|
-
return /* @__PURE__ */ jsxs6("div", { children: [
|
|
1468
|
+
return /* @__PURE__ */ jsxs6("div", { className: "vos-field", children: [
|
|
1284
1469
|
/* @__PURE__ */ jsxs6("label", { htmlFor: id, className: FIELD_LABEL_CLASS, children: [
|
|
1285
1470
|
field.label,
|
|
1286
|
-
field.required ? /* @__PURE__ */ jsx6("span", { className: "
|
|
1471
|
+
field.required ? /* @__PURE__ */ jsx6("span", { className: "vos-req", children: " *" }) : null
|
|
1287
1472
|
] }),
|
|
1288
1473
|
field.kind === "textarea" ? /* @__PURE__ */ jsx6(
|
|
1289
1474
|
"textarea",
|
|
@@ -1379,9 +1564,9 @@ function RelationEditor({
|
|
|
1379
1564
|
} catch {
|
|
1380
1565
|
}
|
|
1381
1566
|
};
|
|
1382
|
-
return /* @__PURE__ */ jsxs7("section", { className: "
|
|
1383
|
-
/* @__PURE__ */ jsx7("h3", { className: "
|
|
1384
|
-
/* @__PURE__ */ jsxs7("div", { className: "
|
|
1567
|
+
return /* @__PURE__ */ jsxs7("section", { className: "vos-relation", children: [
|
|
1568
|
+
/* @__PURE__ */ jsx7("h3", { className: "vos-sectitle", children: "Link a record" }),
|
|
1569
|
+
/* @__PURE__ */ jsxs7("div", { className: "vos-field-stack", children: [
|
|
1385
1570
|
/* @__PURE__ */ jsxs7(
|
|
1386
1571
|
"select",
|
|
1387
1572
|
{
|
|
@@ -1411,14 +1596,15 @@ function RelationEditor({
|
|
|
1411
1596
|
]
|
|
1412
1597
|
}
|
|
1413
1598
|
) : null,
|
|
1414
|
-
error ? /* @__PURE__ */ jsx7("p", { className: "
|
|
1599
|
+
error ? /* @__PURE__ */ jsx7("p", { className: "vos-error", children: error }) : null,
|
|
1415
1600
|
/* @__PURE__ */ jsx7(
|
|
1416
1601
|
"button",
|
|
1417
1602
|
{
|
|
1418
1603
|
type: "button",
|
|
1419
1604
|
onClick: onLink,
|
|
1420
1605
|
disabled: !active || !targetId || linking,
|
|
1421
|
-
className: "
|
|
1606
|
+
className: "vos-btn vos-btn-sm",
|
|
1607
|
+
style: { alignSelf: "flex-start" },
|
|
1422
1608
|
children: linking ? "Linking\u2026" : "Link"
|
|
1423
1609
|
}
|
|
1424
1610
|
)
|
|
@@ -1427,9 +1613,12 @@ function RelationEditor({
|
|
|
1427
1613
|
}
|
|
1428
1614
|
|
|
1429
1615
|
// src/register-browser.tsx
|
|
1430
|
-
import { useState as useState6 } from "react";
|
|
1431
1616
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1432
|
-
function RegisterBrowser({
|
|
1617
|
+
function RegisterBrowser({
|
|
1618
|
+
register,
|
|
1619
|
+
basePath,
|
|
1620
|
+
subtitle
|
|
1621
|
+
}) {
|
|
1433
1622
|
const { records, loading, error, refresh: refreshList } = useList(
|
|
1434
1623
|
register,
|
|
1435
1624
|
basePath
|
|
@@ -1443,26 +1632,39 @@ function RegisterBrowser({ register, basePath }) {
|
|
|
1443
1632
|
refresh: refreshRecord
|
|
1444
1633
|
} = useRecord(register, openId, basePath);
|
|
1445
1634
|
return /* @__PURE__ */ jsxs8("div", { children: [
|
|
1446
|
-
/* @__PURE__ */ jsxs8("div", { className: "
|
|
1447
|
-
/* @__PURE__ */
|
|
1635
|
+
/* @__PURE__ */ jsxs8("div", { className: "vos-head", children: [
|
|
1636
|
+
/* @__PURE__ */ jsxs8("div", { children: [
|
|
1637
|
+
/* @__PURE__ */ jsx8("h1", { children: REGISTER_LABEL[register] }),
|
|
1638
|
+
subtitle ? /* @__PURE__ */ jsx8("p", { children: subtitle }) : null
|
|
1639
|
+
] }),
|
|
1640
|
+
/* @__PURE__ */ jsx8("div", { className: "vos-spacer" }),
|
|
1641
|
+
/* @__PURE__ */ jsx8(
|
|
1642
|
+
"button",
|
|
1643
|
+
{
|
|
1644
|
+
type: "button",
|
|
1645
|
+
onClick: () => refreshList(),
|
|
1646
|
+
className: "vos-btn vos-btn-ghost",
|
|
1647
|
+
children: "\u21BB Refresh"
|
|
1648
|
+
}
|
|
1649
|
+
),
|
|
1448
1650
|
/* @__PURE__ */ jsxs8(
|
|
1449
1651
|
"button",
|
|
1450
1652
|
{
|
|
1451
1653
|
type: "button",
|
|
1452
1654
|
onClick: () => setCreating(true),
|
|
1453
|
-
className: "
|
|
1655
|
+
className: "vos-btn",
|
|
1454
1656
|
children: [
|
|
1455
|
-
"New ",
|
|
1657
|
+
"+ New ",
|
|
1456
1658
|
REGISTER_SINGULAR[register]
|
|
1457
1659
|
]
|
|
1458
1660
|
}
|
|
1459
1661
|
)
|
|
1460
1662
|
] }),
|
|
1461
|
-
loading && !records ? /* @__PURE__ */ jsxs8("p", { className: "
|
|
1663
|
+
loading && !records ? /* @__PURE__ */ jsxs8("p", { className: "vos-muted", children: [
|
|
1462
1664
|
"Loading ",
|
|
1463
1665
|
REGISTER_LABEL[register].toLowerCase(),
|
|
1464
1666
|
"\u2026"
|
|
1465
|
-
] }) : error ? /* @__PURE__ */ jsx8("p", { className: "
|
|
1667
|
+
] }) : error ? /* @__PURE__ */ jsx8("p", { className: "vos-error", children: error }) : /* @__PURE__ */ jsx8(
|
|
1466
1668
|
RegisterTable,
|
|
1467
1669
|
{
|
|
1468
1670
|
register,
|
|
@@ -1505,12 +1707,11 @@ function RegisterBrowser({ register, basePath }) {
|
|
|
1505
1707
|
open: creating,
|
|
1506
1708
|
onClose: () => setCreating(false),
|
|
1507
1709
|
ariaLabel: `New ${REGISTER_SINGULAR[register]} record`,
|
|
1508
|
-
scroll: false,
|
|
1509
1710
|
children: [
|
|
1510
|
-
/* @__PURE__ */
|
|
1511
|
-
/* @__PURE__ */ jsx8("p", { className: "
|
|
1512
|
-
/* @__PURE__ */ jsx8("h2", { className: "
|
|
1513
|
-
] }),
|
|
1711
|
+
/* @__PURE__ */ jsx8("header", { className: "vos-drawer-header", children: /* @__PURE__ */ jsxs8("div", { children: [
|
|
1712
|
+
/* @__PURE__ */ jsx8("p", { className: "vos-drawer-eyebrow", children: "New" }),
|
|
1713
|
+
/* @__PURE__ */ jsx8("h2", { className: "vos-drawer-title", children: REGISTER_SINGULAR[register] })
|
|
1714
|
+
] }) }),
|
|
1514
1715
|
/* @__PURE__ */ jsx8(
|
|
1515
1716
|
RecordForm,
|
|
1516
1717
|
{
|
|
@@ -1530,6 +1731,38 @@ function RegisterBrowser({ register, basePath }) {
|
|
|
1530
1731
|
] });
|
|
1531
1732
|
}
|
|
1532
1733
|
|
|
1734
|
+
// src/register-nav.tsx
|
|
1735
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1736
|
+
function RegisterNav({
|
|
1737
|
+
active,
|
|
1738
|
+
onSelect,
|
|
1739
|
+
counts,
|
|
1740
|
+
registers
|
|
1741
|
+
}) {
|
|
1742
|
+
const groups = REGISTER_GROUPS.map((g) => ({
|
|
1743
|
+
...g,
|
|
1744
|
+
registers: registers ? g.registers.filter((r) => registers.includes(r)) : g.registers
|
|
1745
|
+
})).filter((g) => g.registers.length > 0);
|
|
1746
|
+
return /* @__PURE__ */ jsx9("nav", { className: "vos-nav", "aria-label": "Registers", children: groups.map((group) => /* @__PURE__ */ jsxs9("div", { children: [
|
|
1747
|
+
/* @__PURE__ */ jsx9("div", { className: "vos-nav-group", children: group.label }),
|
|
1748
|
+
group.registers.map((register) => /* @__PURE__ */ jsxs9(
|
|
1749
|
+
"button",
|
|
1750
|
+
{
|
|
1751
|
+
type: "button",
|
|
1752
|
+
className: `vos-nav-item ${register === active ? "is-active" : ""}`,
|
|
1753
|
+
"aria-current": register === active ? "page" : void 0,
|
|
1754
|
+
onClick: () => onSelect(register),
|
|
1755
|
+
children: [
|
|
1756
|
+
/* @__PURE__ */ jsx9("span", { className: "vos-nav-ic", "aria-hidden": "true", children: REGISTER_ICON[register] }),
|
|
1757
|
+
REGISTER_LABEL[register],
|
|
1758
|
+
/* @__PURE__ */ jsx9("span", { className: "vos-nav-count vos-num", children: counts?.[register] !== void 0 ? formatCount(counts[register] ?? 0) : "\xB7" })
|
|
1759
|
+
]
|
|
1760
|
+
},
|
|
1761
|
+
register
|
|
1762
|
+
))
|
|
1763
|
+
] }, group.label)) });
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1533
1766
|
// src/use-counts.ts
|
|
1534
1767
|
import { useCallback as useCallback3, useEffect as useEffect4, useState as useState7 } from "react";
|
|
1535
1768
|
function useCounts(basePath = "/api") {
|
|
@@ -1560,32 +1793,172 @@ function useCounts(basePath = "/api") {
|
|
|
1560
1793
|
const refresh = useCallback3(() => setTick((t2) => t2 + 1), []);
|
|
1561
1794
|
return { counts, loading, error, refresh };
|
|
1562
1795
|
}
|
|
1796
|
+
|
|
1797
|
+
// src/dashboard-app.tsx
|
|
1798
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1799
|
+
function initialsOf(name) {
|
|
1800
|
+
const parts = name.trim().split(/\s+/);
|
|
1801
|
+
const first = parts[0]?.[0] ?? "";
|
|
1802
|
+
const last = parts.length > 1 ? parts[parts.length - 1]?.[0] ?? "" : "";
|
|
1803
|
+
return (first + last).toUpperCase() || "?";
|
|
1804
|
+
}
|
|
1805
|
+
function registerFromHash(available) {
|
|
1806
|
+
if (typeof window === "undefined") return null;
|
|
1807
|
+
const hash = window.location.hash.replace(/^#\/?/, "");
|
|
1808
|
+
return available.includes(hash) ? hash : null;
|
|
1809
|
+
}
|
|
1810
|
+
function ValidationOSDashboard({ config = {} }) {
|
|
1811
|
+
const {
|
|
1812
|
+
basePath = "/api",
|
|
1813
|
+
backendLabel,
|
|
1814
|
+
branding,
|
|
1815
|
+
agentLabel,
|
|
1816
|
+
user,
|
|
1817
|
+
registers = REGISTER_ORDER
|
|
1818
|
+
} = config;
|
|
1819
|
+
const [active, setActive] = useState8(
|
|
1820
|
+
() => registerFromHash(registers) ?? registers[0] ?? "assumptions"
|
|
1821
|
+
);
|
|
1822
|
+
const { counts } = useCounts(basePath);
|
|
1823
|
+
useEffect5(() => {
|
|
1824
|
+
if (typeof window === "undefined") return;
|
|
1825
|
+
const onHash = () => {
|
|
1826
|
+
const next = registerFromHash(registers);
|
|
1827
|
+
if (next) setActive(next);
|
|
1828
|
+
};
|
|
1829
|
+
window.addEventListener("hashchange", onHash);
|
|
1830
|
+
return () => window.removeEventListener("hashchange", onHash);
|
|
1831
|
+
}, [registers]);
|
|
1832
|
+
const select = useCallback4((register) => {
|
|
1833
|
+
setActive(register);
|
|
1834
|
+
if (typeof window !== "undefined") {
|
|
1835
|
+
window.location.hash = register;
|
|
1836
|
+
}
|
|
1837
|
+
}, []);
|
|
1838
|
+
const toggleTheme = useCallback4(() => {
|
|
1839
|
+
const root = document.documentElement;
|
|
1840
|
+
const current = root.getAttribute("data-theme") ?? (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
|
|
1841
|
+
root.setAttribute("data-theme", current === "dark" ? "light" : "dark");
|
|
1842
|
+
}, []);
|
|
1843
|
+
const brandName = branding?.name ?? "Validation-OS";
|
|
1844
|
+
const brandMark = branding?.initials ?? "V";
|
|
1845
|
+
return /* @__PURE__ */ jsxs10("div", { className: "vos-app", children: [
|
|
1846
|
+
/* @__PURE__ */ jsxs10("div", { className: "vos-brand", children: [
|
|
1847
|
+
/* @__PURE__ */ jsx10("span", { className: "vos-brand-dot", children: branding?.logoUrl ? /* @__PURE__ */ jsx10("img", { src: branding.logoUrl, alt: "" }) : brandMark }),
|
|
1848
|
+
" ",
|
|
1849
|
+
brandName
|
|
1850
|
+
] }),
|
|
1851
|
+
/* @__PURE__ */ jsxs10("div", { className: "vos-topbar", children: [
|
|
1852
|
+
backendLabel ? /* @__PURE__ */ jsxs10("div", { className: "vos-backend", children: [
|
|
1853
|
+
/* @__PURE__ */ jsx10("span", { className: "vos-live-dot" }),
|
|
1854
|
+
" Backend: ",
|
|
1855
|
+
/* @__PURE__ */ jsx10("b", { children: backendLabel })
|
|
1856
|
+
] }) : null,
|
|
1857
|
+
agentLabel ? /* @__PURE__ */ jsxs10("span", { className: "vos-hint", children: [
|
|
1858
|
+
"Agent: ",
|
|
1859
|
+
/* @__PURE__ */ jsx10("b", { style: { color: "var(--vos-text)" }, children: agentLabel })
|
|
1860
|
+
] }) : null,
|
|
1861
|
+
/* @__PURE__ */ jsx10("div", { className: "vos-spacer" }),
|
|
1862
|
+
/* @__PURE__ */ jsx10("button", { type: "button", className: "vos-iconbtn", onClick: toggleTheme, children: "\u25D0 Theme" }),
|
|
1863
|
+
user?.name ? /* @__PURE__ */ jsxs10("div", { className: "vos-user", children: [
|
|
1864
|
+
/* @__PURE__ */ jsx10("span", { className: "vos-avatar", children: initialsOf(user.name) }),
|
|
1865
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
1866
|
+
/* @__PURE__ */ jsx10("span", { className: "vos-user-name", children: user.name }),
|
|
1867
|
+
user.caption ? /* @__PURE__ */ jsx10("small", { children: user.caption }) : null
|
|
1868
|
+
] })
|
|
1869
|
+
] }) : null
|
|
1870
|
+
] }),
|
|
1871
|
+
/* @__PURE__ */ jsx10(
|
|
1872
|
+
RegisterNav,
|
|
1873
|
+
{
|
|
1874
|
+
active,
|
|
1875
|
+
onSelect: select,
|
|
1876
|
+
counts,
|
|
1877
|
+
registers
|
|
1878
|
+
}
|
|
1879
|
+
),
|
|
1880
|
+
/* @__PURE__ */ jsx10("main", { className: "vos-main", children: /* @__PURE__ */ jsx10(
|
|
1881
|
+
RegisterBrowser,
|
|
1882
|
+
{
|
|
1883
|
+
register: active,
|
|
1884
|
+
basePath,
|
|
1885
|
+
subtitle: REGISTER_SUBTITLE[active]
|
|
1886
|
+
},
|
|
1887
|
+
active
|
|
1888
|
+
) })
|
|
1889
|
+
] });
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
// src/register-counts.tsx
|
|
1893
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1894
|
+
function RegisterCounts({
|
|
1895
|
+
counts,
|
|
1896
|
+
caption,
|
|
1897
|
+
onSelect,
|
|
1898
|
+
active
|
|
1899
|
+
}) {
|
|
1900
|
+
const registers = REGISTER_ORDER.filter(
|
|
1901
|
+
(r) => counts[r] !== void 0
|
|
1902
|
+
);
|
|
1903
|
+
return /* @__PURE__ */ jsxs11("section", { "aria-label": "Register counts", children: [
|
|
1904
|
+
/* @__PURE__ */ jsx11("div", { className: "vos-tile-grid", children: registers.map((register) => /* @__PURE__ */ jsx11(
|
|
1905
|
+
StatTile,
|
|
1906
|
+
{
|
|
1907
|
+
label: REGISTER_LABEL[register],
|
|
1908
|
+
value: counts[register] ?? 0,
|
|
1909
|
+
onClick: onSelect ? () => onSelect(register) : void 0,
|
|
1910
|
+
active: active === register
|
|
1911
|
+
},
|
|
1912
|
+
register
|
|
1913
|
+
)) }),
|
|
1914
|
+
caption ? /* @__PURE__ */ jsx11("p", { className: "vos-hint", style: { marginTop: 16 }, children: caption }) : null
|
|
1915
|
+
] });
|
|
1916
|
+
}
|
|
1563
1917
|
export {
|
|
1564
1918
|
CONFLICT_MESSAGE,
|
|
1919
|
+
ConfidenceCell,
|
|
1920
|
+
REGISTER_GROUPS,
|
|
1921
|
+
REGISTER_ICON,
|
|
1565
1922
|
REGISTER_LABEL,
|
|
1566
1923
|
REGISTER_ORDER,
|
|
1567
1924
|
REGISTER_SINGULAR,
|
|
1925
|
+
REGISTER_SUBTITLE,
|
|
1926
|
+
RISK_CRIT,
|
|
1927
|
+
RISK_WARN,
|
|
1568
1928
|
RecordDrawer,
|
|
1569
1929
|
RecordForm,
|
|
1570
1930
|
RegisterBrowser,
|
|
1571
1931
|
RegisterCounts,
|
|
1932
|
+
RegisterNav,
|
|
1572
1933
|
RegisterTable,
|
|
1573
1934
|
RelationEditor,
|
|
1935
|
+
RiskBar,
|
|
1936
|
+
Sparkline,
|
|
1937
|
+
StatTile,
|
|
1938
|
+
StatusPill,
|
|
1574
1939
|
UnderstandingPanel,
|
|
1940
|
+
ValidationOSDashboard,
|
|
1575
1941
|
buildPatch,
|
|
1576
1942
|
buildUnderstanding,
|
|
1577
1943
|
cellValue,
|
|
1578
1944
|
columnsFor,
|
|
1945
|
+
confidenceTone,
|
|
1579
1946
|
draftFrom,
|
|
1580
1947
|
editableFields,
|
|
1581
1948
|
emptyDraft,
|
|
1582
1949
|
formFieldsFor,
|
|
1950
|
+
formatCount,
|
|
1951
|
+
formatSigned,
|
|
1583
1952
|
formatValue,
|
|
1584
1953
|
hasEdits,
|
|
1585
1954
|
interpretSave,
|
|
1586
1955
|
linkChoicesFrom,
|
|
1587
1956
|
missingRequired,
|
|
1588
1957
|
primaryLabel,
|
|
1958
|
+
riskFraction,
|
|
1959
|
+
riskLevel,
|
|
1960
|
+
sparklinePath,
|
|
1961
|
+
statusTone,
|
|
1589
1962
|
toCreatePayload,
|
|
1590
1963
|
useCounts,
|
|
1591
1964
|
useCreate,
|