@azure-id/orc 0.56.1 → 1.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 (162) hide show
  1. package/CHANGELOG.md +219 -0
  2. package/README-id.md +49 -107
  3. package/README.md +694 -719
  4. package/bin/build-agents.js +18 -11
  5. package/bin/cli.js +33699 -29855
  6. package/bin/onboarding-content.js +0 -3
  7. package/bin/test-run.js +491 -0
  8. package/bin/verify-contracts.js +4330 -3277
  9. package/bin/verify-package.js +76 -6
  10. package/bin/webui/api.js +1201 -1163
  11. package/bin/webui/app.html +210 -198
  12. package/bin/webui/css/06-responsive.css +120 -108
  13. package/bin/webui/css/panels/extra.css +29 -0
  14. package/bin/webui/css/panels/lanes.css +134 -0
  15. package/bin/webui/css/panels/settings.css +79 -3
  16. package/bin/webui/css/panels/wait.css +123 -0
  17. package/bin/webui/fixtures/extra.js +2036 -1934
  18. package/bin/webui/fixtures/flow.js +2 -2
  19. package/bin/webui/fixtures/index.js +510 -486
  20. package/bin/webui/fixtures/lanes.js +218 -0
  21. package/bin/webui/fixtures/settings.js +261 -34
  22. package/bin/webui/fixtures/stats.js +3 -3
  23. package/bin/webui/fixtures/wait.js +97 -0
  24. package/bin/webui/i18n/TERMS.md +135 -134
  25. package/bin/webui/i18n/en/extra.json +345 -313
  26. package/bin/webui/i18n/en/lanes.json +29 -0
  27. package/bin/webui/i18n/en/nav.json +21 -19
  28. package/bin/webui/i18n/en/overview.json +2 -0
  29. package/bin/webui/i18n/en/settings.json +16 -5
  30. package/bin/webui/i18n/en/wait.json +41 -0
  31. package/bin/webui/i18n/id/extra.json +345 -313
  32. package/bin/webui/i18n/id/lanes.json +29 -0
  33. package/bin/webui/i18n/id/nav.json +21 -19
  34. package/bin/webui/i18n/id/overview.json +2 -0
  35. package/bin/webui/i18n/id/settings.json +15 -4
  36. package/bin/webui/i18n/id/wait.json +41 -0
  37. package/bin/webui/js/01-i18n.js +151 -149
  38. package/bin/webui/js/panels/extra.js +3199 -3011
  39. package/bin/webui/js/panels/lanes.js +259 -0
  40. package/bin/webui/js/panels/overview.js +10 -0
  41. package/bin/webui/js/panels/settings.js +149 -3
  42. package/bin/webui/js/panels/wait.js +253 -0
  43. package/package.json +39 -39
  44. package/templates/agents/MODEL-MAPPING.md +23 -35
  45. package/templates/agents/orc-executor-opus-4-7-high.md +1 -1
  46. package/templates/agents/orc-executor-opus-4-7-med.md +1 -1
  47. package/templates/agents/orc-executor-opus-4-8-high.md +1 -1
  48. package/templates/agents/orc-executor-opus-5-high.md +1 -1
  49. package/templates/agents/orc-executor-opus-5-low.md +1 -1
  50. package/templates/agents/orc-executor-opus-5-med.md +1 -1
  51. package/templates/agents/orc-retro-opus-5-med.md +73 -73
  52. package/templates/agents/orc-retro-sonnet-5-high.md +72 -72
  53. package/templates/agents/orc-trace-writer-haiku-4-5.md +107 -107
  54. package/templates/commands/orc-route.md +1 -1
  55. package/templates/commands/orc-wait.md +19 -0
  56. package/templates/commands/orc.md +1 -1
  57. package/templates/hooks/orc-statusline.js +39 -0
  58. package/templates/skills/_shared/README.md +7 -3
  59. package/templates/skills/_shared/config-precedence.md +198 -0
  60. package/templates/skills/_shared/drift-recovery.md +1 -1
  61. package/templates/skills/_shared/extra-dispatch.md +159 -7
  62. package/templates/skills/_shared/interview.md +1 -1
  63. package/templates/skills/_shared/opus5-only.md +9 -7
  64. package/templates/skills/_shared/phases/README.md +82 -0
  65. package/templates/skills/{orc/references → _shared/phases}/analyst-gates.md +15 -3
  66. package/templates/skills/_shared/phases/execution.md +143 -0
  67. package/templates/skills/{orc/references → _shared/phases}/house-rules.md +13 -1
  68. package/templates/skills/{orc/references → _shared/phases}/intake.md +15 -2
  69. package/templates/skills/_shared/phases/mock-example.md +56 -0
  70. package/templates/skills/{orc/references → _shared/phases}/plan-handoff.md +16 -3
  71. package/templates/skills/_shared/phases/planning.md +135 -0
  72. package/templates/skills/_shared/phases/preflight.md +98 -0
  73. package/templates/skills/_shared/phases/review.md +53 -0
  74. package/templates/skills/_shared/phases/scoring.md +65 -0
  75. package/templates/skills/{orc/references → _shared/phases}/security-checklist.md +12 -1
  76. package/templates/skills/_shared/phases/security.md +55 -0
  77. package/templates/skills/_shared/phases/ship.md +88 -0
  78. package/templates/skills/{orc/references/stop-and-resume.md → _shared/phases/stop-resume.md} +169 -160
  79. package/templates/skills/_shared/phases/summary.md +48 -0
  80. package/templates/skills/_shared/phases/testgen.md +51 -0
  81. package/templates/skills/{orc/references/trace-protocol.md → _shared/phases/trace.md} +49 -6
  82. package/templates/skills/_shared/phases/verify.md +70 -0
  83. package/templates/skills/{orc/references → _shared/phases}/wave-grouping.md +14 -2
  84. package/templates/skills/{orc/references → _shared/phases}/wiki-consult.md +13 -1
  85. package/templates/skills/_shared/read-ladder.md +48 -0
  86. package/templates/skills/_shared/wait.md +240 -0
  87. package/templates/skills/context-combiner/SKILL.md +214 -187
  88. package/templates/skills/orc/README.md +148 -150
  89. package/templates/skills/orc/SKILL.md +75 -358
  90. package/templates/skills/orc/config.md +137 -389
  91. package/templates/skills/orc/examples/full-run-mock.md +1 -1
  92. package/templates/skills/orc/references/effort-and-mode.md +16 -18
  93. package/templates/skills/orc/references/phases/intake.md +47 -0
  94. package/templates/skills/orc/references/phases/integration.md +19 -0
  95. package/templates/skills/orc/references/preflight-report.md +4 -4
  96. package/templates/skills/orc/references/ultra-mode.md +117 -123
  97. package/templates/skills/orc/schemas/planning-output.md +279 -279
  98. package/templates/skills/orc/subskills/orc-planner/SKILL.md +260 -264
  99. package/templates/skills/orc/subskills/orc-pr/stack-gate.md +4 -1
  100. package/templates/skills/orc-aftermath/SKILL.md +158 -136
  101. package/templates/skills/orc-analyze/SKILL.md +252 -220
  102. package/templates/skills/orc-analyze/references/branching.md +51 -51
  103. package/templates/skills/orc-analyze/references/thin-input.md +1 -1
  104. package/templates/skills/orc-analyze-mini/SKILL.md +128 -112
  105. package/templates/skills/orc-boundary/SKILL.md +249 -208
  106. package/templates/skills/orc-brainstorm/SKILL.md +377 -339
  107. package/templates/skills/orc-budget/SKILL.md +248 -225
  108. package/templates/skills/orc-challenge/README.md +1 -1
  109. package/templates/skills/orc-challenge/SKILL.md +298 -255
  110. package/templates/skills/orc-challenge/examples/council-full-roster.md +1 -1
  111. package/templates/skills/orc-claude/SKILL.md +221 -199
  112. package/templates/skills/orc-diy/README.md +2 -1
  113. package/templates/skills/orc-diy/SKILL.md +132 -68
  114. package/templates/skills/orc-diy/references/blocks/analyze.md +1 -1
  115. package/templates/skills/orc-diy/references/compile.md +21 -7
  116. package/templates/skills/orc-diy/references/flow-schema.md +2 -2
  117. package/templates/skills/orc-doc/SKILL.md +49 -7
  118. package/templates/skills/orc-explain/SKILL.md +24 -0
  119. package/templates/skills/orc-export/SKILL.md +31 -5
  120. package/templates/skills/orc-fast/SKILL.md +218 -185
  121. package/templates/skills/orc-grill/SKILL.md +238 -207
  122. package/templates/skills/orc-handoff/SKILL.md +223 -204
  123. package/templates/skills/orc-learn/SKILL.md +181 -156
  124. package/templates/skills/orc-mini/SKILL.md +47 -24
  125. package/templates/skills/orc-pact/SKILL.md +255 -218
  126. package/templates/skills/orc-pattern/SKILL.md +154 -124
  127. package/templates/skills/orc-poly/SKILL.md +242 -216
  128. package/templates/skills/orc-poly/references/gather.md +1 -1
  129. package/templates/skills/orc-pr-driver/SKILL.md +196 -167
  130. package/templates/skills/orc-pr-driver/references/orc-run-split.md +1 -1
  131. package/templates/skills/orc-pr-setup/SKILL.md +212 -184
  132. package/templates/skills/orc-quick/README.md +2 -2
  133. package/templates/skills/orc-quick/SKILL.md +51 -13
  134. package/templates/skills/orc-quick/references/dispatch-gate.md +2 -3
  135. package/templates/skills/orc-retro/SKILL.md +249 -222
  136. package/templates/skills/orc-retro/examples/retro-mock.md +171 -171
  137. package/templates/skills/orc-route/SKILL.md +188 -165
  138. package/templates/skills/orc-verify/SKILL.md +103 -84
  139. package/templates/skills/orc-wait/SKILL.md +163 -0
  140. package/templates/skills/orc-wiki/SKILL.md +60 -206
  141. package/templates/skills/orc-wiki/references/phases/phase-0.md +71 -0
  142. package/templates/skills/orc-wiki/references/phases/phase-1.md +35 -0
  143. package/templates/skills/orc-wiki/references/phases/phase-2.md +52 -0
  144. package/templates/skills/orc-wiki/references/phases/phase-3.md +57 -0
  145. package/templates/skills/orc-wiki/references/phases/phase-3c.md +36 -0
  146. package/templates/agents/orc-advisor-fable-5.md +0 -50
  147. package/templates/agents/orc-analyst-fable-5.md +0 -115
  148. package/templates/agents/orc-judge-fable-5.md +0 -79
  149. package/templates/agents/orc-planner-fable-5.md +0 -152
  150. package/templates/agents/orc-reviewer-fable-5.md +0 -57
  151. package/templates/skills/_shared/fable5-override.md +0 -56
  152. package/templates/skills/orc-diy/references/blocks/execution.md +0 -42
  153. package/templates/skills/orc-diy/references/blocks/mock-example.md +0 -22
  154. package/templates/skills/orc-diy/references/blocks/planning.md +0 -34
  155. package/templates/skills/orc-diy/references/blocks/review.md +0 -18
  156. package/templates/skills/orc-diy/references/blocks/scoring.md +0 -16
  157. package/templates/skills/orc-diy/references/blocks/security.md +0 -25
  158. package/templates/skills/orc-diy/references/blocks/ship.md +0 -25
  159. package/templates/skills/orc-diy/references/blocks/summary.md +0 -18
  160. package/templates/skills/orc-diy/references/blocks/testgen.md +0 -17
  161. package/templates/skills/orc-diy/references/blocks/trace.md +0 -28
  162. package/templates/skills/orc-diy/references/blocks/verify.md +0 -25
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ /* panels/lanes.js — orc ui client
3
+ The `orc lane` noun, rendered: which lanes exist, which SHARED phases each
4
+ one runs, and the whole call catalogue.
5
+
6
+ Loaded by app.html in the order its numeric prefix names. Classic
7
+ script, no import/export: an ES module import carries no query string,
8
+ and every static request here needs the per-launch session token. */
9
+
10
+
11
+ /* =================================================================== LANES == */
12
+
13
+ // TWO TABS, on this panel's own precedent (Crosslink's two, Knowledge's five,
14
+ // Extra's six). Phases and Calls answer two different questions about the same
15
+ // noun — what a lane READS, in order, and what a lane RUNS — and stacking both
16
+ // down one column made the second one something you had to know was there.
17
+ //
18
+ // The tab survives a re-render, exactly as `KN_TAB` and `EX_TAB` do: losing
19
+ // your place because a fetch resolved is the small rudeness that makes a panel
20
+ // feel unreliable.
21
+ let LN_TAB = "phases";
22
+ // The selected lane likewise. A lane picker that snaps back to `orc` every time
23
+ // the panel repaints is a picker nobody uses twice.
24
+ let LN_LANE = "orc";
25
+
26
+ PANELS.lanes = function (host) {
27
+ head(host, t("lanes.title"), t("lanes.sub"));
28
+ section(
29
+ host,
30
+ () => read("/api/lanes").then((r) => r.data),
31
+ (d) => {
32
+ const out = frag();
33
+ const lanes = d.lanes || [];
34
+ if (!lanes.length) {
35
+ out.append(el("div", "empty", t("lanes.none")));
36
+ return out;
37
+ }
38
+ if (!lanes.some((l) => l.lane === LN_LANE)) LN_LANE = lanes[0].lane;
39
+
40
+ const body = el("div", "stack");
41
+ const paint = () => {
42
+ body.replaceChildren();
43
+ if (LN_TAB === "phases") phasesTab(body, lanes);
44
+ else callsTab(body);
45
+ };
46
+
47
+ // The panel's own `.tabs` convention: a flat strip driven by
48
+ // `aria-selected`, exactly as Knowledge and Runs build theirs. Keys are
49
+ // written out IN FULL rather than assembled from the tab id — a key built
50
+ // from a fragment is invisible to every check that looks for one.
51
+ const strip = el("div", "tabs");
52
+ const select = (which) => {
53
+ LN_TAB = which;
54
+ for (const b of strip.children) b.setAttribute("aria-selected", String(b.dataset.tab === which));
55
+ paint();
56
+ };
57
+ for (const [which, label] of [
58
+ ["phases", t("lanes.tab.phases")],
59
+ ["calls", t("lanes.tab.calls")],
60
+ ]) {
61
+ const b = el("button", null, label);
62
+ b.type = "button";
63
+ b.dataset.tab = which;
64
+ b.setAttribute("aria-selected", String(which === LN_TAB));
65
+ b.addEventListener("click", () => select(which));
66
+ strip.append(b);
67
+ }
68
+ out.append(strip, body);
69
+ paint();
70
+ return out;
71
+ }
72
+ );
73
+ };
74
+
75
+ /* ------------------------------------------------------------------ phases -- */
76
+
77
+ function phasesTab(host, lanes) {
78
+ const picker = card(t("lanes.picker.title"));
79
+ picker.append(el("div", "note", t("lanes.picker.note")));
80
+
81
+ const sel = el("select", "text-input");
82
+ sel.setAttribute("aria-label", t("lanes.picker.aria"));
83
+ for (const l of lanes) {
84
+ const o = el("option", null, l.lane + (l.command ? " " + l.command : ""));
85
+ o.value = l.lane;
86
+ if (l.lane === LN_LANE) o.selected = true;
87
+ sel.append(o);
88
+ }
89
+ picker.append(sel);
90
+ host.append(picker);
91
+
92
+ const slot = el("div", "stack");
93
+ host.append(slot);
94
+
95
+ const load = () => {
96
+ slot.replaceChildren(skeleton(3));
97
+ read("/api/lane/phases?lane=" + encodeURIComponent(LN_LANE))
98
+ .then((r) => slot.replaceChildren(phasesCard(r.data)))
99
+ .catch((e) => slot.replaceChildren(failBox(e)));
100
+ };
101
+ sel.addEventListener("change", () => {
102
+ LN_LANE = sel.value;
103
+ load();
104
+ });
105
+ load();
106
+ }
107
+
108
+ function phasesCard(d) {
109
+ const one = (d.lanes || [])[0];
110
+ const c = card(t("lanes.phases.title"));
111
+ if (!one) {
112
+ c.append(el("div", "empty", t("lanes.phases.unknown")));
113
+ return c;
114
+ }
115
+
116
+ // The trace tier and token are the CLI's words for how this lane narrates.
117
+ // They belong at the top because every phase below closes into one of them.
118
+ const meta = el("div", "lane-meta");
119
+ meta.append(el("span", "lane-meta-label", t("lanes.phases.trace")));
120
+ if (one.trace_tier) meta.append(chip(one.trace_tier));
121
+ if (one.trace_token) meta.append(chip("lane token " + one.trace_token, "lane"));
122
+ c.append(meta);
123
+
124
+ const phases = one.phases || [];
125
+ if (!phases.length) {
126
+ // AN EMPTY ANSWER IS AN ANSWER (v0.43.0). Five lanes keep their pipeline in
127
+ // their own spine on purpose, so "no shared phases" is a fact about this
128
+ // lane and not a fetch that failed. Saying nothing here would make the two
129
+ // look identical.
130
+ c.append(el("div", "empty", t("lanes.phases.inSpine")));
131
+ return c;
132
+ }
133
+
134
+ for (const p of phases) {
135
+ const row = el("div", "phase-row");
136
+ row.append(el("span", "phase-ord", String(p.ord)));
137
+
138
+ const mid = el("div", "phase-mid");
139
+ mid.append(el("div", "phase-id", p.id));
140
+ // A row names a FILE or a HEADING, never both and never neither — the
141
+ // manifest rule the two-way lint enforces. Whichever it is, it is a CLI
142
+ // string and is printed verbatim.
143
+ if (p.file) mid.append(el("div", "phase-file", p.file));
144
+ if (p.heading) mid.append(el("div", "phase-file", p.heading));
145
+ if ((p.calls || []).length) {
146
+ const calls = el("div", "phase-calls");
147
+ calls.append(el("span", "phase-calls-label", t("lanes.phases.calls")));
148
+ for (const id of p.calls) calls.append(chip(id, "lane"));
149
+ mid.append(calls);
150
+ }
151
+ row.append(mid);
152
+
153
+ const right = el("div", "phase-right");
154
+ // `when` is one of the CLI's own words (`always`, `on-phase`,
155
+ // `compile-time`, …). It is never softened into "sometimes".
156
+ if (p.when) right.append(chip(p.when, p.when === "always" ? "ok" : null));
157
+ if (p.read) right.append(el("div", "phase-read", p.read));
158
+ if ((p.layers || []).length) right.append(el("div", "phase-layers", p.layers.join(" · ")));
159
+ row.append(right);
160
+
161
+ if (p.optional_when) row.append(el("div", "phase-why", p.optional_when));
162
+ c.append(row);
163
+ }
164
+
165
+ if ((d.phase_files || []).length || (d.layer_set || []).length) {
166
+ const foot = el("div", "note");
167
+ foot.append(document.createTextNode(t("lanes.phases.foot")));
168
+ c.append(foot);
169
+ }
170
+ return c;
171
+ }
172
+
173
+ /* ------------------------------------------------------------------- calls -- */
174
+
175
+ function callsTab(host) {
176
+ const slot = el("div", "stack");
177
+ host.append(slot);
178
+ slot.replaceChildren(skeleton(4));
179
+ read("/api/lane/calls")
180
+ .then((r) => slot.replaceChildren(callsCard(r.data)))
181
+ .catch((e) => slot.replaceChildren(failBox(e)));
182
+ }
183
+
184
+ function callsCard(d) {
185
+ const calls = d.calls || [];
186
+ const c = card(t("lanes.calls.title", { n: calls.length }));
187
+ c.append(el("div", "note", t("lanes.calls.note")));
188
+ if (!calls.length) {
189
+ c.append(el("div", "empty", t("lanes.calls.none")));
190
+ return c;
191
+ }
192
+
193
+ for (const call of calls) {
194
+ // EXPANDS IN PLACE, one row at a time — the Runs-row rule (v0.43.6). A
195
+ // detail box below a list means reading a row and then looking somewhere
196
+ // else for what it said.
197
+ const row = el("div", "call-row");
198
+ const h = el("button", "call-head");
199
+ h.type = "button";
200
+ h.setAttribute("aria-expanded", "false");
201
+ h.append(el("span", "call-caret", "▸"));
202
+ h.append(el("span", "call-cmd", call.cmd));
203
+ if (call.cost) h.append(chip(call.cost, call.cost === "free" ? "ok" : "warn"));
204
+ h.append(el("span", "call-lanes-n", tn((call.lanes || []).length, "lanes.calls.lanes")));
205
+
206
+ const detail = el("div", "call-detail");
207
+ detail.hidden = true;
208
+ // Every string below is the CLI's registry text and is never translated: it
209
+ // names commands, exit codes and reference files by their real ids.
210
+ const rows = [];
211
+ if (call.what) rows.push([t("lanes.calls.what"), call.what]);
212
+ if (call.when) rows.push([t("lanes.calls.when"), call.when]);
213
+ if (call.on_absent) rows.push([t("lanes.calls.onAbsent"), call.on_absent]);
214
+ if (call.never) rows.push([t("lanes.calls.never"), call.never]);
215
+ if (call.canonical) rows.push([t("lanes.calls.canonical"), call.canonical]);
216
+ detail.append(kvList(rows));
217
+
218
+ // The exit codes are a CONTRACT, so they are drawn as a table rather than
219
+ // folded into a sentence — a lane branches on these numbers.
220
+ const exits = call.exits || {};
221
+ const codes = Object.keys(exits);
222
+ if (codes.length) {
223
+ const ex = el("div", "call-exits");
224
+ ex.append(el("span", "call-exits-label", t("lanes.calls.exits")));
225
+ for (const code of codes) ex.append(el("span", "call-exit", code + " = " + exits[code]));
226
+ detail.append(ex);
227
+ }
228
+ if ((call.states || []).length) {
229
+ const st = el("div", "call-exits");
230
+ st.append(el("span", "call-exits-label", t("lanes.calls.states")));
231
+ for (const s of call.states) st.append(chip(s));
232
+ detail.append(st);
233
+ }
234
+ if ((call.lanes || []).length) {
235
+ const ls = el("div", "call-lane-chips");
236
+ ls.append(el("span", "call-exits-label", t("lanes.calls.calledBy")));
237
+ for (const l of call.lanes) ls.append(chip(l, "lane"));
238
+ detail.append(ls);
239
+ }
240
+
241
+ h.addEventListener("click", () => {
242
+ const open = h.getAttribute("aria-expanded") === "true";
243
+ // ONE ROW OPEN AT A TIME, the Runs-row rule again: thirty open rows is
244
+ // the wall this panel replaced.
245
+ for (const other of c.querySelectorAll(".call-head[aria-expanded='true']")) {
246
+ other.setAttribute("aria-expanded", "false");
247
+ other.parentElement.querySelector(".call-detail").hidden = true;
248
+ }
249
+ if (!open) {
250
+ h.setAttribute("aria-expanded", "true");
251
+ detail.hidden = false;
252
+ }
253
+ });
254
+
255
+ row.append(h, detail);
256
+ c.append(row);
257
+ }
258
+ return c;
259
+ }
@@ -54,6 +54,16 @@ const FINDING_ROUTE = {
54
54
  // evicts the old package first), and `upgrade` is an action row on
55
55
  // MAINTENANCE — so that is genuinely the panel that can clear it.
56
56
  "legacy-global-package": { panel: "maintenance", cta: "overview.item.legacyGlobalPackage.cta" },
57
+ // v1.0.0 W5. The rule again: a demoted run is cleared by `orc extra promote`,
58
+ // and Recovery is the Extra tab where every other journal-shaped state is
59
+ // already rendered — so that is genuinely the panel that can clear it.
60
+ "extra-demoted-run": { panel: "extra", cta: "overview.item.extraDemotedRun.cta" },
61
+ // v1.0.0 W16. An INSTALL-FOOTPRINT finding, so it takes the documented
62
+ // default rather than an interesting exception: the thing that clears it is
63
+ // `orc update`, which is an action row on MAINTENANCE. It is spelled out
64
+ // rather than left to DEFAULT_FINDING_ROUTE because the CTA is specific --
65
+ // a reader who sees "lane config drifted" should be told which button.
66
+ "lane-keys-drifted": { panel: "maintenance", cta: "overview.item.laneKeysDrifted.cta" },
57
67
  };
