@azure-id/orc 2.0.2 → 2.1.1
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/CHANGELOG.md +455 -0
- package/README-id.md +52 -23
- package/README.md +36 -20
- package/bin/build-agents.js +205 -206
- package/bin/clear-logs.js +620 -0
- package/bin/cli.js +47417 -46420
- package/bin/fix.js +459 -0
- package/bin/gotcha.js +288 -8
- package/bin/habit.js +1538 -1453
- package/bin/mockrun-catalog.js +3 -0
- package/bin/onboarding-content.js +2 -1
- package/bin/pricing.json +28 -21
- package/bin/trace-write.js +869 -657
- package/bin/verify-contracts.js +5552 -5471
- package/bin/verify-package.js +716 -705
- package/bin/webui/api.js +125 -6
- package/bin/webui/css/panels/hookui.css +79 -0
- package/bin/webui/fixtures/behaviour.js +30 -0
- package/bin/webui/fixtures/extra.js +2036 -2036
- package/bin/webui/fixtures/flow.js +81 -81
- package/bin/webui/fixtures/hookui.js +506 -431
- package/bin/webui/fixtures/index.js +32 -3
- package/bin/webui/fixtures/knowledge.js +2 -2
- package/bin/webui/fixtures/maintenance.js +123 -42
- package/bin/webui/fixtures/rules.js +1007 -1002
- package/bin/webui/fixtures/settings.js +6 -7
- package/bin/webui/fixtures/stats.js +9 -6
- package/bin/webui/fixtures/wait.js +98 -97
- package/bin/webui/i18n/en/behaviour.json +13 -0
- package/bin/webui/i18n/en/hookui.json +290 -194
- package/bin/webui/i18n/en/maintenance.json +71 -52
- package/bin/webui/i18n/en/settings.json +60 -60
- package/bin/webui/i18n/id/behaviour.json +13 -0
- package/bin/webui/i18n/id/hookui.json +290 -194
- package/bin/webui/i18n/id/maintenance.json +71 -52
- package/bin/webui/i18n/id/settings.json +60 -60
- package/bin/webui/js/05-banners.js +181 -172
- package/bin/webui/js/06-edit.js +193 -183
- package/bin/webui/js/panels/behaviour.js +77 -12
- package/bin/webui/js/panels/hookui.js +2196 -1691
- package/bin/webui/js/panels/maintenance.js +336 -235
- package/bin/webui/js/panels/settings.js +726 -719
- package/mock-run/INDEX.md +1 -0
- package/mock-run/a-normal-day.md +2 -2
- package/mock-run/extra-slots.md +177 -177
- package/mock-run/habits.md +1 -1
- package/mock-run/orc-aftermath.md +392 -392
- package/mock-run/orc-budget.md +29 -33
- package/mock-run/orc-cli.md +200 -200
- package/mock-run/orc-explain.md +86 -86
- package/mock-run/orc-extra.md +389 -392
- package/mock-run/orc-fast.md +2 -2
- package/mock-run/orc-fix.md +87 -0
- package/mock-run/orc-pattern.md +112 -112
- package/mock-run/orc-quick.md +146 -146
- package/mock-run/orc.md +4 -4
- package/package.json +1 -1
- package/templates/agents/MODEL-MAPPING.md +176 -173
- package/templates/agents/orc-analyze-mini-sonnet-5-high.md +2 -2
- package/templates/agents/{orc-claude-writer-opus-5-med.md → orc-claude-writer-opus-5-low.md} +4 -4
- package/templates/agents/orc-executor-haiku-4-5.md +1 -1
- package/templates/agents/orc-executor-opus-5-low.md +1 -1
- package/templates/agents/orc-executor-sonnet-4-6-high.md +1 -1
- package/templates/agents/orc-executor-sonnet-4-6-med.md +1 -1
- package/templates/agents/orc-executor-sonnet-5-high.md +2 -2
- package/templates/agents/orc-executor-sonnet-5-low.md +155 -0
- package/templates/agents/orc-executor-sonnet-5-med.md +155 -0
- package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +2 -2
- package/templates/agents/orc-planner-mini-sonnet-5-high.md +2 -2
- package/templates/agents/{orc-recon-sonnet-4-6-med.md → orc-recon-sonnet-5-med.md} +4 -4
- package/templates/agents/orc-retro-sonnet-5-high.md +2 -2
- package/templates/agents/{orc-reviewer-opus-5-med.md → orc-reviewer-opus-5-low.md} +4 -4
- package/templates/agents/{orc-wiki-scanner-opus-5-med.md → orc-wiki-scanner-opus-5-low.md} +3 -3
- package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +2 -2
- package/templates/commands/orc-fast.md +10 -10
- package/templates/commands/orc-fix.md +18 -0
- package/templates/commands/orc-wiki.md +64 -64
- package/templates/hooks/README.md +75 -7
- package/templates/hooks/orc-session-hook.js +78 -3
- package/templates/hooks/orc-statusline-render.js +317 -23
- package/templates/hooks/orc-statusline.js +162 -49
- package/templates/hooks/orc-subagent-line.js +23 -5
- package/templates/hooks/orc-weather-fetch.js +123 -0
- package/templates/skills/_shared/extra-dispatch.md +1331 -1331
- package/templates/skills/_shared/gotchas.md +27 -7
- package/templates/skills/_shared/habits.md +2 -2
- package/templates/skills/_shared/opus5-only.md +137 -137
- package/templates/skills/_shared/phases/review.md +2 -2
- package/templates/skills/_shared/phases/trace-verbs.md +17 -3
- package/templates/skills/_shared/phases/trace.md +136 -136
- package/templates/skills/_shared/review-slice.md +28 -1
- package/templates/skills/_shared/wait.md +1 -0
- package/templates/skills/orc/README.md +2 -2
- package/templates/skills/orc/SKILL.md +227 -227
- package/templates/skills/orc/config.md +25 -27
- package/templates/skills/orc/examples/full-run-mock.md +74 -73
- package/templates/skills/orc/references/effort-and-mode.md +222 -222
- package/templates/skills/orc/references/preflight-report.md +3 -3
- package/templates/skills/orc/references/ultra-mode.md +1 -1
- package/templates/skills/orc/schemas/checkpoint.md +122 -122
- package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +3 -3
- package/templates/skills/orc/subskills/orc-review-verify/core.md +1 -1
- package/templates/skills/orc-analyze/SKILL.md +4 -2
- package/templates/skills/orc-claude/SKILL.md +14 -14
- package/templates/skills/orc-claude/examples/claude-run-mock.md +65 -65
- package/templates/skills/orc-diy/SKILL.md +5 -5
- package/templates/skills/orc-fast/SKILL.md +210 -208
- package/templates/skills/orc-fix/SKILL.md +110 -0
- package/templates/skills/orc-grill/SKILL.md +232 -232
- package/templates/skills/orc-grill/references/grill-doc.md +1 -1
- package/templates/skills/orc-mini/SKILL.md +4 -4
- package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -2
- package/templates/skills/orc-quick/README.md +536 -536
- package/templates/skills/orc-quick/SKILL.md +2 -2
- package/templates/skills/orc-quick/references/context-doc.md +145 -145
- package/templates/skills/orc-quick/references/dispatch-gate.md +220 -219
- package/templates/skills/orc-quick/references/gh-mode.md +4 -2
- package/templates/skills/orc-retro/SKILL.md +1 -1
- package/templates/skills/orc-retro/examples/retro-mock.md +16 -16
- package/templates/skills/orc-wiki/README.md +325 -325
- package/templates/skills/orc-wiki/SKILL.md +195 -195
- package/templates/skills/orc-wiki/examples/wiki-run-mock.md +37 -37
- package/templates/skills/orc-wiki/references/extra.md +2 -2
- package/templates/skills/orc-wiki/references/partial-refresh.md +276 -271
- package/templates/skills/orc-wiki/references/phases/phase-0.md +1 -1
- package/templates/skills/orc-wiki/references/phases/phase-2.md +3 -3
- package/templates/skills/orc-wiki/references/phases/phase-3c.md +3 -2
- package/templates/agents/orc-claude-writer-opus-4-8-high.md +0 -48
- package/templates/agents/orc-wiki-scanner-opus-4-8-high.md +0 -74
package/bin/webui/js/06-edit.js
CHANGED
|
@@ -1,183 +1,193 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/* 06-edit.js — orc ui client
|
|
3
|
-
editSet, editBar, applyEdits — the staged-write machinery Settings AND Flow
|
|
4
|
-
both use. Nothing is written until Apply; the CLI is still the only writer and
|
|
5
|
-
the only validator.
|
|
6
|
-
|
|
7
|
-
Loaded by app.html in the order its numeric prefix names. Classic
|
|
8
|
-
script, no import/export: an ES module import carries no query string,
|
|
9
|
-
and every static request here needs the per-launch session token. */
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/* ============================================================ staged edits == */
|
|
13
|
-
//
|
|
14
|
-
// WRITES ARE BATCHED (v0.44.1). Every control used to commit on the spot: one
|
|
15
|
-
// click, one `orc config set`, one full re-render of the panel. Changing four
|
|
16
|
-
// keys meant four subprocesses and four re-renders, and each one scrolled the
|
|
17
|
-
// list out from under you — so a routine "set these five things" was a fight.
|
|
18
|
-
//
|
|
19
|
-
// Nothing is written until Apply now. Edits accumulate here, the affected rows
|
|
20
|
-
// mark themselves, and an edit bar at the bottom says how many are pending.
|
|
21
|
-
// The CLI is still the only writer and still the only validator; what changed
|
|
22
|
-
// is WHEN it is called, never by whom.
|
|
23
|
-
//
|
|
24
|
-
// An entry is `{kind: "set", value}` or `{kind: "reset"}` — a per-key reset is
|
|
25
|
-
// `orc config reset <key>` (it REMOVES the key from the file), which is not the
|
|
26
|
-
// same write as setting it to its default value, so it cannot be flattened into
|
|
27
|
-
// one.
|
|
28
|
-
function editSet(onChange) {
|
|
29
|
-
const map = new Map();
|
|
30
|
-
const api = {
|
|
31
|
-
map,
|
|
32
|
-
get size() {
|
|
33
|
-
return map.size;
|
|
34
|
-
},
|
|
35
|
-
// Staging a value back to what it already was CLEARS the edit rather than
|
|
36
|
-
// recording a no-op — otherwise "cancel" and "set it back by hand" would
|
|
37
|
-
// leave the bar claiming an unsaved change that would write nothing.
|
|
38
|
-
set(key, value, original) {
|
|
39
|
-
if (String(value) === String(original)) map.delete(key);
|
|
40
|
-
else map.set(key, { kind: "set", value: String(value), original: String(original) });
|
|
41
|
-
onChange(api);
|
|
42
|
-
},
|
|
43
|
-
reset(key) {
|
|
44
|
-
map.set(key, { kind: "reset" });
|
|
45
|
-
onChange(api);
|
|
46
|
-
},
|
|
47
|
-
// An ACTION entry (v0.49.2): a route and a body, staged like any other
|
|
48
|
-
// edit, applied by `applyActions`. `label` is what the pending list names —
|
|
49
|
-
// a count is not a change list.
|
|
50
|
-
action(key, route, body, label) {
|
|
51
|
-
map.set(key, { kind: "action", route, body, value: label });
|
|
52
|
-
onChange(api);
|
|
53
|
-
},
|
|
54
|
-
drop(key) {
|
|
55
|
-
map.delete(key);
|
|
56
|
-
onChange(api);
|
|
57
|
-
},
|
|
58
|
-
clear() {
|
|
59
|
-
map.clear();
|
|
60
|
-
onChange(api);
|
|
61
|
-
},
|
|
62
|
-
entries() {
|
|
63
|
-
return [...map.entries()];
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
return api;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// The bar. Apply is disabled with nothing pending; Reset is always offered
|
|
70
|
-
// (it is a write in its own right, not an undo); **Cancel appears only when
|
|
71
|
-
// there is something to cancel** — a permanently visible Cancel next to a
|
|
72
|
-
// disabled Apply reads as though the panel is broken.
|
|
73
|
-
function editBar(edits, { onApply, onReset, onCancel, resetLabel }) {
|
|
74
|
-
const bar = el("div", "edit-bar");
|
|
75
|
-
const summary = el("div", "edit-summary");
|
|
76
|
-
const actions = el("div", "edit-actions");
|
|
77
|
-
|
|
78
|
-
const apply = el("button", "btn btn-sm btn-primary", t("edits.apply"));
|
|
79
|
-
apply.type = "button";
|
|
80
|
-
apply.addEventListener("click", () => onApply(apply));
|
|
81
|
-
|
|
82
|
-
const reset = el("button", "btn btn-sm btn-ghost", resetLabel || t("edits.reset"));
|
|
83
|
-
reset.type = "button";
|
|
84
|
-
reset.addEventListener("click", onReset);
|
|
85
|
-
|
|
86
|
-
const cancel = el("button", "btn btn-sm btn-ghost", t("edits.cancel"));
|
|
87
|
-
cancel.type = "button";
|
|
88
|
-
cancel.addEventListener("click", onCancel);
|
|
89
|
-
|
|
90
|
-
bar.paint = () => {
|
|
91
|
-
const n = edits.size;
|
|
92
|
-
bar.classList.toggle("edit-bar-dirty", n > 0);
|
|
93
|
-
apply.disabled = n === 0;
|
|
94
|
-
apply.textContent = n ? t("edits.applyN", { n }) : t("edits.apply");
|
|
95
|
-
// The pending list is named, never counted: "3 changes" is not consent for
|
|
96
|
-
// three writes you can no longer see.
|
|
97
|
-
summary.replaceChildren();
|
|
98
|
-
if (!n) {
|
|
99
|
-
summary.append(el("span", "note", t("edits.none")));
|
|
100
|
-
} else {
|
|
101
|
-
summary.append(el("span", "note", t("edits.pending")));
|
|
102
|
-
const list = el("div", "edit-list");
|
|
103
|
-
for (const [key, e] of edits.entries()) {
|
|
104
|
-
const item = el("span", "edit-chip");
|
|
105
|
-
// Key names and values are CLI data — never translated.
|
|
106
|
-
item.append(el("span", "edit-key", key));
|
|
107
|
-
item.append(document.createTextNode(e.kind === "reset" ? " → " + t("edits.toDefault") : " → " + e.value));
|
|
108
|
-
list.append(item);
|
|
109
|
-
}
|
|
110
|
-
summary.append(list);
|
|
111
|
-
}
|
|
112
|
-
actions.replaceChildren();
|
|
113
|
-
actions.append(apply, reset);
|
|
114
|
-
if (n) actions.append(cancel);
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
bar.append(summary, actions);
|
|
118
|
-
bar.paint();
|
|
119
|
-
return bar;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Apply runs the staged writes ONE AT A TIME, in the order they were staged —
|
|
123
|
-
// the same sequence a terminal user would type, which matters because settings
|
|
124
|
-
// can shadow each other. A failure does not abort the rest: the remaining
|
|
125
|
-
// writes are independent, and stopping halfway would leave a state nobody
|
|
126
|
-
// chose. Every failure is reported by key.
|
|
127
|
-
async function applyEdits(edits, routes, button) {
|
|
128
|
-
const list = edits.entries();
|
|
129
|
-
if (!list.length) return { ok: true, failed: [] };
|
|
130
|
-
const label = button && button.textContent;
|
|
131
|
-
if (button) {
|
|
132
|
-
button.disabled = true;
|
|
133
|
-
button.textContent = t("edits.applying");
|
|
134
|
-
}
|
|
135
|
-
const failed = [];
|
|
136
|
-
for (const [key, e] of list) {
|
|
137
|
-
try {
|
|
138
|
-
const r = e.kind === "reset" ? await post(routes.reset, { key }) : await post(routes.set, { key, value: e.value });
|
|
139
|
-
if (!r.ok) failed.push(`${key}: ${(r.output || r.command || "").trim().split("\n")[0]}`);
|
|
140
|
-
} catch (err) {
|
|
141
|
-
failed.push(`${key}: ${err.message}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (button) {
|
|
145
|
-
button.disabled = false;
|
|
146
|
-
if (label) button.textContent = label;
|
|
147
|
-
}
|
|
148
|
-
if (failed.length) toast(t("edits.someFailed", { n: failed.length }), "bad", failed.join("\n"));
|
|
149
|
-
else toast(t("edits.applied", { n: list.length }), "ok");
|
|
150
|
-
return { ok: !failed.length, failed };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// The same batching, for a panel whose edits are not key/value pairs (v0.49.2).
|
|
154
|
-
// The house-rule ledger stages ADDs, REMOVEs, TOGGLEs and MOVEs — four routes,
|
|
155
|
-
// four body shapes — so the entry carries its own `route` and `body` and this
|
|
156
|
-
// runs them one at a time in staged order. Every other rule of `applyEdits`
|
|
157
|
-
// holds exactly: a refused write NEVER aborts the rest, and every failure is
|
|
158
|
-
// reported by the key it was staged under.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
/* 06-edit.js — orc ui client
|
|
3
|
+
editSet, editBar, applyEdits — the staged-write machinery Settings AND Flow
|
|
4
|
+
both use. Nothing is written until Apply; the CLI is still the only writer and
|
|
5
|
+
the only validator.
|
|
6
|
+
|
|
7
|
+
Loaded by app.html in the order its numeric prefix names. Classic
|
|
8
|
+
script, no import/export: an ES module import carries no query string,
|
|
9
|
+
and every static request here needs the per-launch session token. */
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/* ============================================================ staged edits == */
|
|
13
|
+
//
|
|
14
|
+
// WRITES ARE BATCHED (v0.44.1). Every control used to commit on the spot: one
|
|
15
|
+
// click, one `orc config set`, one full re-render of the panel. Changing four
|
|
16
|
+
// keys meant four subprocesses and four re-renders, and each one scrolled the
|
|
17
|
+
// list out from under you — so a routine "set these five things" was a fight.
|
|
18
|
+
//
|
|
19
|
+
// Nothing is written until Apply now. Edits accumulate here, the affected rows
|
|
20
|
+
// mark themselves, and an edit bar at the bottom says how many are pending.
|
|
21
|
+
// The CLI is still the only writer and still the only validator; what changed
|
|
22
|
+
// is WHEN it is called, never by whom.
|
|
23
|
+
//
|
|
24
|
+
// An entry is `{kind: "set", value}` or `{kind: "reset"}` — a per-key reset is
|
|
25
|
+
// `orc config reset <key>` (it REMOVES the key from the file), which is not the
|
|
26
|
+
// same write as setting it to its default value, so it cannot be flattened into
|
|
27
|
+
// one.
|
|
28
|
+
function editSet(onChange) {
|
|
29
|
+
const map = new Map();
|
|
30
|
+
const api = {
|
|
31
|
+
map,
|
|
32
|
+
get size() {
|
|
33
|
+
return map.size;
|
|
34
|
+
},
|
|
35
|
+
// Staging a value back to what it already was CLEARS the edit rather than
|
|
36
|
+
// recording a no-op — otherwise "cancel" and "set it back by hand" would
|
|
37
|
+
// leave the bar claiming an unsaved change that would write nothing.
|
|
38
|
+
set(key, value, original) {
|
|
39
|
+
if (String(value) === String(original)) map.delete(key);
|
|
40
|
+
else map.set(key, { kind: "set", value: String(value), original: String(original) });
|
|
41
|
+
onChange(api);
|
|
42
|
+
},
|
|
43
|
+
reset(key) {
|
|
44
|
+
map.set(key, { kind: "reset" });
|
|
45
|
+
onChange(api);
|
|
46
|
+
},
|
|
47
|
+
// An ACTION entry (v0.49.2): a route and a body, staged like any other
|
|
48
|
+
// edit, applied by `applyActions`. `label` is what the pending list names —
|
|
49
|
+
// a count is not a change list.
|
|
50
|
+
action(key, route, body, label) {
|
|
51
|
+
map.set(key, { kind: "action", route, body, value: label });
|
|
52
|
+
onChange(api);
|
|
53
|
+
},
|
|
54
|
+
drop(key) {
|
|
55
|
+
map.delete(key);
|
|
56
|
+
onChange(api);
|
|
57
|
+
},
|
|
58
|
+
clear() {
|
|
59
|
+
map.clear();
|
|
60
|
+
onChange(api);
|
|
61
|
+
},
|
|
62
|
+
entries() {
|
|
63
|
+
return [...map.entries()];
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
return api;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// The bar. Apply is disabled with nothing pending; Reset is always offered
|
|
70
|
+
// (it is a write in its own right, not an undo); **Cancel appears only when
|
|
71
|
+
// there is something to cancel** — a permanently visible Cancel next to a
|
|
72
|
+
// disabled Apply reads as though the panel is broken.
|
|
73
|
+
function editBar(edits, { onApply, onReset, onCancel, resetLabel }) {
|
|
74
|
+
const bar = el("div", "edit-bar");
|
|
75
|
+
const summary = el("div", "edit-summary");
|
|
76
|
+
const actions = el("div", "edit-actions");
|
|
77
|
+
|
|
78
|
+
const apply = el("button", "btn btn-sm btn-primary", t("edits.apply"));
|
|
79
|
+
apply.type = "button";
|
|
80
|
+
apply.addEventListener("click", () => onApply(apply));
|
|
81
|
+
|
|
82
|
+
const reset = el("button", "btn btn-sm btn-ghost", resetLabel || t("edits.reset"));
|
|
83
|
+
reset.type = "button";
|
|
84
|
+
reset.addEventListener("click", onReset);
|
|
85
|
+
|
|
86
|
+
const cancel = el("button", "btn btn-sm btn-ghost", t("edits.cancel"));
|
|
87
|
+
cancel.type = "button";
|
|
88
|
+
cancel.addEventListener("click", onCancel);
|
|
89
|
+
|
|
90
|
+
bar.paint = () => {
|
|
91
|
+
const n = edits.size;
|
|
92
|
+
bar.classList.toggle("edit-bar-dirty", n > 0);
|
|
93
|
+
apply.disabled = n === 0;
|
|
94
|
+
apply.textContent = n ? t("edits.applyN", { n }) : t("edits.apply");
|
|
95
|
+
// The pending list is named, never counted: "3 changes" is not consent for
|
|
96
|
+
// three writes you can no longer see.
|
|
97
|
+
summary.replaceChildren();
|
|
98
|
+
if (!n) {
|
|
99
|
+
summary.append(el("span", "note", t("edits.none")));
|
|
100
|
+
} else {
|
|
101
|
+
summary.append(el("span", "note", t("edits.pending")));
|
|
102
|
+
const list = el("div", "edit-list");
|
|
103
|
+
for (const [key, e] of edits.entries()) {
|
|
104
|
+
const item = el("span", "edit-chip");
|
|
105
|
+
// Key names and values are CLI data — never translated.
|
|
106
|
+
item.append(el("span", "edit-key", key));
|
|
107
|
+
item.append(document.createTextNode(e.kind === "reset" ? " → " + t("edits.toDefault") : " → " + e.value));
|
|
108
|
+
list.append(item);
|
|
109
|
+
}
|
|
110
|
+
summary.append(list);
|
|
111
|
+
}
|
|
112
|
+
actions.replaceChildren();
|
|
113
|
+
actions.append(apply, reset);
|
|
114
|
+
if (n) actions.append(cancel);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
bar.append(summary, actions);
|
|
118
|
+
bar.paint();
|
|
119
|
+
return bar;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Apply runs the staged writes ONE AT A TIME, in the order they were staged —
|
|
123
|
+
// the same sequence a terminal user would type, which matters because settings
|
|
124
|
+
// can shadow each other. A failure does not abort the rest: the remaining
|
|
125
|
+
// writes are independent, and stopping halfway would leave a state nobody
|
|
126
|
+
// chose. Every failure is reported by key.
|
|
127
|
+
async function applyEdits(edits, routes, button) {
|
|
128
|
+
const list = edits.entries();
|
|
129
|
+
if (!list.length) return { ok: true, failed: [] };
|
|
130
|
+
const label = button && button.textContent;
|
|
131
|
+
if (button) {
|
|
132
|
+
button.disabled = true;
|
|
133
|
+
button.textContent = t("edits.applying");
|
|
134
|
+
}
|
|
135
|
+
const failed = [];
|
|
136
|
+
for (const [key, e] of list) {
|
|
137
|
+
try {
|
|
138
|
+
const r = e.kind === "reset" ? await post(routes.reset, { key }) : await post(routes.set, { key, value: e.value });
|
|
139
|
+
if (!r.ok) failed.push(`${key}: ${(r.output || r.command || "").trim().split("\n")[0]}`);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
failed.push(`${key}: ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (button) {
|
|
145
|
+
button.disabled = false;
|
|
146
|
+
if (label) button.textContent = label;
|
|
147
|
+
}
|
|
148
|
+
if (failed.length) toast(t("edits.someFailed", { n: failed.length }), "bad", failed.join("\n"));
|
|
149
|
+
else toast(t("edits.applied", { n: list.length }), "ok");
|
|
150
|
+
return { ok: !failed.length, failed };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// The same batching, for a panel whose edits are not key/value pairs (v0.49.2).
|
|
154
|
+
// The house-rule ledger stages ADDs, REMOVEs, TOGGLEs and MOVEs — four routes,
|
|
155
|
+
// four body shapes — so the entry carries its own `route` and `body` and this
|
|
156
|
+
// runs them one at a time in staged order. Every other rule of `applyEdits`
|
|
157
|
+
// holds exactly: a refused write NEVER aborts the rest, and every failure is
|
|
158
|
+
// reported by the key it was staged under.
|
|
159
|
+
// `opts.stopOnFail` is the exception: POSITIONAL writes (the hook board) were
|
|
160
|
+
// each computed assuming every earlier one succeeded, so after a refusal the
|
|
161
|
+
// rest would hit the wrong part. It stops at the first failure; the panel
|
|
162
|
+
// passes `opts.stoppedText(n)` to name the n writes that were NOT sent.
|
|
163
|
+
async function applyActions(edits, button, opts) {
|
|
164
|
+
const o = opts || {};
|
|
165
|
+
const list = edits.entries();
|
|
166
|
+
if (!list.length) return { ok: true, failed: [] };
|
|
167
|
+
const label = button && button.textContent;
|
|
168
|
+
if (button) {
|
|
169
|
+
button.disabled = true;
|
|
170
|
+
button.textContent = t("edits.applying");
|
|
171
|
+
}
|
|
172
|
+
const failed = [];
|
|
173
|
+
for (const [key, e] of list) {
|
|
174
|
+
try {
|
|
175
|
+
const r = await post(e.route, e.body);
|
|
176
|
+
if (!r.ok) failed.push(`${key}: ${(r.output || r.command || "").trim().split("\n")[0]}`);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
failed.push(`${key}: ${err.message}`);
|
|
179
|
+
}
|
|
180
|
+
if (o.stopOnFail && failed.length) {
|
|
181
|
+
const left = list.length - list.findIndex((x) => x[0] === key) - 1;
|
|
182
|
+
if (left > 0) failed.push(o.stoppedText ? o.stoppedText(left) : String(left));
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (button) {
|
|
187
|
+
button.disabled = false;
|
|
188
|
+
if (label) button.textContent = label;
|
|
189
|
+
}
|
|
190
|
+
if (failed.length) toast(t("edits.someFailed", { n: o.stopOnFail ? 1 : failed.length }), "bad", failed.join("\n"));
|
|
191
|
+
else toast(t("edits.applied", { n: list.length }), "ok");
|
|
192
|
+
return { ok: !failed.length, failed };
|
|
193
|
+
}
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
and every static request here needs the per-launch session token. */
|
|
20
20
|
|
|
21
21
|
const BH_TABS = ["habits", "rhythm", "gotchas", "quality", "log"];
|
|
22
|
+
// v2.1.0 (A13): the tabs that stay under `habits: off` — review learning is always on.
|
|
23
|
+
const BH_TABS_OFF = ["quality", "gotchas"];
|
|
22
24
|
const BH_WINDOWS = ["30d", "90d", "all"];
|
|
23
25
|
const BH_SVG = "http://www.w3.org/2000/svg";
|
|
24
26
|
let bhTab = "habits";
|
|
@@ -90,22 +92,31 @@ async function bhLoad(body, right, focusId) {
|
|
|
90
92
|
}
|
|
91
93
|
const d = hab.data || {};
|
|
92
94
|
right.replaceChildren(bhModeSwitch(d, body, right));
|
|
93
|
-
// `habits: off` (exit 3) —
|
|
95
|
+
// `habits: off` (exit 3) — the off card INSTEAD of the habit tabs. v2.1.0 (A13):
|
|
96
|
+
// review learning is ALWAYS on, so Review quality and Gotchas stay below it.
|
|
94
97
|
if (d.ok === false && Array.isArray(d.on)) {
|
|
98
|
+
const [gotchas, quality, cands] = await Promise.all([
|
|
99
|
+
read("/api/gotchas").catch(() => ({ data: null })),
|
|
100
|
+
read("/api/gotcha/quality?window=" + encodeURIComponent(bhWindow)).catch(() => ({ data: null })),
|
|
101
|
+
read("/api/gotcha/candidates").catch(() => ({ data: null })),
|
|
102
|
+
]);
|
|
103
|
+
const tabs = bhTabs({ d: {}, tabs: BH_TABS_OFF, gotchas: gotchas.data, quality: quality.data, cands: cands.data, body, right });
|
|
95
104
|
body.classList.toggle("bh-quiet", bhPainted);
|
|
96
|
-
body.replaceChildren(bhOffCard(d), bhFooter(null, true));
|
|
105
|
+
body.replaceChildren(bhOffCard(d), tabs.bar, tabs.panel, bhFooter(null, true));
|
|
106
|
+
tabs.fill();
|
|
97
107
|
bhPainted = true;
|
|
98
108
|
return;
|
|
99
109
|
}
|
|
100
110
|
right.append(bhWindowSwitch(body, right));
|
|
101
|
-
const [log, states, gotchas, quality, cands] = await Promise.all([
|
|
111
|
+
const [log, states, gotchas, quality, cands, points] = await Promise.all([
|
|
102
112
|
read("/api/habits/log").catch(() => ({ data: null })),
|
|
103
113
|
read("/api/habits/states").catch(() => ({ data: null })),
|
|
104
114
|
read("/api/gotchas").catch(() => ({ data: null })),
|
|
105
115
|
read("/api/gotcha/quality?window=" + encodeURIComponent(bhWindow)).catch(() => ({ data: null })),
|
|
106
116
|
read("/api/gotcha/candidates").catch(() => ({ data: null })),
|
|
117
|
+
read("/api/habits/points").catch(() => ({ data: null })),
|
|
107
118
|
]);
|
|
108
|
-
const ctx = { d, log: log.data, states: states.data, gotchas: gotchas.data, quality: quality.data, cands: cands.data, body, right };
|
|
119
|
+
const ctx = { d, log: log.data, states: states.data, gotchas: gotchas.data, quality: quality.data, cands: cands.data, points: points.data, body, right };
|
|
109
120
|
body.classList.toggle("bh-quiet", bhPainted);
|
|
110
121
|
const out = frag();
|
|
111
122
|
out.append(bhHero(d));
|
|
@@ -285,8 +296,9 @@ function bhTabs(ctx) {
|
|
|
285
296
|
gotchas: g ? g.rows.length : null,
|
|
286
297
|
log: ctx.log && ctx.log.events ? ctx.log.events.length : null,
|
|
287
298
|
};
|
|
299
|
+
const TABS = ctx.tabs || BH_TABS;
|
|
288
300
|
const btns = [];
|
|
289
|
-
for (const id of
|
|
301
|
+
for (const id of TABS) {
|
|
290
302
|
const b = el("button", "bh-tab");
|
|
291
303
|
b.type = "button";
|
|
292
304
|
b.id = "bh-tab-" + id;
|
|
@@ -300,12 +312,12 @@ function bhTabs(ctx) {
|
|
|
300
312
|
}
|
|
301
313
|
b.addEventListener("click", () => select(id, false));
|
|
302
314
|
b.addEventListener("keydown", (e) => {
|
|
303
|
-
const i =
|
|
315
|
+
const i = TABS.indexOf(id);
|
|
304
316
|
const to =
|
|
305
|
-
e.key === "ArrowRight" ?
|
|
306
|
-
: e.key === "ArrowLeft" ?
|
|
307
|
-
: e.key === "Home" ?
|
|
308
|
-
: e.key === "End" ?
|
|
317
|
+
e.key === "ArrowRight" ? TABS[(i + 1) % TABS.length]
|
|
318
|
+
: e.key === "ArrowLeft" ? TABS[(i - 1 + TABS.length) % TABS.length]
|
|
319
|
+
: e.key === "Home" ? TABS[0]
|
|
320
|
+
: e.key === "End" ? TABS[TABS.length - 1]
|
|
309
321
|
: null;
|
|
310
322
|
if (!to) return;
|
|
311
323
|
e.preventDefault();
|
|
@@ -335,7 +347,7 @@ function bhTabs(ctx) {
|
|
|
335
347
|
({ habits: bhHabits, rhythm: bhRhythm, gotchas: bhGotchas, quality: bhQuality, log: bhLog })[id](panel, ctx);
|
|
336
348
|
requestAnimationFrame(moveInk);
|
|
337
349
|
}
|
|
338
|
-
return { bar, panel, fill: () => select(
|
|
350
|
+
return { bar, panel, fill: () => select(TABS.includes(bhTab) ? bhTab : TABS[0], false) };
|
|
339
351
|
}
|
|
340
352
|
|
|
341
353
|
// ── Habits ──────────────────────────────────────────────────────────────────
|
|
@@ -866,6 +878,8 @@ function bhQuality(slot, ctx) {
|
|
|
866
878
|
const floor = (c) => c.append(el("div", "bh-floor", Q.floor_line || ""));
|
|
867
879
|
const g = el("div", "grid grid-2");
|
|
868
880
|
const cc = card(t("behaviour.q.cats"), el("span", "note", tn(Q.reviews || 0, "behaviour.q.reviews")));
|
|
881
|
+
// v2.1.0 — a review the project asked for is counted on its own line (the CLI sums it).
|
|
882
|
+
if (Q.project_reviews) cc.append(el("div", "note", t("behaviour.q.projectReviews").replace("{n}", String(Q.project_reviews))));
|
|
869
883
|
if (Q.below_floor) floor(cc);
|
|
870
884
|
else {
|
|
871
885
|
const bands = Q.bands || {};
|
|
@@ -948,19 +962,70 @@ function bhQuality(slot, ctx) {
|
|
|
948
962
|
}
|
|
949
963
|
g2.append(sp);
|
|
950
964
|
slot.append(g2);
|
|
965
|
+
bhFixes(slot, Q);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// v2.1.0 — "Fixes after review" (`orc fix record`). Every number is the CLI's:
|
|
969
|
+
// the count, each cause's `n` and its bar `share`, and the misses list.
|
|
970
|
+
function bhFixes(slot, Q) {
|
|
971
|
+
const F = Q.fixes;
|
|
972
|
+
if (!F) return;
|
|
973
|
+
const M = Q.misses || { total: 0, list: [] };
|
|
974
|
+
const g = el("div", "grid grid-2");
|
|
975
|
+
const fc = card(t("behaviour.q.fixes"), el("span", "note", tn(F.total || 0, "behaviour.q.fixCount")));
|
|
976
|
+
if (!F.total) fc.append(empty(t("behaviour.q.fixesNone")));
|
|
977
|
+
else {
|
|
978
|
+
(F.causes || []).forEach((c, i) => {
|
|
979
|
+
const row = el("div", "bh-cat");
|
|
980
|
+
const bar = el("div", "bh-cat-bar");
|
|
981
|
+
const s = el("span", "bh-grow-x");
|
|
982
|
+
s.style.width = Math.round((c.share || 0) * 100) + "%";
|
|
983
|
+
s.dataset.band = c.id === "other" ? "warn" : "bad";
|
|
984
|
+
s.style.animationDelay = i * 90 + "ms";
|
|
985
|
+
bar.append(s);
|
|
986
|
+
const label = { sonar: t("behaviour.q.cause.sonar"), orc: t("behaviour.q.cause.orc"), ai: t("behaviour.q.cause.ai"), other: t("behaviour.q.cause.other") }[c.id] || c.id;
|
|
987
|
+
row.append(el("div", "bh-cat-name", label), bar, el("div", "bh-cat-num mono", String(c.n)));
|
|
988
|
+
fc.append(row);
|
|
989
|
+
});
|
|
990
|
+
fc.append(el("div", "note", t("behaviour.q.fixesNote")));
|
|
991
|
+
}
|
|
992
|
+
g.append(fc);
|
|
993
|
+
const mc = card(t("behaviour.q.misses"), el("span", "note", String(M.total || 0)));
|
|
994
|
+
if (!(M.list || []).length) mc.append(empty(t("behaviour.q.missesNone")));
|
|
995
|
+
for (const m of M.list || []) {
|
|
996
|
+
const row = el("div", "bh-lesson");
|
|
997
|
+
row.append(el("div", "bh-lesson-id mono", m.obs || ""));
|
|
998
|
+
const mid = el("div");
|
|
999
|
+
mid.append(el("div", "bh-lesson-rule mono", `${m.path || "—"}${m.lines && m.lines.length ? ":" + m.lines.join("-") : ""}`));
|
|
1000
|
+
const sub = el("div", "bh-lesson-sub");
|
|
1001
|
+
if (m.rule) sub.append(el("span", "mono", m.rule));
|
|
1002
|
+
if (m.category) sub.append(el("span", "mono", m.category));
|
|
1003
|
+
if (m.missed_by) sub.append(el("span", null, t("behaviour.q.missedBy", { run: m.missed_by })));
|
|
1004
|
+
mid.append(sub);
|
|
1005
|
+
row.append(mid);
|
|
1006
|
+
mc.append(row);
|
|
1007
|
+
}
|
|
1008
|
+
g.append(mc);
|
|
1009
|
+
slot.append(g);
|
|
951
1010
|
}
|
|
952
1011
|
|
|
953
1012
|
// ── Answer log ──────────────────────────────────────────────────────────────
|
|
954
1013
|
function bhLog(slot, ctx) {
|
|
955
1014
|
const c = card(t("behaviour.log.title"), el("span", "note", t("behaviour.log.from")));
|
|
956
1015
|
const events = (ctx.log && ctx.log.events) || [];
|
|
1016
|
+
const titles = {};
|
|
1017
|
+
for (const p of (ctx.points && ctx.points.points) || []) titles[p.qid] = p.title;
|
|
957
1018
|
if (!events.length) c.append(empty(t("behaviour.emptyTitle"), t("behaviour.emptyHint")));
|
|
958
1019
|
const list = el("div", "bh-log");
|
|
959
1020
|
events.forEach((e, i) => {
|
|
960
1021
|
const row = el("div", "bh-log-row");
|
|
961
1022
|
row.style.animationDelay = Math.min(i, 12) * 70 + "ms";
|
|
962
1023
|
const q = el("span", "bh-log-q");
|
|
963
|
-
|
|
1024
|
+
// v2.1.0 (A12): the question's title from the registry, the id kept as a hint.
|
|
1025
|
+
const title = titles[e.qid];
|
|
1026
|
+
const qn = el("span", title ? null : "mono", title || e.qid);
|
|
1027
|
+
if (title) qn.title = e.qid;
|
|
1028
|
+
q.append(qn, document.createTextNode(" → "), el("b", null, e.chose));
|
|
964
1029
|
const by = chip(e.by);
|
|
965
1030
|
by.dataset.by = e.by;
|
|
966
1031
|
row.append(el("span", "t mono", new Date(e.at).toLocaleString()), el("span", "ln mono", e.lane), q, by);
|