whodecided 0.1.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/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/checks/gate.d.ts +10 -0
- package/dist/checks/gate.js +19 -0
- package/dist/checks/gate.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +79 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/apply.d.ts +1 -0
- package/dist/commands/apply.js +68 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/board.d.ts +6 -0
- package/dist/commands/board.js +57 -0
- package/dist/commands/board.js.map +1 -0
- package/dist/commands/distill.d.ts +18 -0
- package/dist/commands/distill.js +116 -0
- package/dist/commands/distill.js.map +1 -0
- package/dist/commands/gate.d.ts +2 -0
- package/dist/commands/gate.js +27 -0
- package/dist/commands/gate.js.map +1 -0
- package/dist/commands/hook.d.ts +1 -0
- package/dist/commands/hook.js +189 -0
- package/dist/commands/hook.js.map +1 -0
- package/dist/commands/recall.d.ts +19 -0
- package/dist/commands/recall.js +58 -0
- package/dist/commands/recall.js.map +1 -0
- package/dist/commands/report.d.ts +1 -0
- package/dist/commands/report.js +18 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/review.d.ts +1 -0
- package/dist/commands/review.js +211 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/scope.d.ts +17 -0
- package/dist/commands/scope.js +51 -0
- package/dist/commands/scope.js.map +1 -0
- package/dist/commands/share.d.ts +6 -0
- package/dist/commands/share.js +73 -0
- package/dist/commands/share.js.map +1 -0
- package/dist/commands/sync.d.ts +23 -0
- package/dist/commands/sync.js +214 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.js +80 -0
- package/dist/config.js.map +1 -0
- package/dist/distill/distiller.d.ts +15 -0
- package/dist/distill/distiller.js +92 -0
- package/dist/distill/distiller.js.map +1 -0
- package/dist/distill/prompt.d.ts +18 -0
- package/dist/distill/prompt.js +53 -0
- package/dist/distill/prompt.js.map +1 -0
- package/dist/distill/watermark.d.ts +5 -0
- package/dist/distill/watermark.js +22 -0
- package/dist/distill/watermark.js.map +1 -0
- package/dist/extract/candidates.d.ts +25 -0
- package/dist/extract/candidates.js +136 -0
- package/dist/extract/candidates.js.map +1 -0
- package/dist/git.d.ts +22 -0
- package/dist/git.js +78 -0
- package/dist/git.js.map +1 -0
- package/dist/jsonl.d.ts +14 -0
- package/dist/jsonl.js +30 -0
- package/dist/jsonl.js.map +1 -0
- package/dist/ledger/io.d.ts +30 -0
- package/dist/ledger/io.js +85 -0
- package/dist/ledger/io.js.map +1 -0
- package/dist/ledger/schema.d.ts +31 -0
- package/dist/ledger/schema.js +57 -0
- package/dist/ledger/schema.js.map +1 -0
- package/dist/patch/apply.d.ts +15 -0
- package/dist/patch/apply.js +63 -0
- package/dist/patch/apply.js.map +1 -0
- package/dist/patch/schema.d.ts +11 -0
- package/dist/patch/schema.js +21 -0
- package/dist/patch/schema.js.map +1 -0
- package/dist/render/html.d.ts +33 -0
- package/dist/render/html.js +593 -0
- package/dist/render/html.js.map +1 -0
- package/dist/render/md.d.ts +3 -0
- package/dist/render/md.js +39 -0
- package/dist/render/md.js.map +1 -0
- package/dist/render/term.d.ts +2 -0
- package/dist/render/term.js +40 -0
- package/dist/render/term.js.map +1 -0
- package/dist/trace/schema.d.ts +14 -0
- package/dist/trace/schema.js +25 -0
- package/dist/trace/schema.js.map +1 -0
- package/dist/transcript/locate.d.ts +10 -0
- package/dist/transcript/locate.js +30 -0
- package/dist/transcript/locate.js.map +1 -0
- package/dist/transcript/parser.d.ts +18 -0
- package/dist/transcript/parser.js +130 -0
- package/dist/transcript/parser.js.map +1 -0
- package/dist/validate.d.ts +4 -0
- package/dist/validate.js +13 -0
- package/dist/validate.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-file page: embedded JSON, vanilla JS. Served live by `wdd review`'s
|
|
3
|
+
* ephemeral local server (rulings POST to /apply and land immediately), or
|
|
4
|
+
* written as a static archive/board snapshot (rulings export as patch.jsonl
|
|
5
|
+
* for `wdd apply`). English chrome; ledger content stays in whatever language
|
|
6
|
+
* it was distilled. Grouped repo -> worktree, decisions and traces on a
|
|
7
|
+
* timeline; a decision opens a right-side detail drawer. Theme follows system.
|
|
8
|
+
*/
|
|
9
|
+
export function renderWorkbench(opts) {
|
|
10
|
+
const json = JSON.stringify({
|
|
11
|
+
title: opts.title,
|
|
12
|
+
interactive: opts.interactive,
|
|
13
|
+
live: opts.live ?? false,
|
|
14
|
+
memory: opts.memory ?? [],
|
|
15
|
+
repos: opts.repos,
|
|
16
|
+
}).replace(/</g, "\\u003c");
|
|
17
|
+
return `<!doctype html>
|
|
18
|
+
<html lang="en">
|
|
19
|
+
<meta charset="utf-8">
|
|
20
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
21
|
+
<link rel="icon" href="data:,">
|
|
22
|
+
<title>wdd · ${escapeHtml(opts.title)}</title>
|
|
23
|
+
<style>
|
|
24
|
+
:root {
|
|
25
|
+
--bg: #fbfbfa; --card: #ffffff; --ink: #1c1c1e; --muted: #6e6e76; --faint: #9a9aa2;
|
|
26
|
+
--line: #e7e7e4; --line2: #efefec; --accent: #2f6df6;
|
|
27
|
+
--red: #d1453b; --red-bg: #fbeceb; --amber: #b26a00; --amber-bg: #fbf1df;
|
|
28
|
+
--green: #1f9254; --green-bg: #e6f5ec; --shadow: 0 8px 40px rgba(0,0,0,.14);
|
|
29
|
+
}
|
|
30
|
+
:root[data-theme="dark"] {
|
|
31
|
+
--bg: #101012; --card: #1a1a1d; --ink: #ececef; --muted: #9a9aa3; --faint: #6a6a72;
|
|
32
|
+
--line: #2a2a2e; --line2: #232327; --accent: #6a9bff;
|
|
33
|
+
--red: #e77066; --red-bg: #2a1917; --amber: #d29a4a; --amber-bg: #251d10;
|
|
34
|
+
--green: #56bd83; --green-bg: #14231a; --shadow: 0 8px 40px rgba(0,0,0,.5);
|
|
35
|
+
}
|
|
36
|
+
@media (prefers-color-scheme: dark) {
|
|
37
|
+
:root:not([data-theme="light"]) {
|
|
38
|
+
--bg: #101012; --card: #1a1a1d; --ink: #ececef; --muted: #9a9aa3; --faint: #6a6a72;
|
|
39
|
+
--line: #2a2a2e; --line2: #232327; --accent: #6a9bff;
|
|
40
|
+
--red: #e77066; --red-bg: #2a1917; --amber: #d29a4a; --amber-bg: #251d10;
|
|
41
|
+
--green: #56bd83; --green-bg: #14231a; --shadow: 0 8px 40px rgba(0,0,0,.5);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
* { box-sizing: border-box; }
|
|
45
|
+
body { margin: 0; background: var(--bg); color: var(--ink);
|
|
46
|
+
font: 14px/1.55 -apple-system, "SF Pro Text", system-ui, "Segoe UI", sans-serif;
|
|
47
|
+
-webkit-font-smoothing: antialiased; }
|
|
48
|
+
.mono { font-family: ui-monospace, "SF Mono", Menlo, monospace; }
|
|
49
|
+
main { max-width: 720px; margin: 0 auto; padding: 30px 20px 80px; }
|
|
50
|
+
|
|
51
|
+
header .top { display: flex; align-items: center; gap: 10px; }
|
|
52
|
+
header h1 { margin: 0; font-size: 16px; font-weight: 640; letter-spacing: -0.01em; }
|
|
53
|
+
.chip { font: 500 11.5px/1 ui-monospace, monospace; border: 1px solid var(--line); background: var(--card);
|
|
54
|
+
color: var(--muted); border-radius: 6px; padding: 4px 7px; }
|
|
55
|
+
#theme { margin-left: auto; width: 32px; height: 32px; border: 1px solid var(--line); border-radius: 8px;
|
|
56
|
+
background: var(--card); color: var(--muted); cursor: pointer; font-size: 15px; line-height: 1; }
|
|
57
|
+
#theme:hover { color: var(--ink); }
|
|
58
|
+
#brief { color: var(--muted); font-size: 13px; margin: 12px 0 0; max-width: 60ch; }
|
|
59
|
+
#brief b { color: var(--ink); font-weight: 600; }
|
|
60
|
+
#brief code { font-family: ui-monospace, monospace; font-size: 12px; }
|
|
61
|
+
#stats { display: flex; gap: 8px; flex-wrap: wrap; margin: 18px 0 0; }
|
|
62
|
+
.stat { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 9px 13px;
|
|
63
|
+
font-size: 12px; color: var(--muted); min-width: 74px; }
|
|
64
|
+
.stat b { display: block; font-size: 17px; font-weight: 660; color: var(--ink); letter-spacing: -0.01em; }
|
|
65
|
+
.stat.warn b { color: var(--red); }
|
|
66
|
+
|
|
67
|
+
.repo { margin-top: 34px; }
|
|
68
|
+
.repo-h { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; padding-bottom: 9px;
|
|
69
|
+
border-bottom: 1px solid var(--line); }
|
|
70
|
+
.repo-h .name { font-size: 14.5px; font-weight: 640; }
|
|
71
|
+
.gate { font: 600 11px/1 inherit; border-radius: 99px; padding: 3px 9px; }
|
|
72
|
+
.gate.green { background: var(--green-bg); color: var(--green); }
|
|
73
|
+
.gate.red { background: var(--red-bg); color: var(--red); }
|
|
74
|
+
.repo-h .mini { font-size: 12px; color: var(--faint); margin-left: auto; }
|
|
75
|
+
|
|
76
|
+
.wt { margin-top: 14px; }
|
|
77
|
+
.wt-h { font-size: 11.5px; color: var(--faint); letter-spacing: 0.05em; text-transform: uppercase;
|
|
78
|
+
font-weight: 600; margin-bottom: 2px; }
|
|
79
|
+
.wt-h .path { text-transform: none; letter-spacing: 0; font-family: ui-monospace, monospace; }
|
|
80
|
+
|
|
81
|
+
.tl { position: relative; margin: 6px 0 0; padding: 0; list-style: none; }
|
|
82
|
+
.tl::before { content: ""; position: absolute; left: 58px; top: 10px; bottom: 10px; width: 1px; background: var(--line); }
|
|
83
|
+
.day { position: relative; margin: 16px 0 4px; padding-left: 74px; font: 600 11px/1.8 ui-monospace, monospace; color: var(--faint); }
|
|
84
|
+
.item { position: relative; padding-left: 74px; margin: 2px 0; }
|
|
85
|
+
.item .t { position: absolute; left: 0; top: 9px; width: 42px; text-align: right; font: 11px/1.4 ui-monospace, monospace; color: var(--faint); }
|
|
86
|
+
.item .dot { position: absolute; left: 54px; top: 12px; width: 9px; height: 9px; border-radius: 50%; background: var(--muted); outline: 3px solid var(--bg); }
|
|
87
|
+
.item.silent .dot { background: var(--red); }
|
|
88
|
+
.item.reflex .dot { background: var(--amber); }
|
|
89
|
+
.item.aware .dot { background: var(--green); }
|
|
90
|
+
.item.trace .dot { width: 7px; height: 7px; left: 55px; background: var(--card); border: 1.5px solid var(--line); }
|
|
91
|
+
|
|
92
|
+
.trow { display: flex; gap: 8px; align-items: baseline; padding: 5px 2px; font-size: 12.5px; color: var(--muted); }
|
|
93
|
+
.trow .ok { color: var(--green); } .trow .bad { color: var(--red); }
|
|
94
|
+
.trow .cmd { font-family: ui-monospace, monospace; font-size: 11.5px; color: var(--faint);
|
|
95
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
96
|
+
|
|
97
|
+
.drow { width: 100%; text-align: left; background: var(--card); border: 1px solid var(--line);
|
|
98
|
+
border-radius: 10px; padding: 10px 12px; display: flex; gap: 9px; align-items: center;
|
|
99
|
+
cursor: pointer; font: inherit; color: inherit; transition: border-color .12s, background .12s; }
|
|
100
|
+
.drow:hover { border-color: var(--muted); }
|
|
101
|
+
.drow .id { font: 600 11px/1 ui-monospace, monospace; color: var(--faint); flex: none; }
|
|
102
|
+
.drow .what { flex: 1; font-weight: 400; min-width: 0; }
|
|
103
|
+
.drow .chev { color: var(--faint); flex: none; font-size: 15px; }
|
|
104
|
+
|
|
105
|
+
/* refined category tag: dot + label, quiet fill */
|
|
106
|
+
.tag { flex: none; display: inline-flex; align-items: center; gap: 5px; font: 600 11px/1 inherit;
|
|
107
|
+
border-radius: 99px; padding: 4px 9px 4px 8px; white-space: nowrap; }
|
|
108
|
+
.tag i { width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .9; }
|
|
109
|
+
.tag.silent { background: var(--red-bg); color: var(--red); }
|
|
110
|
+
.tag.reflex { background: var(--amber-bg); color: var(--amber); }
|
|
111
|
+
.tag.aware { background: var(--green-bg); color: var(--green); }
|
|
112
|
+
.v { flex: none; font: 600 11px/1 inherit; border-radius: 99px; padding: 4px 9px; }
|
|
113
|
+
.v.accept { background: var(--green-bg); color: var(--green); }
|
|
114
|
+
.v.reject { background: var(--red-bg); color: var(--red); }
|
|
115
|
+
.v.pending { border: 1px dashed var(--line); color: var(--faint); }
|
|
116
|
+
.drow.reject .what { text-decoration: line-through; color: var(--muted); }
|
|
117
|
+
|
|
118
|
+
/* drawer */
|
|
119
|
+
#scrim { position: fixed; inset: 0; background: rgba(0,0,0,.32); opacity: 0; pointer-events: none;
|
|
120
|
+
transition: opacity .18s; z-index: 40; }
|
|
121
|
+
#scrim.open { opacity: 1; pointer-events: auto; }
|
|
122
|
+
#drawer { position: fixed; top: 0; right: 0; height: 100%; width: min(480px, 100%); background: var(--card);
|
|
123
|
+
border-left: 1px solid var(--line); box-shadow: var(--shadow); transform: translateX(100%);
|
|
124
|
+
transition: transform .22s cubic-bezier(.4,0,.2,1); z-index: 41; display: flex; flex-direction: column; }
|
|
125
|
+
#drawer.open { transform: translateX(0); }
|
|
126
|
+
.dh { display: flex; align-items: flex-start; gap: 10px; padding: 18px 20px 14px; border-bottom: 1px solid var(--line); }
|
|
127
|
+
.dh .id { font: 600 12px/1.6 ui-monospace, monospace; color: var(--faint); }
|
|
128
|
+
.dh h2 { margin: 4px 0 0; font-size: 16px; font-weight: 640; line-height: 1.35; }
|
|
129
|
+
.dh .x { margin-left: auto; flex: none; border: 0; background: none; color: var(--muted); font-size: 22px;
|
|
130
|
+
line-height: 1; cursor: pointer; padding: 0 2px; }
|
|
131
|
+
.dbody { padding: 16px 20px; overflow: auto; flex: 1; }
|
|
132
|
+
.sec { margin: 0 0 16px; }
|
|
133
|
+
.lbl { font-size: 11px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--faint); margin-bottom: 5px; }
|
|
134
|
+
.alts { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
135
|
+
.alts span { border: 1px solid var(--line); border-radius: 7px; padding: 3px 9px; font-size: 13px; }
|
|
136
|
+
.files { display: flex; flex-direction: column; gap: 4px; }
|
|
137
|
+
.files code { font-family: ui-monospace, monospace; font-size: 12.5px; color: var(--accent);
|
|
138
|
+
background: var(--bg); border: 1px solid var(--line); border-radius: 6px; padding: 3px 8px; }
|
|
139
|
+
.quad b.bad { color: var(--red); font-weight: 600; } .quad b.ok { color: var(--green); font-weight: 600; }
|
|
140
|
+
.excerpt { background: var(--bg); border: 1px solid var(--line); border-radius: 9px; padding: 11px 13px;
|
|
141
|
+
font: 11.5px/1.65 ui-monospace, monospace; white-space: pre-wrap; word-break: break-word;
|
|
142
|
+
max-height: 300px; overflow: auto; color: var(--muted); }
|
|
143
|
+
.excerpt .u { color: var(--ink); font-weight: 600; }
|
|
144
|
+
.refline { font: 11px/1.6 ui-monospace, monospace; color: var(--faint); margin-top: 5px; }
|
|
145
|
+
.df { border-top: 1px solid var(--line); padding: 14px 20px; }
|
|
146
|
+
.btns { display: flex; gap: 8px; }
|
|
147
|
+
.btns button { flex: 1; font: 640 13px/1 inherit; cursor: pointer; border-radius: 9px; padding: 11px;
|
|
148
|
+
border: 1px solid var(--line); background: var(--card); color: var(--ink); transition: all .12s; }
|
|
149
|
+
.btns .ok:hover, .btns .ok.on { background: var(--green); border-color: var(--green); color: #fff; }
|
|
150
|
+
.btns .no:hover, .btns .no.on { background: var(--red); border-color: var(--red); color: #fff; }
|
|
151
|
+
.note { width: 100%; margin-top: 9px; font: 13px/1.5 inherit; padding: 9px 11px; border: 1px solid var(--line);
|
|
152
|
+
border-radius: 9px; background: var(--bg); color: var(--ink); resize: vertical; min-height: 38px; }
|
|
153
|
+
.next { font-size: 12px; color: var(--muted); margin-top: 10px; line-height: 1.5; }
|
|
154
|
+
.next b { color: var(--ink); font-weight: 600; }
|
|
155
|
+
.ruleswitch { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 550; cursor: pointer; }
|
|
156
|
+
.ruleswitch input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
|
|
157
|
+
.rulehint { font-size: 12px; color: var(--accent); margin: 5px 0 0; min-height: 0; }
|
|
158
|
+
.v.rule { background: var(--accent); color: #fff; }
|
|
159
|
+
@media (prefers-color-scheme: dark) { .v.rule { color: #0f0f10; } }
|
|
160
|
+
#modal { position: fixed; inset: 0; background: rgba(0,0,0,.4); z-index: 60; display: none;
|
|
161
|
+
align-items: center; justify-content: center; padding: 20px; }
|
|
162
|
+
#modal.show { display: flex; }
|
|
163
|
+
.mbox { background: var(--card); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow);
|
|
164
|
+
max-width: 420px; width: 100%; padding: 18px; }
|
|
165
|
+
.mmsg { font-size: 14px; line-height: 1.5; }
|
|
166
|
+
.mmsg .cq { margin-top: 8px; padding: 8px 10px; background: var(--bg); border: 1px solid var(--line);
|
|
167
|
+
border-radius: 8px; font-size: 13px; }
|
|
168
|
+
.mbtns { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
|
|
169
|
+
.mbtns button { font: 600 13px/1 inherit; cursor: pointer; border-radius: 8px; padding: 9px 16px; border: 1px solid var(--line); background: var(--card); color: var(--ink); }
|
|
170
|
+
.mbtns .mok { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
171
|
+
.ro { font-size: 12px; color: var(--muted); margin-top: 8px; }
|
|
172
|
+
.ro code { font-family: ui-monospace, monospace; font-size: 11.5px; }
|
|
173
|
+
|
|
174
|
+
#bar { position: fixed; bottom: 0; left: 0; right: 0; background: var(--card); border-top: 1px solid var(--line);
|
|
175
|
+
padding: 10px 20px; z-index: 30; }
|
|
176
|
+
#bar .inner { max-width: 720px; margin: 0 auto; display: flex; gap: 12px; align-items: center; }
|
|
177
|
+
#staged { font-size: 13px; color: var(--muted); }
|
|
178
|
+
#staged.hot { color: var(--ink); font-weight: 600; }
|
|
179
|
+
#how { font: 11px/1.4 ui-monospace, monospace; color: var(--faint); margin-left: auto; }
|
|
180
|
+
#export { font: 640 13px/1 inherit; background: var(--accent); color: #fff; border: 0; border-radius: 9px;
|
|
181
|
+
padding: 10px 16px; cursor: pointer; }
|
|
182
|
+
#export:disabled { opacity: .4; cursor: default; }
|
|
183
|
+
.empty { color: var(--faint); text-align: center; padding: 30px 0; font-size: 13px; }
|
|
184
|
+
#banner { position: fixed; bottom: 60px; left: 50%; transform: translateX(-50%) translateY(12px);
|
|
185
|
+
max-width: 560px; width: calc(100% - 40px); border-radius: 10px; padding: 0; z-index: 50;
|
|
186
|
+
display: none; box-shadow: var(--shadow); border: 1px solid var(--line); }
|
|
187
|
+
#banner.show { display: block; animation: bpop .18s ease; }
|
|
188
|
+
@keyframes bpop { from { transform: translateX(-50%) translateY(12px); opacity: 0; } }
|
|
189
|
+
#banner.ok { background: var(--green-bg); border-color: var(--green); }
|
|
190
|
+
#banner.warn { background: var(--amber-bg); border-color: var(--amber); }
|
|
191
|
+
#banner.err { background: var(--red-bg); border-color: var(--red); }
|
|
192
|
+
#banner .bx { padding: 12px 40px 12px 14px; font-size: 13px; line-height: 1.5; color: var(--ink); }
|
|
193
|
+
#banner .bx b { font-weight: 650; }
|
|
194
|
+
#banner .bpin { margin-top: 6px; color: var(--muted); }
|
|
195
|
+
#banner .bpin ul { margin: 4px 0 0; padding-left: 18px; }
|
|
196
|
+
#banner .bpin li { margin: 2px 0; }
|
|
197
|
+
#banner .bxc { position: absolute; top: 8px; right: 8px; border: 0; background: none; font-size: 18px;
|
|
198
|
+
line-height: 1; color: var(--muted); cursor: pointer; }
|
|
199
|
+
</style>
|
|
200
|
+
<body>
|
|
201
|
+
<main>
|
|
202
|
+
<header>
|
|
203
|
+
<div class="top">
|
|
204
|
+
<h1 id="title"></h1>
|
|
205
|
+
<button id="theme" title="theme">◐</button>
|
|
206
|
+
</div>
|
|
207
|
+
<p id="brief"></p>
|
|
208
|
+
<div id="stats"></div>
|
|
209
|
+
</header>
|
|
210
|
+
<div id="repos"></div>
|
|
211
|
+
</main>
|
|
212
|
+
<div id="scrim"></div>
|
|
213
|
+
<aside id="drawer" aria-hidden="true"></aside>
|
|
214
|
+
<div id="bar" style="display:none"><div class="inner">
|
|
215
|
+
<span id="staged"></span><span id="how"></span><button id="export" style="display:none"></button>
|
|
216
|
+
</div></div>
|
|
217
|
+
<div id="banner"></div>
|
|
218
|
+
<div id="modal"></div>
|
|
219
|
+
<script type="application/json" id="data">${json}</script>
|
|
220
|
+
<script>
|
|
221
|
+
const state = JSON.parse(document.getElementById("data").textContent);
|
|
222
|
+
|
|
223
|
+
const T = {
|
|
224
|
+
brief: (s) => "Decisions made inside AI sessions, on a timeline. <b>" + s + "</b> were never shown to you. " +
|
|
225
|
+
(state.interactive
|
|
226
|
+
? "Open one to review — <b>Approve</b> to keep it, <b>Reject</b> if it was wrong. Rulings save straight to the ledger."
|
|
227
|
+
: "Read-only snapshot; run <code>wdd review</code> to make rulings, or download this as Markdown."),
|
|
228
|
+
decisions: "decisions", pending: "to review", tests: "tests", silent: "AI decided", repos: "repos",
|
|
229
|
+
tag: { silent: "AI decided", reflex: "You didn't read", aware: "You decided" },
|
|
230
|
+
stampedFast: (s) => "You clicked in " + s + "s",
|
|
231
|
+
affirm: "Approve", overturn: "Reject",
|
|
232
|
+
explain: "<b>Approve</b> — AI will follow this next time. <b>Reject</b> — AI will avoid it. Either way your code isn't changed here.",
|
|
233
|
+
affirmed: "Approved", overturned: "Rejected", already: (v) => "Already " + (v === "accept" ? "approved" : "rejected"),
|
|
234
|
+
hard: "hard to change", why: "Why", alts: "Other options", files: "Files touched", attrs: "About", excerpt: "From the session",
|
|
235
|
+
noExcerpt: "session is too old to quote; the ref still points to it in the transcript",
|
|
236
|
+
byAgent: "AI decided", byUser: "you decided", awareNo: "you didn't confirm it", awareYes: "you confirmed it",
|
|
237
|
+
rev: { low: "hard to change", medium: "medium to change", high: "easy to change" },
|
|
238
|
+
note: "Add a note (optional)…",
|
|
239
|
+
nextAffirm: "AI will follow this from now on. Your code isn't changed.",
|
|
240
|
+
nextOverturn: "AI won't do this again. Your code isn't changed — note the fix and do it yourself.",
|
|
241
|
+
remember: "Save this as a rule for agents",
|
|
242
|
+
ruleOnHint: (where) => "On approve/reject, this becomes a rule in " + where + " (with your note).",
|
|
243
|
+
confirmRule: (v, where, what, note) =>
|
|
244
|
+
"Add a rule to <b>" + esc(where) + "</b> that agents will follow:<div class='cq'>" +
|
|
245
|
+
(v === "reject" ? "DON'T " : "DO ") + esc(what) + (note ? " — " + esc(note) : "") + "</div>",
|
|
246
|
+
cancel: "Cancel", confirm: "Add the rule",
|
|
247
|
+
pinNoTarget: "Saved the ruling, but no memory file is configured — the rule wasn't written. Run wdd sync to pick one.",
|
|
248
|
+
earlier: "earlier", today: "today", yesterday: "yesterday",
|
|
249
|
+
howLive: "Open a decision · Approve / Reject saves it straight to the ledger",
|
|
250
|
+
howStatic: "This is a read-only snapshot. Run wdd review to make rulings.",
|
|
251
|
+
download: "Download as Markdown",
|
|
252
|
+
saveFail: (e) => "Save failed: " + e,
|
|
253
|
+
savedN: (n) => "Saved " + n + " decision" + (n === 1 ? "" : "s"),
|
|
254
|
+
pinnedTo: (where) => "Saved as a rule in " + where + ":",
|
|
255
|
+
uncommitted: "Saved to .wdd/ledger.jsonl on disk — but git can't track it because .wdd is gitignored. Remove the .wdd line from .gitignore to version it, or run <b>wdd share local</b> to keep it local-only.",
|
|
256
|
+
empty: "no decisions in this scope",
|
|
257
|
+
testLine: (k, p, f) => k + " · " + (p ?? 0) + " passed" + (f ? " · " + f + " failed" : ""),
|
|
258
|
+
ruleHere: (root) => 'Read-only. Rule inside the repo: <code>cd ' + root + ' && wdd review</code>',
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const cat = (e) => (e.aware ? "aware" : e.by === "agent" ? "silent" : "reflex");
|
|
262
|
+
const allEntries = () => state.repos.flatMap((r, ri) => r.entries.map((e) => ({ ...e, _ri: ri })));
|
|
263
|
+
const flatIndex = {}; // "ri:id" -> {entry, repo}
|
|
264
|
+
state.repos.forEach((r, ri) => r.entries.forEach((e) => (flatIndex[ri + ":" + e.id] = { e, r })));
|
|
265
|
+
|
|
266
|
+
/* theme */
|
|
267
|
+
const themeBtn = document.getElementById("theme");
|
|
268
|
+
function applyTheme() {
|
|
269
|
+
const saved = localStorage.getItem("wdd:theme");
|
|
270
|
+
if (saved) document.documentElement.setAttribute("data-theme", saved);
|
|
271
|
+
else document.documentElement.removeAttribute("data-theme");
|
|
272
|
+
const dark = saved ? saved === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
|
|
273
|
+
themeBtn.textContent = dark ? "☀" : "☾";
|
|
274
|
+
}
|
|
275
|
+
themeBtn.onclick = () => {
|
|
276
|
+
const dark = document.documentElement.getAttribute("data-theme") === "dark" ||
|
|
277
|
+
(!localStorage.getItem("wdd:theme") && matchMedia("(prefers-color-scheme: dark)").matches);
|
|
278
|
+
localStorage.setItem("wdd:theme", dark ? "light" : "dark");
|
|
279
|
+
applyTheme();
|
|
280
|
+
};
|
|
281
|
+
applyTheme();
|
|
282
|
+
|
|
283
|
+
function updateBar() {
|
|
284
|
+
const ex = document.getElementById("export");
|
|
285
|
+
if (state.live) {
|
|
286
|
+
// Rulings save immediately from the drawer; the bar just shows progress.
|
|
287
|
+
const all = allEntries();
|
|
288
|
+
const ruled = all.filter((e) => e.verdict).length;
|
|
289
|
+
document.getElementById("staged").textContent = ruled + " / " + all.length + " reviewed";
|
|
290
|
+
document.getElementById("staged").className = "";
|
|
291
|
+
document.getElementById("how").textContent = T.howLive;
|
|
292
|
+
ex.style.display = "none";
|
|
293
|
+
} else {
|
|
294
|
+
document.getElementById("staged").textContent = "";
|
|
295
|
+
ex.style.display = "";
|
|
296
|
+
ex.textContent = T.download; ex.disabled = false;
|
|
297
|
+
document.getElementById("how").textContent = T.howStatic;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function render() {
|
|
302
|
+
document.getElementById("bar").style.display = "";
|
|
303
|
+
document.getElementById("title").textContent = state.title;
|
|
304
|
+
const entries = allEntries();
|
|
305
|
+
const silent = entries.filter((e) => cat(e) === "silent").length;
|
|
306
|
+
const pend = entries.filter((e) => !verdictOf(e)).length;
|
|
307
|
+
const pass = state.repos.flatMap((r) => r.traces).reduce((s, x) => s + (x.pass || 0), 0);
|
|
308
|
+
const fail = state.repos.flatMap((r) => r.traces).reduce((s, x) => s + (x.fail || 0), 0);
|
|
309
|
+
document.getElementById("brief").innerHTML = T.brief(silent);
|
|
310
|
+
document.getElementById("stats").innerHTML =
|
|
311
|
+
(state.repos.length > 1 ? stat(state.repos.length, T.repos) : "") +
|
|
312
|
+
stat(entries.length, T.decisions) +
|
|
313
|
+
stat(pend, T.pending, pend > 0) +
|
|
314
|
+
stat(silent, T.silent, silent > 0 && pend > 0) +
|
|
315
|
+
(pass + fail ? stat(pass + " ✓" + (fail ? " " + fail + " ✗" : ""), T.tests, fail > 0) : "");
|
|
316
|
+
|
|
317
|
+
const root = document.getElementById("repos");
|
|
318
|
+
root.textContent = "";
|
|
319
|
+
state.repos.forEach((r, ri) => root.appendChild(repoSection(r, ri)));
|
|
320
|
+
updateBar();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function verdictOf(e) { return e.verdict; }
|
|
324
|
+
function stat(v, label, warn) { return '<div class="stat' + (warn ? " warn" : "") + '"><b>' + v + "</b>" + esc(label) + "</div>"; }
|
|
325
|
+
|
|
326
|
+
function repoSection(r, ri) {
|
|
327
|
+
const sec = document.createElement("section");
|
|
328
|
+
sec.className = "repo";
|
|
329
|
+
if (state.repos.length > 1 || r.gate) {
|
|
330
|
+
const pend = r.entries.filter((e) => !verdictOf({ ...e, _ri: ri })).length;
|
|
331
|
+
sec.innerHTML = '<div class="repo-h"><span class="name">' + esc(r.repo) + "</span>" +
|
|
332
|
+
(r.branch ? '<span class="chip">' + esc(r.branch) + "</span>" : "") +
|
|
333
|
+
(r.gate ? '<span class="gate ' + (r.gate.green ? "green" : "red") + '">' + (r.gate.green ? "gate green" : "gate red") + "</span>" : "") +
|
|
334
|
+
'<span class="mini">' + pend + " / " + r.entries.length + " pending</span></div>";
|
|
335
|
+
}
|
|
336
|
+
const items = [
|
|
337
|
+
...r.entries.map((e) => ({ type: "d", t: e.t, cwd: e.cwd, e: { ...e, _ri: ri } })),
|
|
338
|
+
...r.traces.map((tr) => ({ type: "t", t: tr.t, cwd: tr.cwd, tr })),
|
|
339
|
+
];
|
|
340
|
+
if (!items.length) { sec.insertAdjacentHTML("beforeend", '<div class="empty">' + T.empty + "</div>"); return sec; }
|
|
341
|
+
const cwds = [...new Set(items.map((i) => i.cwd || r.root))].sort((a, b) => (a === r.root ? -1 : b === r.root ? 1 : a < b ? -1 : 1));
|
|
342
|
+
for (const cwd of cwds) {
|
|
343
|
+
const group = items.filter((i) => (i.cwd || r.root) === cwd).sort((a, b) => ((a.t || "") < (b.t || "") ? -1 : 1));
|
|
344
|
+
const wt = document.createElement("div");
|
|
345
|
+
wt.className = "wt";
|
|
346
|
+
if (cwds.length > 1) wt.innerHTML = '<div class="wt-h">worktree <span class="path">' + esc(wtLabel(r, cwd)) + "</span></div>";
|
|
347
|
+
const ol = document.createElement("ol");
|
|
348
|
+
ol.className = "tl";
|
|
349
|
+
let day = null;
|
|
350
|
+
for (const it of group) {
|
|
351
|
+
const d = localDay(it.t);
|
|
352
|
+
if (d !== day) { day = d; const dh = document.createElement("div"); dh.className = "day"; dh.textContent = dayLabel(it.t); ol.appendChild(dh); }
|
|
353
|
+
ol.appendChild(it.type === "d" ? decisionRow(it) : traceRow(it));
|
|
354
|
+
}
|
|
355
|
+
wt.appendChild(ol);
|
|
356
|
+
sec.appendChild(wt);
|
|
357
|
+
}
|
|
358
|
+
if (!state.interactive) sec.insertAdjacentHTML("beforeend", '<div class="ro">' + T.ruleHere(esc(r.root)) + "</div>");
|
|
359
|
+
return sec;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function wtLabel(r, cwd) {
|
|
363
|
+
if (!cwd || cwd === r.root) return r.repo;
|
|
364
|
+
return cwd.startsWith(r.root) ? r.repo + cwd.slice(r.root.length) : cwd;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function traceRow(it) {
|
|
368
|
+
const li = document.createElement("li");
|
|
369
|
+
li.className = "item trace";
|
|
370
|
+
const ok = it.tr.exit === 0;
|
|
371
|
+
li.innerHTML = '<span class="t" title="' + esc(fullTime(it.t)) + '">' + hhmm(it.t) + '</span><span class="dot"></span>' +
|
|
372
|
+
'<div class="trow"><span class="' + (ok ? "ok" : "bad") + '">' + (ok ? "✓" : "✗") + "</span><span>" +
|
|
373
|
+
esc(T.testLine(it.tr.kind, it.tr.pass, it.tr.fail)) + '</span><span class="cmd">' + esc(it.tr.cmd) + "</span></div>";
|
|
374
|
+
return li;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function decisionRow(it) {
|
|
378
|
+
const e = it.e;
|
|
379
|
+
const c = cat(e);
|
|
380
|
+
const verdict = verdictOf(e);
|
|
381
|
+
const li = document.createElement("li");
|
|
382
|
+
li.className = "item " + c;
|
|
383
|
+
const btn = document.createElement("button");
|
|
384
|
+
btn.className = "drow" + (verdict === "reject" ? " reject" : "");
|
|
385
|
+
btn.innerHTML =
|
|
386
|
+
'<span class="id">' + esc(e.id) + "</span>" +
|
|
387
|
+
'<span class="tag ' + c + '"><i></i>' + esc(tagLabel(e, c)) + "</span>" +
|
|
388
|
+
'<span class="what">' + esc(e.what) + "</span>" +
|
|
389
|
+
(e.pinned ? '<span class="v rule">rule</span>' : "") +
|
|
390
|
+
(verdict ? '<span class="v ' + verdict + '">' + esc(T[verdict === "accept" ? "affirmed" : "overturned"]) + "</span>" : "") +
|
|
391
|
+
'<span class="chev">›</span>';
|
|
392
|
+
btn.onclick = () => openDrawer(e._ri + ":" + e.id);
|
|
393
|
+
li.innerHTML = '<span class="t" title="' + esc(fullTime(it.t)) + '">' + hhmm(it.t) + '</span><span class="dot"></span>';
|
|
394
|
+
li.appendChild(btn);
|
|
395
|
+
return li;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function tagLabel(e, c) {
|
|
399
|
+
if (c === "reflex" && e.latencyMs != null && e.latencyMs < 10000) return T.stampedFast((e.latencyMs / 1000).toFixed(1));
|
|
400
|
+
return T.tag[c];
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* drawer */
|
|
404
|
+
const scrim = document.getElementById("scrim");
|
|
405
|
+
const drawer = document.getElementById("drawer");
|
|
406
|
+
let openKey = null;
|
|
407
|
+
function openDrawer(key) {
|
|
408
|
+
openKey = key;
|
|
409
|
+
drawer.innerHTML = drawerHtml(key);
|
|
410
|
+
wireDrawer(key);
|
|
411
|
+
scrim.classList.add("open");
|
|
412
|
+
drawer.classList.add("open");
|
|
413
|
+
drawer.setAttribute("aria-hidden", "false");
|
|
414
|
+
}
|
|
415
|
+
function closeDrawer() {
|
|
416
|
+
openKey = null;
|
|
417
|
+
scrim.classList.remove("open");
|
|
418
|
+
drawer.classList.remove("open");
|
|
419
|
+
drawer.setAttribute("aria-hidden", "true");
|
|
420
|
+
}
|
|
421
|
+
scrim.onclick = closeDrawer;
|
|
422
|
+
addEventListener("keydown", (ev) => { if (ev.key === "Escape") closeDrawer(); });
|
|
423
|
+
|
|
424
|
+
function drawerHtml(key) {
|
|
425
|
+
const { e, r } = flatIndex[key];
|
|
426
|
+
const verdict = e.verdict;
|
|
427
|
+
let body = "";
|
|
428
|
+
if (e.why) body += sec(T.why, esc(e.why));
|
|
429
|
+
if (e.alternatives && e.alternatives.length)
|
|
430
|
+
body += sec(T.alts, '<div class="alts">' + e.alternatives.map((a) => "<span>" + esc(a) + "</span>").join("") + "</div>");
|
|
431
|
+
if (e.files && e.files.length)
|
|
432
|
+
body += sec(T.files, '<div class="files">' + e.files.map((f) => '<code>' + esc(relPath(f, r.root)) + "</code>").join("") + "</div>");
|
|
433
|
+
body += sec(T.attrs, '<span class="quad">' +
|
|
434
|
+
"<b class=" + (e.by === "agent" ? "bad" : "ok") + ">" + esc(e.by === "agent" ? T.byAgent : T.byUser) + "</b> · " +
|
|
435
|
+
"<b class=" + (e.aware ? "ok" : "bad") + ">" + esc(e.aware ? T.awareYes : T.awareNo) + "</b>" +
|
|
436
|
+
(e.model ? ' · <span class="mono">' + esc(e.model) + "</span>" : "") +
|
|
437
|
+
(e.reversibility ? " · " + esc(T.rev[e.reversibility]) : "") + "</span>");
|
|
438
|
+
const slice = r.slices[e.ref];
|
|
439
|
+
body += sec(T.excerpt,
|
|
440
|
+
(slice ? '<div class="excerpt">' + excerptHtml(slice) + "</div>" : '<span style="color:var(--muted);font-size:12.5px">' + esc(T.noExcerpt) + "</span>") +
|
|
441
|
+
'<div class="refline">' + esc(e.ref) + (e.branch ? " · " + esc(e.branch) : "") + "</div>");
|
|
442
|
+
|
|
443
|
+
let footer = "";
|
|
444
|
+
if (!state.interactive) {
|
|
445
|
+
footer = '<div class="ro">' + T.ruleHere(esc(r.root)) + "</div>";
|
|
446
|
+
} else {
|
|
447
|
+
// Order (bottom-up): rule switch, then note, then the ruling buttons.
|
|
448
|
+
// Clicking Approve/Reject IS the save (immediate). No separate Save step.
|
|
449
|
+
footer =
|
|
450
|
+
'<label class="ruleswitch"><input type="checkbox" id="ruleSw"' + (e.pinned ? " checked" : "") + ">" +
|
|
451
|
+
"<span>" + T.remember + "</span></label>" +
|
|
452
|
+
'<div class="rulehint" id="ruleHint">' + (e.pinned ? T.ruleOnHint(memoryLabel()) : "") + "</div>" +
|
|
453
|
+
'<textarea class="note" placeholder="' + esc(T.note) + '">' + esc(e.ruleNote || "") + "</textarea>" +
|
|
454
|
+
'<div class="next">' + T.explain + "</div>" +
|
|
455
|
+
'<div class="btns"><button class="ok' + (verdict === "accept" ? " on" : "") + '" data-v="accept">' + T.affirm + "</button>" +
|
|
456
|
+
'<button class="no' + (verdict === "reject" ? " on" : "") + '" data-v="reject">' + T.overturn + "</button></div>";
|
|
457
|
+
}
|
|
458
|
+
const c = cat(e);
|
|
459
|
+
return (
|
|
460
|
+
'<div class="dh"><div><div class="id">' + esc(e.id) + " · " +
|
|
461
|
+
'<span class="tag ' + c + '" style="padding:2px 7px"><i></i>' + esc(tagLabel(e, c)) + "</span></div>" +
|
|
462
|
+
"<h2>" + esc(e.what) + '</h2></div><button class="x" title="close">×</button></div>' +
|
|
463
|
+
'<div class="dbody">' + body + "</div>" +
|
|
464
|
+
'<div class="df">' + footer + "</div>"
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function memoryLabel() {
|
|
469
|
+
return state.memory && state.memory.length ? state.memory.join(", ") : "your agent memory file";
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function wireDrawer(key) {
|
|
473
|
+
drawer.querySelector(".x").onclick = closeDrawer;
|
|
474
|
+
const noteEl = drawer.querySelector(".note");
|
|
475
|
+
const sw = drawer.querySelector("#ruleSw");
|
|
476
|
+
if (sw) sw.onchange = () => {
|
|
477
|
+
const h = drawer.querySelector("#ruleHint");
|
|
478
|
+
if (h) h.innerHTML = sw.checked ? T.ruleOnHint(memoryLabel()) : "";
|
|
479
|
+
};
|
|
480
|
+
drawer.querySelectorAll(".btns button").forEach((b) => (b.onclick = () => rule(key, b.dataset.v)));
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** A ruling is immediate: Approve/Reject POSTs straight to the ledger.
|
|
484
|
+
* If "save as a rule" is on, confirm first (it writes to agent memory). */
|
|
485
|
+
async function rule(key, verdict) {
|
|
486
|
+
const { e } = flatIndex[key];
|
|
487
|
+
const noteEl = drawer.querySelector(".note");
|
|
488
|
+
const sw = drawer.querySelector("#ruleSw");
|
|
489
|
+
const note = (noteEl && noteEl.value.trim()) || undefined;
|
|
490
|
+
const pin = !!(sw && sw.checked);
|
|
491
|
+
if (pin && !(await confirmModal(T.confirmRule(verdict, memoryLabel(), e.what, note)))) return;
|
|
492
|
+
const op = { op: "ratify", target: key.split(":")[1], verdict, ...(note ? { note } : {}), ...(pin ? { pin: true } : {}) };
|
|
493
|
+
try {
|
|
494
|
+
const res = await fetch("/apply", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify([op]) });
|
|
495
|
+
const r = await res.json();
|
|
496
|
+
if (r.error) return banner(esc(T.saveFail(r.error)), "err");
|
|
497
|
+
e.verdict = verdict; e.ruleNote = note; e.pinned = pin;
|
|
498
|
+
drawer.innerHTML = drawerHtml(key); wireDrawer(key);
|
|
499
|
+
render();
|
|
500
|
+
if (r.uncommitted) return banner(T.uncommitted, "warn");
|
|
501
|
+
// Report the SERVER's truth: which files it actually wrote.
|
|
502
|
+
const wrote = (r.synced || []).filter((f) => !f.startsWith(".wdd"));
|
|
503
|
+
let msg = "<b>" + esc(e.id + " " + (verdict === "accept" ? T.affirmed : T.overturned)) + "</b>";
|
|
504
|
+
if (pin && wrote.length) {
|
|
505
|
+
msg += "<div class=\\"bpin\\">" + esc(T.pinnedTo(wrote.join(", "))) + "<ul><li>" +
|
|
506
|
+
(verdict === "reject" ? "DON'T " : "DO ") + esc(e.what) + (note ? " — " + esc(note) : "") + "</li></ul></div>";
|
|
507
|
+
} else if (pin) {
|
|
508
|
+
// Asked to save as a rule, but no memory file was written.
|
|
509
|
+
return banner(esc(T.pinNoTarget), "warn");
|
|
510
|
+
}
|
|
511
|
+
banner(msg, "ok");
|
|
512
|
+
} catch (err) {
|
|
513
|
+
banner(esc(T.saveFail(err)), "err");
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/* time — ledger stores UTC; render in the viewer's local zone via Date */
|
|
518
|
+
function hhmm(iso) { if (!iso) return ""; const d = new Date(iso); return isNaN(d) ? "" : d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false }); }
|
|
519
|
+
function localDay(iso) { if (!iso) return null; const d = new Date(iso); return isNaN(d) ? null : d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0"); }
|
|
520
|
+
function dayLabel(iso) {
|
|
521
|
+
const day = localDay(iso); if (!day) return T.earlier;
|
|
522
|
+
const today = localDay(new Date().toISOString()), yst = localDay(new Date(Date.now() - 86400000).toISOString());
|
|
523
|
+
return day + (day === today ? " · " + T.today : day === yst ? " · " + T.yesterday : "");
|
|
524
|
+
}
|
|
525
|
+
function fullTime(iso) { if (!iso) return ""; const d = new Date(iso); return isNaN(d) ? "" : d.toLocaleString("en-US", { dateStyle: "medium", timeStyle: "long" }); }
|
|
526
|
+
|
|
527
|
+
// In-page banner (no system alert). kind: ok | warn | err. HTML content allowed.
|
|
528
|
+
let bannerTimer;
|
|
529
|
+
function banner(html, kind) {
|
|
530
|
+
const el = document.getElementById("banner");
|
|
531
|
+
el.className = "show " + (kind || "ok");
|
|
532
|
+
el.innerHTML = '<div class="bx">' + html + '</div><button class="bxc">×</button>';
|
|
533
|
+
el.querySelector(".bxc").onclick = () => el.classList.remove("show");
|
|
534
|
+
clearTimeout(bannerTimer);
|
|
535
|
+
if (kind === "ok") bannerTimer = setTimeout(() => el.classList.remove("show"), 5000);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function relPath(f, root) { return root && f.indexOf(root) === 0 ? f.slice(root.length).replace(/^[/]/, "") : f; }
|
|
539
|
+
function sec(label, body) { return '<div class="sec"><div class="lbl">' + esc(label) + "</div>" + body + "</div>"; }
|
|
540
|
+
function excerptHtml(slice) { return slice.split("\\n").map((l) => (/\\] (USER|USER-[A-Z-]+):/.test(l) ? '<span class="u">' + esc(l) + "</span>" : esc(l))).join("\\n"); }
|
|
541
|
+
|
|
542
|
+
// Static snapshot: the only footer action is downloading a readable record.
|
|
543
|
+
document.getElementById("export").onclick = () => downloadMarkdown();
|
|
544
|
+
|
|
545
|
+
/** In-page confirm (no system dialog). Returns a Promise<boolean>. */
|
|
546
|
+
function confirmModal(msg) {
|
|
547
|
+
return new Promise((resolve) => {
|
|
548
|
+
const m = document.getElementById("modal");
|
|
549
|
+
m.innerHTML =
|
|
550
|
+
'<div class="mbox"><div class="mmsg">' + msg + "</div>" +
|
|
551
|
+
'<div class="mbtns"><button class="mc">' + T.cancel + '</button><button class="mok">' + T.confirm + "</button></div></div>";
|
|
552
|
+
m.className = "show";
|
|
553
|
+
const done = (v) => { m.className = ""; resolve(v); };
|
|
554
|
+
m.querySelector(".mc").onclick = () => done(false);
|
|
555
|
+
m.querySelector(".mok").onclick = () => done(true);
|
|
556
|
+
m.onclick = (ev) => { if (ev.target === m) done(false); };
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** Read-only snapshot download: a plain, human-readable Markdown record. */
|
|
561
|
+
function downloadMarkdown() {
|
|
562
|
+
const md = ["# " + state.title, "", "Decision audit from whodecided.", ""];
|
|
563
|
+
for (const r of state.repos) {
|
|
564
|
+
if (state.repos.length > 1) md.push("## " + r.repo + (r.branch ? " · " + r.branch : ""), "");
|
|
565
|
+
const line = (e) => {
|
|
566
|
+
const v = e.verdict;
|
|
567
|
+
const mark = v === "accept" ? "[approved]" : v === "reject" ? "[rejected]" : "[to review]";
|
|
568
|
+
const who = e.aware ? "you decided" : e.by === "agent" ? "AI decided" : "you didn't read";
|
|
569
|
+
const alt = e.alternatives && e.alternatives.length ? " \\n vs " + e.alternatives.join(", ") : "";
|
|
570
|
+
const why = e.why ? " \\n why: " + e.why : "";
|
|
571
|
+
return "- " + mark + " (" + who + ") " + e.what + alt + why;
|
|
572
|
+
};
|
|
573
|
+
for (const e of r.entries) md.push(line(e));
|
|
574
|
+
md.push("");
|
|
575
|
+
}
|
|
576
|
+
const blob = new Blob([md.join("\\n")], { type: "text/markdown" });
|
|
577
|
+
const a = document.createElement("a");
|
|
578
|
+
a.href = URL.createObjectURL(blob);
|
|
579
|
+
a.download = state.title.replace(/[^a-zA-Z0-9]+/g, "-") + ".md";
|
|
580
|
+
a.click();
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function esc(s) { const d = document.createElement("div"); d.textContent = s == null ? "" : s; return d.innerHTML; }
|
|
584
|
+
render();
|
|
585
|
+
</script>
|
|
586
|
+
</body>
|
|
587
|
+
</html>
|
|
588
|
+
`;
|
|
589
|
+
}
|
|
590
|
+
function escapeHtml(s) {
|
|
591
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
592
|
+
}
|
|
593
|
+
//# sourceMappingURL=html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/render/html.ts"],"names":[],"mappings":"AA4BA;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,IAAsB;IACpD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,KAAK;QACxB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE;QACzB,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC5B,OAAO;;;;;eAKM,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4CAqMO,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiX/C,CAAC;AACF,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { followups, verdictOf } from "../ledger/io.js";
|
|
2
|
+
export function renderMd(entries, ratifies, gate, header) {
|
|
3
|
+
const badge = gate.green ? "🟢 gate: green" : "🔴 gate: red";
|
|
4
|
+
const freshness = gate.fresh ? "ledger fresh" : `undistilled sessions: ${gate.staleSessions.join(", ")}`;
|
|
5
|
+
const fixes = followups(entries, ratifies);
|
|
6
|
+
const lines = [
|
|
7
|
+
`## Decision audit: ${header}`,
|
|
8
|
+
"",
|
|
9
|
+
`${badge} · ${freshness} · ${gate.pendingSilent.length} silent decision(s) pending`,
|
|
10
|
+
"",
|
|
11
|
+
];
|
|
12
|
+
if (fixes.length > 0) {
|
|
13
|
+
lines.push(`### ⚠ Overturned — code fix pending (${fixes.length})`, "");
|
|
14
|
+
for (const f of fixes) {
|
|
15
|
+
lines.push(`- **${f.entry.id}** ${f.entry.what}${f.correction ? ` <br/><sub>→ ${f.correction}</sub>` : ""}`);
|
|
16
|
+
}
|
|
17
|
+
lines.push("");
|
|
18
|
+
}
|
|
19
|
+
section(lines, ratifies, "⚠ Silent agent decisions", entries.filter((e) => e.by === "agent" && !e.aware));
|
|
20
|
+
section(lines, ratifies, "⚠ Your reflexive decisions", entries.filter((e) => e.by === "user" && !e.aware));
|
|
21
|
+
section(lines, ratifies, "✓ Your deliberate decisions", entries.filter((e) => e.aware));
|
|
22
|
+
lines.push(`_generated by [whodecided](https://github.com/zhu-kai/whodecided) · \`wdd report --md\`_`);
|
|
23
|
+
return lines.join("\n");
|
|
24
|
+
}
|
|
25
|
+
function section(lines, ratifies, title, entries) {
|
|
26
|
+
if (entries.length === 0)
|
|
27
|
+
return;
|
|
28
|
+
lines.push(`### ${title} (${entries.length})`, "");
|
|
29
|
+
for (const e of entries) {
|
|
30
|
+
const verdict = verdictOf(ratifies, e.id);
|
|
31
|
+
const mark = verdict === "accept" ? "✅" : verdict === "reject" ? "❌" : "⏳";
|
|
32
|
+
const detail = [e.why, e.alternatives?.length ? `alt: ${e.alternatives.join(", ")}` : "", e.reversibility === "low" ? "hard to reverse" : ""]
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.join(" · ");
|
|
35
|
+
lines.push(`- ${mark} **${e.id}** ${e.what}${detail ? ` <br/><sub>${detail}</sub>` : ""}`);
|
|
36
|
+
}
|
|
37
|
+
lines.push("");
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=md.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"md.js","sourceRoot":"","sources":["../../src/render/md.ts"],"names":[],"mappings":"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"}
|