@azure-id/orc 1.9.2 → 2.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.
Files changed (228) hide show
  1. package/CHANGELOG.md +722 -0
  2. package/README-id.md +56 -42
  3. package/README.md +58 -41
  4. package/bin/build-agents.js +125 -29
  5. package/bin/clear-logs.js +620 -0
  6. package/bin/cli.js +1873 -180
  7. package/bin/fix.js +459 -0
  8. package/bin/gotcha-import.js +1081 -0
  9. package/bin/gotcha.js +1566 -0
  10. package/bin/graph-query.js +1 -1
  11. package/bin/graph.js +717 -717
  12. package/bin/habit.js +1538 -0
  13. package/bin/mockrun-catalog.js +284 -276
  14. package/bin/onboarding-content.js +2 -1
  15. package/bin/pricing.json +28 -21
  16. package/bin/run-undo.js +398 -0
  17. package/bin/trace-write.js +869 -0
  18. package/bin/verify-contracts.js +627 -100
  19. package/bin/verify-package.js +68 -10
  20. package/bin/webui/api.js +151 -6
  21. package/bin/webui/app.html +239 -232
  22. package/bin/webui/css/00-tokens.css +110 -92
  23. package/bin/webui/css/04-motion.css +87 -0
  24. package/bin/webui/css/06-responsive.css +203 -178
  25. package/bin/webui/css/panels/behaviour.css +205 -0
  26. package/bin/webui/css/panels/hookui.css +79 -0
  27. package/bin/webui/fixtures/behaviour.js +562 -0
  28. package/bin/webui/fixtures/extra.js +9 -9
  29. package/bin/webui/fixtures/flow.js +81 -81
  30. package/bin/webui/fixtures/hookui.js +506 -431
  31. package/bin/webui/fixtures/index.js +66 -3
  32. package/bin/webui/fixtures/knowledge.js +9 -3
  33. package/bin/webui/fixtures/maintenance.js +123 -42
  34. package/bin/webui/fixtures/rules.js +1007 -1002
  35. package/bin/webui/fixtures/settings.js +6 -7
  36. package/bin/webui/fixtures/stats.js +25 -6
  37. package/bin/webui/fixtures/wait.js +98 -97
  38. package/bin/webui/i18n/en/behaviour.json +156 -0
  39. package/bin/webui/i18n/en/hookui.json +290 -194
  40. package/bin/webui/i18n/en/maintenance.json +71 -52
  41. package/bin/webui/i18n/en/nav.json +25 -24
  42. package/bin/webui/i18n/en/settings.json +60 -60
  43. package/bin/webui/i18n/en/tour.json +37 -35
  44. package/bin/webui/i18n/id/behaviour.json +156 -0
  45. package/bin/webui/i18n/id/hookui.json +290 -194
  46. package/bin/webui/i18n/id/maintenance.json +71 -52
  47. package/bin/webui/i18n/id/nav.json +25 -24
  48. package/bin/webui/i18n/id/settings.json +60 -60
  49. package/bin/webui/i18n/id/tour.json +37 -35
  50. package/bin/webui/js/01-i18n.js +155 -154
  51. package/bin/webui/js/05-banners.js +181 -172
  52. package/bin/webui/js/06-edit.js +193 -183
  53. package/bin/webui/js/90-tour.js +498 -494
  54. package/bin/webui/js/91-shortcuts.js +126 -126
  55. package/bin/webui/js/99-boot.js +121 -118
  56. package/bin/webui/js/panels/behaviour.js +1087 -0
  57. package/bin/webui/js/panels/hookui.js +2196 -1691
  58. package/bin/webui/js/panels/maintenance.js +336 -235
  59. package/bin/webui/js/panels/settings.js +726 -719
  60. package/mock-run/INDEX.md +110 -107
  61. package/mock-run/extra-slots.md +1 -1
  62. package/mock-run/gotcha-import.md +118 -0
  63. package/mock-run/habits.md +129 -0
  64. package/mock-run/orc-aftermath.md +392 -392
  65. package/mock-run/orc-budget.md +29 -33
  66. package/mock-run/orc-cli.md +200 -200
  67. package/mock-run/orc-explain.md +86 -86
  68. package/mock-run/orc-extra.md +389 -392
  69. package/mock-run/orc-fix.md +87 -0
  70. package/mock-run/orc-pattern.md +112 -112
  71. package/mock-run/orc-quick.md +6 -1
  72. package/mock-run/orc.md +157 -157
  73. package/package.json +1 -1
  74. package/templates/agents/MODEL-MAPPING.md +37 -34
  75. package/templates/agents/orc-advisor-opus-5-xhigh.md +1 -7
  76. package/templates/agents/orc-analyze-mini-opus-5-med.md +1 -6
  77. package/templates/agents/orc-analyze-mini-sonnet-5-high.md +2 -5
  78. package/templates/agents/{orc-claude-writer-opus-4-8-high.md → orc-claude-writer-opus-5-low.md} +48 -53
  79. package/templates/agents/orc-context-combiner-opus-5-high.md +1 -11
  80. package/templates/agents/orc-executor-haiku-4-5.md +14 -6
  81. package/templates/agents/orc-executor-opus-4-7-high.md +14 -6
  82. package/templates/agents/orc-executor-opus-4-7-med.md +14 -6
  83. package/templates/agents/orc-executor-opus-4-8-high.md +14 -6
  84. package/templates/agents/orc-executor-opus-5-high.md +14 -6
  85. package/templates/agents/orc-executor-opus-5-low.md +14 -6
  86. package/templates/agents/orc-executor-opus-5-med.md +14 -6
  87. package/templates/agents/orc-executor-sonnet-4-6-high.md +14 -6
  88. package/templates/agents/orc-executor-sonnet-4-6-med.md +14 -6
  89. package/templates/agents/orc-executor-sonnet-5-high.md +15 -7
  90. package/templates/agents/orc-executor-sonnet-5-low.md +155 -0
  91. package/templates/agents/orc-executor-sonnet-5-med.md +155 -0
  92. package/templates/agents/orc-graph-noter-sonnet-4-6-med.md +1 -10
  93. package/templates/agents/orc-judge-opus-5-xhigh.md +5 -9
  94. package/templates/agents/orc-learn-writer-opus-5-low.md +1 -7
  95. package/templates/agents/orc-pattern-codifier-opus-5-med.md +1 -8
  96. package/templates/agents/orc-pattern-codifier-sonnet-5-high.md +58 -63
  97. package/templates/agents/orc-planner-mini-opus-5-med.md +1 -4
  98. package/templates/agents/orc-planner-mini-sonnet-5-high.md +2 -3
  99. package/templates/agents/orc-planner-opus-5-med.md +1 -4
  100. package/templates/agents/orc-recon-opus-5-low.md +1 -8
  101. package/templates/agents/orc-recon-sonnet-4-6-med.md +1 -8
  102. package/templates/agents/orc-retro-opus-5-med.md +6 -8
  103. package/templates/agents/orc-retro-sonnet-5-high.md +7 -8
  104. package/templates/agents/orc-reviewer-opus-5-low.md +96 -0
  105. package/templates/agents/orc-scout-opus-5-low.md +1 -6
  106. package/templates/agents/orc-scout-sonnet-4-6-high.md +35 -39
  107. package/templates/agents/orc-system-analyst-opus-5-high.md +1 -6
  108. package/templates/agents/orc-test-author-opus-5-med.md +4 -5
  109. package/templates/agents/orc-trace-writer-haiku-4-5.md +3 -7
  110. package/templates/agents/orc-verifier-opus-5-med.md +16 -8
  111. package/templates/agents/{orc-wiki-scanner-opus-4-8-high.md → orc-wiki-scanner-opus-5-low.md} +74 -79
  112. package/templates/agents/orc-wiki-scanner-sonnet-5-high.md +97 -106
  113. package/templates/commands/orc-analyze.md +13 -21
  114. package/templates/commands/orc-fast.md +10 -15
  115. package/templates/commands/orc-fix.md +18 -0
  116. package/templates/commands/orc-poly.md +12 -21
  117. package/templates/commands/orc-pr-driver.md +11 -30
  118. package/templates/commands/orc-pr-setup.md +10 -31
  119. package/templates/commands/orc-route.md +11 -41
  120. package/templates/commands/orc-test.md +5 -60
  121. package/templates/commands/orc-wiki.md +64 -64
  122. package/templates/hooks/README.md +109 -7
  123. package/templates/hooks/orc-session-hook.js +339 -0
  124. package/templates/hooks/orc-statusline-render.js +317 -23
  125. package/templates/hooks/orc-statusline.js +165 -50
  126. package/templates/hooks/orc-subagent-line.js +23 -5
  127. package/templates/hooks/orc-weather-fetch.js +123 -0
  128. package/templates/skills/_shared/README.md +9 -0
  129. package/templates/skills/_shared/code-graph.md +47 -55
  130. package/templates/skills/_shared/config-precedence.md +3 -1
  131. package/templates/skills/_shared/extra-dispatch.md +78 -93
  132. package/templates/skills/_shared/gotchas.md +248 -177
  133. package/templates/skills/_shared/habits.md +101 -0
  134. package/templates/skills/_shared/lane-contract.md +84 -0
  135. package/templates/skills/_shared/opus5-only.md +14 -14
  136. package/templates/skills/_shared/phases/README.md +142 -83
  137. package/templates/skills/_shared/phases/analyst-gates.md +10 -21
  138. package/templates/skills/_shared/phases/execution.md +8 -14
  139. package/templates/skills/_shared/phases/house-rules.md +27 -32
  140. package/templates/skills/_shared/phases/intake.md +127 -133
  141. package/templates/skills/_shared/phases/mock-example.md +46 -56
  142. package/templates/skills/_shared/phases/plan-handoff.md +91 -97
  143. package/templates/skills/_shared/phases/planning.md +7 -17
  144. package/templates/skills/_shared/phases/preflight.md +19 -42
  145. package/templates/skills/_shared/phases/review.md +24 -28
  146. package/templates/skills/_shared/phases/rules.md +18 -42
  147. package/templates/skills/_shared/phases/scoring.md +55 -65
  148. package/templates/skills/_shared/phases/security-checklist.md +46 -50
  149. package/templates/skills/_shared/phases/security.md +45 -55
  150. package/templates/skills/_shared/phases/ship.md +6 -15
  151. package/templates/skills/_shared/phases/stop-resume.md +2 -5
  152. package/templates/skills/_shared/phases/summary.md +73 -48
  153. package/templates/skills/_shared/phases/testgen.md +41 -51
  154. package/templates/skills/_shared/phases/trace-verbs.md +447 -0
  155. package/templates/skills/_shared/phases/trace.md +66 -297
  156. package/templates/skills/_shared/phases/verify.md +62 -70
  157. package/templates/skills/_shared/phases/wave-grouping.md +128 -133
  158. package/templates/skills/_shared/phases/wiki-consult.md +10 -6
  159. package/templates/skills/_shared/read-ladder.md +2 -55
  160. package/templates/skills/_shared/return-validation.md +17 -70
  161. package/templates/skills/_shared/review-slice.md +106 -0
  162. package/templates/skills/_shared/smoke-gate.md +46 -28
  163. package/templates/skills/_shared/wait.md +1 -0
  164. package/templates/skills/context-combiner/SKILL.md +15 -44
  165. package/templates/skills/orc/README.md +2 -2
  166. package/templates/skills/orc/SKILL.md +35 -63
  167. package/templates/skills/orc/config.md +25 -27
  168. package/templates/skills/orc/examples/full-run-mock.md +2 -1
  169. package/templates/skills/orc/references/effort-and-mode.md +17 -17
  170. package/templates/skills/orc/references/pattern-gate.md +89 -89
  171. package/templates/skills/orc/references/phases/intake.md +41 -47
  172. package/templates/skills/orc/references/phases/integration.md +13 -19
  173. package/templates/skills/orc/references/preflight-report.md +11 -12
  174. package/templates/skills/orc/references/ultra-mode.md +9 -7
  175. package/templates/skills/orc/schemas/checkpoint.md +122 -122
  176. package/templates/skills/orc/subskills/orc-execution/SKILL.md +27 -73
  177. package/templates/skills/orc/subskills/orc-execution/core.md +12 -99
  178. package/templates/skills/orc/subskills/orc-execution/subagent.md +14 -13
  179. package/templates/skills/orc/subskills/orc-review-verify/SKILL.md +12 -53
  180. package/templates/skills/orc/subskills/orc-review-verify/core.md +52 -135
  181. package/templates/skills/orc/subskills/orc-review-verify/subagent.md +7 -7
  182. package/templates/skills/orc/subskills/orc-testgen/SKILL.md +11 -22
  183. package/templates/skills/orc/subskills/orc-testgen/core.md +20 -59
  184. package/templates/skills/orc/subskills/orc-testgen/subagent.md +7 -7
  185. package/templates/skills/orc-advisor/SKILL.md +56 -60
  186. package/templates/skills/orc-analyze/SKILL.md +33 -67
  187. package/templates/skills/orc-analyze/schemas/report-audit.md +2 -1
  188. package/templates/skills/orc-analyze/schemas/report-prose.md +2 -1
  189. package/templates/skills/orc-analyze-mini/SKILL.md +35 -70
  190. package/templates/skills/orc-claude/SKILL.md +14 -14
  191. package/templates/skills/orc-claude/examples/claude-run-mock.md +65 -65
  192. package/templates/skills/orc-diy/README.md +31 -0
  193. package/templates/skills/orc-diy/SKILL.md +27 -78
  194. package/templates/skills/orc-diy/references/blocks/pattern.md +18 -18
  195. package/templates/skills/orc-fast/SKILL.md +49 -75
  196. package/templates/skills/orc-fix/SKILL.md +110 -0
  197. package/templates/skills/orc-judge/SKILL.md +77 -82
  198. package/templates/skills/orc-mini/SKILL.md +64 -112
  199. package/templates/skills/orc-mini/examples/mini-run-mock.md +4 -2
  200. package/templates/skills/orc-pattern/SKILL.md +27 -48
  201. package/templates/skills/orc-poly/SKILL.md +32 -61
  202. package/templates/skills/orc-pr-driver/SKILL.md +25 -51
  203. package/templates/skills/orc-pr-driver/references/green-gate.md +113 -105
  204. package/templates/skills/orc-pr-setup/SKILL.md +20 -45
  205. package/templates/skills/orc-quick/README.md +44 -3
  206. package/templates/skills/orc-quick/SKILL.md +77 -108
  207. package/templates/skills/orc-quick/references/dispatch-gate.md +18 -6
  208. package/templates/skills/orc-quick/references/gh-mode.md +50 -1
  209. package/templates/skills/orc-quick/references/look.md +3 -1
  210. package/templates/skills/orc-retro/SKILL.md +20 -19
  211. package/templates/skills/orc-retro/examples/retro-mock.md +17 -17
  212. package/templates/skills/orc-route/SKILL.md +25 -45
  213. package/templates/skills/orc-test/SKILL.md +16 -37
  214. package/templates/skills/orc-verify/SKILL.md +14 -32
  215. package/templates/skills/orc-wait/SKILL.md +156 -163
  216. package/templates/skills/orc-wiki/README.md +325 -325
  217. package/templates/skills/orc-wiki/SKILL.md +195 -195
  218. package/templates/skills/orc-wiki/examples/wiki-run-mock.md +37 -37
  219. package/templates/skills/orc-wiki/references/extra.md +2 -2
  220. package/templates/skills/orc-wiki/references/partial-refresh.md +276 -271
  221. package/templates/skills/orc-wiki/references/phases/phase-0.md +2 -7
  222. package/templates/skills/orc-wiki/references/phases/phase-1.md +1 -6
  223. package/templates/skills/orc-wiki/references/phases/phase-2.md +4 -9
  224. package/templates/skills/orc-wiki/references/phases/phase-3.md +1 -6
  225. package/templates/skills/orc-wiki/references/phases/phase-3c.md +4 -8
  226. package/templates/agents/orc-claude-writer-opus-5-med.md +0 -55
  227. package/templates/agents/orc-reviewer-opus-5-med.md +0 -60
  228. package/templates/agents/orc-wiki-scanner-opus-5-med.md +0 -81
@@ -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
- async function applyActions(edits, button) {
160
- const list = edits.entries();
161
- if (!list.length) return { ok: true, failed: [] };
162
- const label = button && button.textContent;
163
- if (button) {
164
- button.disabled = true;
165
- button.textContent = t("edits.applying");
166
- }
167
- const failed = [];
168
- for (const [key, e] of list) {
169
- try {
170
- const r = await post(e.route, e.body);
171
- if (!r.ok) failed.push(`${key}: ${(r.output || r.command || "").trim().split("\n")[0]}`);
172
- } catch (err) {
173
- failed.push(`${key}: ${err.message}`);
174
- }
175
- }
176
- if (button) {
177
- button.disabled = false;
178
- if (label) button.textContent = label;
179
- }
180
- if (failed.length) toast(t("edits.someFailed", { n: failed.length }), "bad", failed.join("\n"));
181
- else toast(t("edits.applied", { n: list.length }), "ok");
182
- return { ok: !failed.length, failed };
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
+ }