58
68
  const DEFAULT_FINDING_ROUTE = { panel: "maintenance", cta: "overview.item.doctor.cta" };
59
69
  const findingRoute = (id) => FINDING_ROUTE[id] || DEFAULT_FINDING_ROUTE;
@@ -17,8 +17,8 @@
17
17
  // keys stay GREPPABLE: a build of a string key from a fragment is a key no
18
18
  // coverage check can see, and the check is the only thing standing between a
19
19
  // renamed key and a raw dotted string on somebody's screen.
20
- const TIER_LABEL_KEY = { common: "settings.tier.common", fable5: "settings.tier.fable5", advanced: "settings.tier.advanced" };
21
- const TIER_DESC_KEY = { common: "settings.tierDesc.common", fable5: "settings.tierDesc.fable5", advanced: "settings.tierDesc.advanced" };
20
+ const TIER_LABEL_KEY = { common: "settings.tier.common", advanced: "settings.tier.advanced" };
21
+ const TIER_DESC_KEY = { common: "settings.tierDesc.common", advanced: "settings.tierDesc.advanced" };
22
22
  const TIER_LABEL = (tier) => (TIER_LABEL_KEY[tier] ? t(TIER_LABEL_KEY[tier]) : tier);
23
23
  const TIER_DESC = (tier) => (TIER_DESC_KEY[tier] ? t(TIER_DESC_KEY[tier]) : "");
24
24
 
@@ -162,6 +162,12 @@ async function renderSettings(body) {
162
162
  // flips — which is how the precedence rule gets taught rather than described.
163
163
  out.append(ladderCard(d.score_table));
164
164
 
165
+ // WHO DECIDES WHAT (v1.0.0 W16). The ladder above answers one family;
166
+ // this answers all of them, and it is the picture a flat key list cannot
167
+ // draw: a contested setting is not decided by the key you are looking at,
168
+ // it is decided by the highest rank that resolved.
169
+ out.append(ranksCard(d.families, d.families_resolved, d.rank_states));
170
+
165
171
  // 36 keys in three flat lists is a wall, and the answer to "where is the one
166
172
  // I want" was scrolling. Each tier is now its own collapsible card, and the
167
173
  // toolbar filters across ALL of them at once — so finding a key never depends
@@ -169,7 +175,7 @@ async function renderSettings(body) {
169
175
  const tiers = [];
170
176
  out.append(settingsToolbar(d, tiers));
171
177
 
172
- for (const tier of ["common", "fable5", "advanced"]) {
178
+ for (const tier of ["common", "advanced"]) {
173
179
  const keys = d.keys.filter((k) => k.tier === tier);
174
180
  if (!keys.length) continue;
175
181
 
@@ -234,6 +240,32 @@ async function renderSettings(body) {
234
240
  out.append(c);
235
241
  }
236
242
 
243
+ // A key ORC REMOVED, still sitting in the user's file. It is NOT an editable
244
+ // setting and it is NOT a hand-edited override, so it gets its own block —
245
+ // rendering it as either would tell the user a dead line still does
246
+ // something. The CLI computed the row; this only draws it.
247
+ if ((d.retired_keys || []).length) {
248
+ const c = card(t("settings.retired.title"));
249
+ c.append(el("div", "note", t("settings.retired.note")));
250
+ for (const r of d.retired_keys) {
251
+ const row = el("div", "setting shadowed");
252
+ const left = el("div");
253
+ const name = el("div", "setting-name");
254
+ name.append(document.createTextNode(r.key));
255
+ const lock = el("span", "lock");
256
+ lock.append(document.createTextNode("🔒 " + t("settings.retired.badge")));
257
+ name.append(lock);
258
+ left.append(name);
259
+ left.append(el("div", "setting-desc", t("settings.retired.readonly")));
260
+ left.append(el("div", "shadow-why", `${t("settings.retired.removedIn")} ${r.removed_in} — ${r.why}`));
261
+ const right = el("div", "setting-control");
262
+ right.append(el("div", "readonly-value", String(r.value)));
263
+ row.append(left, right);
264
+ c.append(row);
265
+ }
266
+ out.append(c);
267
+ }
268
+
237
269
  // Last block on the panel, and it sticks to the bottom of the viewport once
238
270
  // something is pending: with 36 keys across three tiers, an Apply you have to
239
271
  // go looking for is an Apply that gets forgotten.
@@ -243,6 +275,99 @@ async function renderSettings(body) {
243
275
  body.replaceChildren(out);
244
276
  }
245
277
 
278
+ // WHO DECIDES WHAT (v1.0.0 W16) — the contested families, resolved.
279
+ //
280
+ // THE PANEL DECIDES NOTHING HERE. `families` is the CLI's static registry (which
281
+ // ranks exist, in which order, and the question each family answers) and
282
+ // `families_resolved` is what those ranks actually did against the config on
283
+ // disk right now. Re-deriving a precedence in the browser is the exact drift
284
+ // this panel exists to make impossible — the Flow-stepper rule, applied to
285
+ // config.
286
+ //
287
+ // Only CONTESTED families are drawn. A family with one rank has no precedence
288
+ // to teach, and a card that lists thirty uncontested rows to show two
289
+ // interesting ones is a card nobody reads to the bottom of.
290
+ //
291
+ // Every state word is the CLI's own, from the closed set it publishes as
292
+ // `rank_states` — never a friendlier synonym (`resolved`, `not-read`, `inert`,
293
+ // `demoted`, `absent`, `partly-resolved`). A `why` is CLI prose and is never
294
+ // translated: it names keys, runs, profiles and commands by their real ids.
295
+ const RANK_STATE_KIND = {
296
+ resolved: "ok",
297
+ "partly-resolved": "ok",
298
+ "not-read": null,
299
+ inert: "warn",
300
+ demoted: "warn",
301
+ absent: null,
302
+ };
303
+
304
+ function ranksCard(families, resolved, states) {
305
+ const c = card(t("settings.ranks.title"));
306
+ c.id = "ranks-card";
307
+ c.append(el("div", "note", t("settings.ranks.note")));
308
+
309
+ const names = Object.keys(families || {}).filter((n) => (families[n] || {}).contested);
310
+ if (!names.length) {
311
+ c.append(el("div", "empty", t("settings.ranks.none")));
312
+ return c;
313
+ }
314
+
315
+ for (const name of names) {
316
+ const fam = families[name] || {};
317
+ const res = (resolved || {})[name] || {};
318
+ const block = el("div", "rank-family");
319
+
320
+ const h = el("div", "rank-family-head");
321
+ // The family id is a CLI id. The QUESTION beside it is the CLI's too —
322
+ // it is the whole reason the grouping means anything.
323
+ h.append(el("span", "rank-family-name", name));
324
+ h.append(el("span", "rank-family-q", fam.question || ""));
325
+ block.append(h);
326
+
327
+ // WHAT ANSWERED, stated before the ladder rather than left to be inferred
328
+ // from which row happens to be green. `resolved_by` is null on a terminal
329
+ // rank, which is not a gap: it means nothing above the floor resolved.
330
+ if (res.resolved_at) {
331
+ const by = el("div", "rank-answer");
332
+ by.append(
333
+ document.createTextNode(
334
+ res.resolved_by
335
+ ? t("settings.ranks.answeredBy", { prio: res.resolved_at, key: res.resolved_by })
336
+ : t("settings.ranks.answeredByDefault", { prio: res.resolved_at })
337
+ )
338
+ );
339
+ block.append(by);
340
+ }
341
+
342
+ for (const r of res.ranks || fam.ranks || []) {
343
+ const row = el("div", "rank-row" + (r.state === "resolved" ? " is-resolved" : ""));
344
+ row.append(el("span", "rank-prio", r.prio));
345
+ // A rank is either a KEY or the family's terminal floor. The floor has no
346
+ // key on purpose — it is "the thing that happens when nobody chose" — and
347
+ // rendering it as a key would invite somebody to look for it in the file.
348
+ row.append(el("span", "rank-key" + (r.key ? "" : " is-terminal"), r.key || r.terminal || ""));
349
+ const st = r.state || "absent";
350
+ // An unknown state is RENDERED, not swallowed: the CLI owns this set, and
351
+ // a panel that silently drops a word it does not recognise is a panel that
352
+ // hides the next state somebody adds.
353
+ row.append(chip(st, RANK_STATE_KIND[st] === undefined ? null : RANK_STATE_KIND[st]));
354
+ if (r.why) row.append(el("div", "rank-why", r.why));
355
+ block.append(row);
356
+ }
357
+ c.append(block);
358
+ }
359
+
360
+ // The closed set, once, at the bottom. It is what makes an unfamiliar chip
361
+ // above readable without leaving the page.
362
+ if ((states || []).length) {
363
+ const legend = el("div", "rank-legend");
364
+ legend.append(el("span", "rank-legend-label", t("settings.ranks.legend")));
365
+ for (const s of states) legend.append(chip(s, RANK_STATE_KIND[s] === undefined ? null : RANK_STATE_KIND[s]));
366
+ c.append(legend);
367
+ }
368
+ return c;
369
+ }
370
+
246
371
  function ladderCard(table) {
247
372
  const c = card(t("settings.ladder.title"));
248
373
  c.id = "ladder-card"; // the FLIP morph finds it by id, never by a :has() query
@@ -324,6 +449,27 @@ function settingRow(k, panelBody, edits) {
324
449
  // translated. They name keys, values and precedence rules by their real ids.
325
450
  left.append(el("div", "setting-desc", k.desc));
326
451
  if (k.shadow_reason) left.append(el("div", "shadow-why", k.shadow_reason));
452
+ // WHICH LANES READ THIS (v1.0.0 W16). `k.lanes[]` is the CLI's own
453
+ // `CONFIG_META[].lanes[]` — the same list `orc lane config` filters on, and
454
+ // the same list a two-way lint already refuses to let drift. It answers the
455
+ // question a settings screen otherwise leaves you guessing at: I changed
456
+ // this, so what did I just change? A lane name is a CLI id and is never
457
+ // translated.
458
+ //
459
+ // AN EMPTY LIST IS AN ANSWER AND KEEPS ITS ROW. Ten keys are permanently
460
+ // empty and the lint has an allowlist naming every one of them: they are
461
+ // OPERATING keys of the `orc extra` bridge — a lane calls `orc extra
462
+ // dispatch` and the CLI reads `extra_timeout_s`, so no lane ever names it.
463
+ // Skipping the line would make "no lane reads this" look identical to "we
464
+ // did not render it", which is the distinction this whole row exists to
465
+ // draw.
466
+ {
467
+ const lanes = el("div", "setting-lanes");
468
+ lanes.append(el("span", "setting-lanes-label", t("settings.lanes.label")));
469
+ if ((k.lanes || []).length) for (const lane of k.lanes) lanes.append(chip(lane, "lane"));
470
+ else lanes.append(el("span", "setting-lanes-none", t("settings.lanes.none")));
471
+ left.append(lanes);
472
+ }
327
473
 
328
474
  const right = el("div", "setting-control");
329
475
  right.append(controlFor(k, edits));