create-pmos 0.51.0 → 0.53.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 (212) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +2 -2
  3. package/payload/mods/pmos-cockpit/README.md +13 -0
  4. package/payload/mods/pmos-cockpit/hooks/lib/commands.mjs +2 -1
  5. package/payload/mods/pmos-cockpit/hooks/lib/grants.mjs +41 -11
  6. package/payload/mods/pmos-cockpit/hooks/lib/guard.mjs +228 -8
  7. package/payload/mods/pmos-cockpit/hooks/register.tsx +11 -3
  8. package/payload/planning/evals/eval-calibration.md +1 -1
  9. package/payload/planning/okrs/health-budgets.md +1 -1
  10. package/payload/scripts/plan-items-stage.js +271 -0
  11. package/payload/scripts/pmos-log.sh +18 -7
  12. package/payload/scripts/workflow-state.js +28 -2
  13. package/payload/state/discoveries.md +1 -1
  14. package/payload/state/log.md +1 -1
  15. package/workspace/bin/pmos.js +15 -1
  16. package/workspace/cut-stub.js +40 -0
  17. package/workspace/first-paint-audit.js +145 -0
  18. package/workspace/fixtures/capture/requirements/capture-2026-10-04-1.md +10 -0
  19. package/workspace/fixtures/initiatives-api/list.json +176 -0
  20. package/workspace/fixtures/initiatives-api/post-403.json +5 -0
  21. package/workspace/fixtures/initiatives-api/post-ok.json +10 -0
  22. package/workspace/fixtures/initiatives-api/roadmap.json +208 -0
  23. package/workspace/fixtures/initiatives-api/sheet.json +112 -0
  24. package/workspace/fixtures/report-api/draft.json +177 -0
  25. package/workspace/fixtures/reports-page/draft-biweekly-2026-10-16.md +97 -0
  26. package/workspace/fixtures/today-api/provider-rows.json +204 -0
  27. package/workspace/fixtures/today-api/seam-probe-kinds.json +5 -0
  28. package/workspace/guide/board.md +81 -0
  29. package/workspace/guide/breakdown.md +154 -0
  30. package/workspace/guide/command-palette.md +75 -0
  31. package/workspace/guide/index.md +27 -17
  32. package/workspace/guide/initiatives-roadmap.md +130 -0
  33. package/workspace/guide/initiatives-screen.md +89 -0
  34. package/workspace/guide/initiatives-timeline.md +57 -0
  35. package/workspace/guide/integrated-work.md +94 -0
  36. package/workspace/guide/knowledge-graph.md +3 -68
  37. package/workspace/guide/layout.md +98 -32
  38. package/workspace/guide/live-notes.md +3 -3
  39. package/workspace/guide/pm-only-acts.md +89 -0
  40. package/workspace/guide/pm-only-lines.md +61 -0
  41. package/workspace/guide/reports-page.md +80 -0
  42. package/workspace/guide/reports.md +73 -0
  43. package/workspace/guide/timeline.md +70 -0
  44. package/workspace/guide/today.md +85 -0
  45. package/workspace/guide/validate-with-typesafe.md +1 -1
  46. package/workspace/lib/actor.js +91 -0
  47. package/workspace/lib/board-cache.js +32 -2
  48. package/workspace/lib/board-cli.js +108 -0
  49. package/workspace/lib/board-fold.js +232 -0
  50. package/workspace/lib/breakdown-export.js +80 -0
  51. package/workspace/lib/capture.js +98 -0
  52. package/workspace/lib/engine/plan-items-stage.js +271 -0
  53. package/workspace/lib/engine/workflow-state.js +28 -2
  54. package/workspace/lib/factory-join.js +158 -0
  55. package/workspace/lib/grant-mint.js +113 -0
  56. package/workspace/lib/health-rules-factory.js +92 -0
  57. package/workspace/lib/health-rules-plan.js +164 -0
  58. package/workspace/lib/health.js +79 -0
  59. package/workspace/lib/initiative-cli.js +222 -0
  60. package/workspace/lib/initiative-display.js +183 -0
  61. package/workspace/lib/initiative-needs.js +127 -0
  62. package/workspace/lib/initiative-rows.js +206 -0
  63. package/workspace/lib/initiative-writer.js +141 -0
  64. package/workspace/lib/issues.js +206 -0
  65. package/workspace/lib/item-cli.js +195 -0
  66. package/workspace/lib/item-spans.js +137 -0
  67. package/workspace/lib/measure-cli.js +100 -0
  68. package/workspace/lib/measurements.js +189 -0
  69. package/workspace/lib/mobile-nav.js +57 -53
  70. package/workspace/lib/needs-providers.js +120 -0
  71. package/workspace/lib/placements.js +440 -0
  72. package/workspace/lib/plan-items.js +922 -0
  73. package/workspace/lib/plan-ledgers.js +85 -0
  74. package/workspace/lib/plan-rules.js +72 -0
  75. package/workspace/lib/plan-tree.js +367 -0
  76. package/workspace/lib/pm-guard.js +174 -0
  77. package/workspace/lib/pm-lines-needs.js +331 -0
  78. package/workspace/lib/pmos-verbs.js +66 -0
  79. package/workspace/lib/proactive.js +7 -4
  80. package/workspace/lib/report-cli.js +152 -0
  81. package/workspace/lib/report-draft.js +579 -0
  82. package/workspace/lib/report-needs.js +213 -0
  83. package/workspace/lib/route-modules.js +128 -0
  84. package/workspace/lib/routes-board.js +91 -0
  85. package/workspace/lib/routes-capture.js +52 -0
  86. package/workspace/lib/routes-initiatives.js +371 -0
  87. package/workspace/lib/routes-reports.js +163 -0
  88. package/workspace/lib/routes-timeline.js +197 -0
  89. package/workspace/lib/routes-today.js +32 -0
  90. package/workspace/lib/today-cli.js +99 -0
  91. package/workspace/lib/today.js +333 -0
  92. package/workspace/lib/triage.js +163 -0
  93. package/workspace/lib/verify-work-cli.js +81 -0
  94. package/workspace/lib/work-check-needs.js +99 -0
  95. package/workspace/package-lock.json +1 -1
  96. package/workspace/package.json +5 -5
  97. package/workspace/public/index.html +29 -19
  98. package/workspace/public/pm/app.js +299 -30
  99. package/workspace/public/pm/bd-board.js +113 -0
  100. package/workspace/public/pm/bd-drag.js +59 -0
  101. package/workspace/public/pm/bd-model.js +152 -0
  102. package/workspace/public/pm/bd-timeline.js +233 -0
  103. package/workspace/public/pm/bd-tree.js +244 -0
  104. package/workspace/public/pm/board-copy.js +57 -0
  105. package/workspace/public/pm/breakdown-api.js +58 -0
  106. package/workspace/public/pm/breakdown-copy.js +138 -0
  107. package/workspace/public/pm/components.css +3054 -0
  108. package/workspace/public/pm/cycle-review.js +125 -0
  109. package/workspace/public/pm/data.js +40 -187
  110. package/workspace/public/pm/dest-board.js +438 -0
  111. package/workspace/public/pm/dest-initiatives.js +134 -0
  112. package/workspace/public/pm/dest-reports.js +270 -0
  113. package/workspace/public/pm/dest-settings.js +217 -0
  114. package/workspace/public/pm/dest-today.js +176 -0
  115. package/workspace/public/pm/dialogs.js +88 -0
  116. package/workspace/public/pm/display-word.js +56 -0
  117. package/workspace/public/pm/dock.js +105 -0
  118. package/workspace/public/pm/fixture.js +8 -227
  119. package/workspace/public/pm/frame-copy.js +94 -0
  120. package/workspace/public/pm/handoff-box.js +82 -0
  121. package/workspace/public/pm/http.js +88 -0
  122. package/workspace/public/pm/icons.js +53 -0
  123. package/workspace/public/pm/initiative-list.js +218 -0
  124. package/workspace/public/pm/initiative-sheet.js +261 -0
  125. package/workspace/public/pm/initiatives-api.js +59 -0
  126. package/workspace/public/pm/initiatives-copy.js +215 -0
  127. package/workspace/public/pm/initiatives-timeline-copy.js +59 -0
  128. package/workspace/public/pm/initiatives-timeline.js +410 -0
  129. package/workspace/public/pm/initiatives-ui.js +123 -0
  130. package/workspace/public/pm/item-sheet.js +251 -0
  131. package/workspace/public/pm/keep-card.js +204 -0
  132. package/workspace/public/pm/lifecycle.js +7 -1
  133. package/workspace/public/pm/mobile.css +49 -20
  134. package/workspace/public/pm/mobile.js +157 -29
  135. package/workspace/public/pm/modes-live.js +7 -883
  136. package/workspace/public/pm/modes.js +32 -1024
  137. package/workspace/public/pm/new-initiative-sheet.js +74 -0
  138. package/workspace/public/pm/next-step-ui.js +3 -2
  139. package/workspace/public/pm/palette-copy.js +51 -0
  140. package/workspace/public/pm/palette.js +312 -0
  141. package/workspace/public/pm/panel-breakdown.js +702 -0
  142. package/workspace/public/pm/panel-cockpit.js +63 -295
  143. package/workspace/public/pm/panel-detail.js +25 -172
  144. package/workspace/public/pm/panel-initiatives.js +1 -19
  145. package/workspace/public/pm/panel-repos.js +31 -81
  146. package/workspace/public/pm/panel-tabs.js +197 -110
  147. package/workspace/public/pm/panel-trees.js +5 -2
  148. package/workspace/public/pm/portfolio-report.js +61 -0
  149. package/workspace/public/pm/primitives.js +656 -0
  150. package/workspace/public/pm/print.css +33 -0
  151. package/workspace/public/pm/provider-cards.js +136 -0
  152. package/workspace/public/pm/registry.js +194 -0
  153. package/workspace/public/pm/release-card.js +110 -0
  154. package/workspace/public/pm/report-view.js +309 -0
  155. package/workspace/public/pm/reports-copy.js +114 -0
  156. package/workspace/public/pm/roadmap.js +271 -0
  157. package/workspace/public/pm/sig-watch.js +41 -0
  158. package/workspace/public/pm/state.js +8 -4
  159. package/workspace/public/pm/status-banner.js +13 -5
  160. package/workspace/public/pm/tab-doc.js +2 -4
  161. package/workspace/public/pm/tab-term.js +12 -2
  162. package/workspace/public/pm/tab-web.js +3 -2
  163. package/workspace/public/pm/timeline-copy.js +48 -0
  164. package/workspace/public/pm/today-cards.js +244 -0
  165. package/workspace/public/pm/today-copy.js +97 -0
  166. package/workspace/public/pm/tokens.css +16 -18
  167. package/workspace/public/pm/topbar.js +102 -0
  168. package/workspace/public/pm/triage-card.js +60 -0
  169. package/workspace/public/pm/undo-hold.js +103 -0
  170. package/workspace/public/pm/view-addrepo.js +2 -1
  171. package/workspace/public/pm/view-livenotes.js +3 -1
  172. package/workspace/public/pm/view-today.js +7 -162
  173. package/workspace/public/vendor/Sortable.min.js +2 -0
  174. package/workspace/public/vendor/lucide-sprite.js +79 -0
  175. package/workspace/replaced-tests.json +2864 -0
  176. package/workspace/run-tests.js +117 -6
  177. package/workspace/server.js +312 -36
  178. package/workspace/sheet-answer.js +61 -0
  179. package/workspace/skills/measure.skill +41 -0
  180. package/workspace/skills/report-biweekly.skill +23 -25
  181. package/workspace/skills/tracker-refresh.skill +50 -0
  182. package/workspace/test-files.json +20 -4
  183. package/workspace/test-lists/base.json +6 -0
  184. package/workspace/test-lists/run2-a.json +6 -0
  185. package/workspace/test-lists/run2-b.json +13 -0
  186. package/workspace/test-lists/run2-c.json +19 -0
  187. package/workspace/test-lists/run2-d.json +22 -0
  188. package/workspace/test-lists/run3-0.json +5 -0
  189. package/workspace/test-lists/run3-a.json +5 -0
  190. package/workspace/test-lists/run3-b.json +6 -0
  191. package/workspace/test-lists/run3-c.json +15 -0
  192. package/workspace/test-lists/run3-d.json +6 -0
  193. package/workspace/test-lists/run4-a.json +8 -0
  194. package/workspace/test-lists/run4-b.json +6 -0
  195. package/workspace/test-lists/run4-c.json +13 -0
  196. package/workspace/test-lists/run4-d.json +9 -0
  197. package/workspace/test-lists/run5-a.json +6 -0
  198. package/workspace/test-lists/run5-b.json +6 -0
  199. package/workspace/test-lists/run5-c.json +15 -0
  200. package/workspace/test-run.js +27 -1
  201. package/workspace/third-party/README.md +44 -0
  202. package/workspace/third-party/lucide-static-1.52.0.LICENSE +43 -0
  203. package/workspace/third-party/sortablejs-1.15.7.LICENSE +21 -0
  204. package/workspace/design-parity.js +0 -673
  205. package/workspace/public/pm/tab-task.js +0 -86
  206. package/workspace/public/pm/view-guide.js +0 -141
  207. package/workspace/public/pm/view-knowledge.js +0 -225
  208. package/workspace/public/pm/view-memory.js +0 -122
  209. package/workspace/public/pm/view-portfolio.js +0 -153
  210. package/workspace/public/pm/view-reports.js +0 -190
  211. package/workspace/public/pm/view-scheduled.js +0 -161
  212. package/workspace/public/pm/view-skills.js +0 -216
@@ -1,216 +0,0 @@
1
- /* pm/view-skills.js — the Skills view tab, drawn from planning/design/cockpit-mockup/ViewSkills.dc.html.
2
- *
3
- * PRD workspace-cockpit-nav, Run 3 (R4). The board is two panes. On the left, a 340px column with the title, a switch
4
- * (All, Built-in, Yours, Discovered) and the skills. On the right, the skill that waits for the person: a quarantined
5
- * candidate, its text, and Approve and Reject.
6
- *
7
- * Both panes are the view's existing list (modes-live.js `skillsLive`, or the fixture's skills), drawn once and split: the
8
- * skill rows go left, the discovery block and the candidates go right. No node is removed, so every `data-r4="skill"`,
9
- * `data-r13` and `data-r7` hook and every live behaviour stays as it was: a row still opens its skill as a tab, and a
10
- * candidate is still read, approved, rejected or retired by its own buttons. Only the frame is new.
11
- *
12
- * The switch hides the rows of the other sources. It reads each row's own source label, so it never disagrees with the list.
13
- * The board's "+ New skill" has no form in this app (the server's POST /api/skills is reached from the CLI), so it is not
14
- * drawn. The board's "Always produces" and "Procedure" parts belong to a skill's own tab, which a row opens. In the fixture
15
- * the discovery is not read, and the right pane says so.
16
- */
17
- import { el } from './dom.js';
18
-
19
- let seg = 'all'; // the left switch: 'all' · 'builtin' · 'user' · 'discovered'
20
-
21
- const SEGS = [['all', 'All'], ['builtin', 'Built-in'], ['user', 'Yours'], ['discovered', 'Discovered']];
22
- const BTN = 'font:500 12.5px var(--sans);background:transparent;color:var(--foreground);border:1px solid var(--input);border-radius:8px;padding:0 12px;height:30px;min-height:30px;display:inline-grid;place-items:center;cursor:pointer;flex:none';
23
- const BTNP = 'font:500 12.5px var(--sans);background:var(--foreground);color:var(--background);border:0;border-radius:8px;padding:0 12px;height:30px;min-height:30px;display:inline-grid;place-items:center;cursor:pointer;flex:none';
24
- const BADGE = 'font:500 11px var(--sans);border:1px solid var(--border);border-radius:999px;padding:0 8px;line-height:18px;white-space:nowrap;display:inline-block;flex:none';
25
- const TONE = {
26
- s: ';border-color:transparent;background:color-mix(in oklab, var(--success) 16%, transparent);color:var(--success)',
27
- w: ';border-color:transparent;background:color-mix(in oklab, var(--warning) 16%, transparent);color:var(--warning)',
28
- d: ';border-color:transparent;background:color-mix(in oklab, var(--destructive) 18%, transparent);color:var(--destructive)',
29
- };
30
- const LBL = 'font:500 10px var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--muted-foreground)';
31
- const NOTE = 'color:var(--muted-foreground);font-size:13px;line-height:1.5;padding:0';
32
- const ITEM = 'display:flex;flex-direction:column;gap:2px;width:100%;box-sizing:border-box;text-align:left;padding:8px 12px;min-height:40px;border:0;border-radius:8px;background:transparent;color:var(--foreground);cursor:pointer';
33
- const CARD = 'display:flex;flex-direction:column;gap:8px;width:100%;box-sizing:border-box;text-align:left;border:1px solid var(--border);border-radius:10px;padding:14px 16px;background:var(--background);min-height:0';
34
- const PROC = 'white-space:pre-wrap;overflow-wrap:anywhere;margin:0;border:1px solid var(--border);border-radius:10px;background:var(--card);padding:12px 14px;font:12px/1.6 var(--mono);color:var(--muted-foreground);max-height:320px;overflow:auto';
35
-
36
- const badge = (text, tone, attrs) => el('span', { ...(attrs || {}), style: BADGE + (tone ? TONE[tone] : '') }, String(text));
37
-
38
- /** A row's source, read off the row itself: the live row shows it on the right of its first line. A fixture row has none. */
39
- function sourceOf(row) {
40
- const top = row.children[0];
41
- const src = top && top.children.length > 1 ? (top.children[1].textContent || '').trim() : '';
42
- return src || null;
43
- }
44
-
45
- /** Restyle one skill row in place, as the board's item: the name in bold, the source as a badge, the description below. */
46
- function restyleRow(row) {
47
- const active = /background:var\(--accent\)/.test(row.getAttribute('style') || '');
48
- row.setAttribute('style', ITEM + (active ? ';background:var(--accent)' : ''));
49
- const [top, desc] = row.children;
50
- if (top) {
51
- top.setAttribute('style', 'display:flex;align-items:center;gap:8px;min-width:0');
52
- const [name, src] = top.children.length > 1 ? top.children : [top, null];
53
- if (name && name !== top) name.setAttribute('style', 'font:600 13px var(--sans);color:var(--foreground);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap');
54
- if (name === top) top.setAttribute('style', 'display:block;font:600 13px var(--sans);color:var(--foreground)');
55
- if (src) {
56
- const s = (src.textContent || '').trim();
57
- src.setAttribute('style', BADGE + ';margin-left:auto' + (s === 'discovered' ? TONE.s : ''));
58
- src.setAttribute('data-board-badge', 'source');
59
- if (s === 'discovered') src.setAttribute('title', 'discovered, and approved on this machine');
60
- }
61
- }
62
- if (desc) desc.setAttribute('style', 'display:block;font-size:12.5px;line-height:1.45;color:var(--muted-foreground)');
63
- }
64
-
65
- /** Restyle one candidate as a card: its name and state as the board's title and badge, its text as the procedure box. */
66
- function restyleCand(card) {
67
- card.setAttribute('style', CARD);
68
- card.setAttribute('data-board-card', 'candidate');
69
- const st = card.getAttribute('data-state') || '';
70
- const kids = [...card.children];
71
- const [top] = kids;
72
- if (top) {
73
- top.setAttribute('style', 'display:flex;align-items:center;gap:10px;flex-wrap:wrap');
74
- const [name, state] = top.children;
75
- if (name) name.setAttribute('style', 'font:600 16px var(--sans);color:var(--foreground)');
76
- if (state) {
77
- const t = state.textContent || '';
78
- const tone = /running|approved here/.test(t) ? 's' : st === 'quarantined' ? 'w' : st === 'rejected' ? 'd' : null;
79
- state.setAttribute('style', BADGE + (tone ? TONE[tone] : ''));
80
- state.setAttribute('data-board-badge', 'state');
81
- }
82
- }
83
- for (const k of kids.slice(1)) {
84
- if (k.tagName === 'PRE') { k.setAttribute('style', PROC); continue; }
85
- const btns = k.querySelectorAll('button');
86
- if (btns.length) {
87
- k.setAttribute('style', 'display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:2px');
88
- for (const b of btns) {
89
- const h = b.getAttribute('data-r13');
90
- b.setAttribute('style', (h === 'approve' ? BTNP : BTN) + (b.disabled ? ';opacity:.45;cursor:not-allowed' : ''));
91
- }
92
- continue;
93
- }
94
- const tone = /var\(--destructive\)/.test(k.getAttribute('style') || '') ? ';color:var(--destructive)' : '';
95
- const mono = /var\(--mono\)/.test(k.getAttribute('style') || '');
96
- k.setAttribute('style', 'display:block;' + (mono ? 'font:11px var(--mono);overflow-wrap:anywhere;' : 'font-size:13px;line-height:1.5;') + 'color:var(--muted-foreground)' + tone);
97
- }
98
- }
99
-
100
- /** The right pane's parts, restyled in place. `head` is the list's discovery block, in its own order. */
101
- function restyleReview(nodes) {
102
- for (const n of nodes) {
103
- if (!n || n.nodeType !== 1) continue;
104
- const h = n.getAttribute('data-r13') || n.getAttribute('data-r4') || '';
105
- if (h === 'cand') { restyleCand(n); continue; }
106
- if (h === 'discovery-label' || h === 'cands-label') { n.setAttribute('style', LBL + ';padding:6px 0 0'); continue; }
107
- if (h === 'run-note' || h === 'cand-note') { n.setAttribute('style', NOTE + (/destructive/.test(n.getAttribute('style') || '') ? ';color:var(--destructive)' : '')); continue; }
108
- if (n.querySelector('button')) {
109
- // the discovery actions: "find skills now" with its next run, or the one-press weekly set-up
110
- n.setAttribute('style', 'display:flex;gap:10px;align-items:center;flex-wrap:wrap');
111
- for (const b of n.querySelectorAll('button')) b.setAttribute('style', b.getAttribute('data-r13') === 'discover-now' ? BTNP : BTN);
112
- const seed = n.querySelector('[data-r7]');
113
- if (seed) {
114
- seed.setAttribute('style', 'display:flex;flex-direction:column;gap:6px;align-items:flex-start');
115
- for (const c of seed.children) if (c.tagName === 'SPAN') c.setAttribute('style', 'font-size:12px;color:var(--muted-foreground)');
116
- }
117
- for (const s of n.children) if (s.tagName === 'SPAN') s.setAttribute('style', 'font:11px var(--mono);color:var(--muted-foreground)');
118
- continue;
119
- }
120
- const bad = /var\(--destructive\)/.test(n.getAttribute('style') || '');
121
- n.setAttribute('style', NOTE + (bad ? ';color:var(--destructive)' : ''));
122
- }
123
- }
124
-
125
- export function renderBoard(inner, ctx) {
126
- const { FIXTURE } = ctx;
127
-
128
- // The board fills the tab from edge to edge, as the mockup does: the pane's own padding is dropped for this view only.
129
- const pane = inner.parentElement;
130
- if (pane) { pane.style.padding = '0'; pane.style.overflow = 'hidden'; pane.style.display = 'flex'; pane.style.flexDirection = 'column'; }
131
- inner.style.cssText = 'flex:1 1 auto;min-height:0;display:flex;flex-direction:row;gap:0;max-width:none;height:100%';
132
- inner.setAttribute('data-board', 'skills');
133
-
134
- // the existing list, drawn once, then split between the two panes
135
- const drawn = el('div', null);
136
- ctx.renderMode(drawn);
137
- const wrap = !FIXTURE && drawn.children.length === 1 && !drawn.firstElementChild.hasAttribute('data-r4') ? drawn.firstElementChild : null;
138
- const review = [];
139
- if (wrap) {
140
- const kids = [...wrap.children];
141
- // the list's own label "skills · N · click one to read it" starts the skills; everything above it is the discovery block
142
- let cut = kids.findIndex((k) => !k.hasAttribute('data-r13') && !k.hasAttribute('data-r4') && /^skills · \d+/.test((k.textContent || '').trim()));
143
- const counted = cut >= 0;
144
- if (!counted) cut = kids.length - 1; // a read in progress or failed: its status line stays with the list
145
- for (const k of kids.slice(0, Math.max(0, cut))) review.push(k);
146
- for (const k of review) k.remove();
147
- wrap.setAttribute('style', 'display:flex;flex-direction:column;gap:2px');
148
- const label = kids[cut];
149
- if (counted && label) {
150
- label.setAttribute('data-board-part', 'count');
151
- label.setAttribute('style', LBL + ';padding:6px 12px 4px');
152
- }
153
- }
154
- const rows = [...drawn.querySelectorAll(FIXTURE ? 'button' : '[data-r4="skill"]')];
155
- for (const row of rows) restyleRow(row);
156
- for (const n of wrap ? wrap.children : []) {
157
- if (n.hasAttribute('data-r4') || n.hasAttribute('data-board-part')) continue;
158
- n.setAttribute('style', NOTE + ';padding:6px 12px' + (/destructive/.test(n.getAttribute('style') || '') ? ';color:var(--destructive)' : ''));
159
- }
160
-
161
- // the switch: it hides the rows of the other sources, and never removes one
162
- const waiting = review.filter((n) => n.getAttribute && n.getAttribute('data-r13') === 'cand'
163
- && n.getAttribute('data-state') === 'quarantined' && !/approved here/.test(n.textContent || '')).length;
164
- if (!SEGS.some(([k]) => k === seg)) seg = 'all';
165
- let shown = 0;
166
- for (const row of rows) {
167
- const src = sourceOf(row);
168
- const hide = seg !== 'all' && src !== seg;
169
- row.style.display = hide ? 'none' : '';
170
- if (!hide) shown++;
171
- }
172
- const noMatch = rows.length && !shown ? el('div', { 'data-board-empty': seg, style: NOTE + ';padding:6px 12px' },
173
- seg === 'user' ? 'You have no skills of your own here. Open a skill and make your own copy to edit it.'
174
- : seg === 'discovered' ? 'No discovered skill is approved here yet. Candidates wait on the right.'
175
- : 'No skills of this kind.') : null;
176
- const segBtn = ([k, label]) => {
177
- const on = seg === k;
178
- return el('button', {
179
- 'data-board-seg': k, 'aria-pressed': on ? 'true' : 'false',
180
- onClick: () => { seg = k; ctx.setState({}); },
181
- style: 'flex:1 1 0;text-align:center;border:0;border-radius:6px;padding:3px 0;font:' + (on ? '500' : '400') + ' 12.5px var(--sans);cursor:pointer;'
182
- + (on ? 'background:var(--background);color:var(--foreground)' : 'background:transparent;color:var(--muted-foreground)'),
183
- }, label, k === 'discovered' && waiting ? el('span', { 'data-board-waiting': String(waiting), style: 'color:var(--warning);margin-left:4px' }, String(waiting)) : null);
184
- };
185
- const toolbar = FIXTURE ? null : el('div', { 'data-board-part': 'toolbar', role: 'group', 'aria-label': 'Show skills by source',
186
- style: 'display:flex;gap:2px;background:var(--accent);border-radius:8px;padding:3px' }, SEGS.map(segBtn));
187
-
188
- const left = el('div', { 'data-board-part': 'list',
189
- style: 'width:340px;flex:none;box-sizing:border-box;border-right:1px solid var(--border);background:var(--sidebar);padding:14px;display:flex;flex-direction:column;gap:10px;min-height:0;overflow-y:auto' },
190
- el('div', { 'data-board-part': 'header', style: 'display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:30px' },
191
- el('span', { 'data-board-title': '', style: 'font:600 15px var(--sans);color:var(--foreground)' }, 'Skills')),
192
- toolbar,
193
- el('div', { 'data-board-section': 'skills', style: 'display:flex;flex-direction:column;gap:2px;margin:0 -2px' }, [...drawn.childNodes], noMatch));
194
-
195
- // the right pane: what waits for the person
196
- restyleReview(review);
197
- let right;
198
- if (FIXTURE) {
199
- right = [el('div', { 'data-board-part': 'review-title', style: 'font:600 22px var(--sans);color:var(--foreground)' }, 'Skill discovery'),
200
- el('div', { 'data-board-part': 'note', style: NOTE }, 'Skill discovery is not read in the fixture. Choose a skill on the left to see it in the detail.')];
201
- } else {
202
- right = [
203
- el('div', { style: 'display:flex;align-items:center;gap:10px;flex-wrap:wrap' },
204
- el('span', { 'data-board-part': 'review-title', style: 'font:600 22px var(--sans);color:var(--foreground)' }, 'Skill discovery'),
205
- waiting ? badge(waiting + (waiting === 1 ? ' waits for you' : ' wait for you'), 'w', { 'data-board-badge': 'waiting' }) : null),
206
- el('div', { 'data-board-section': 'review', style: 'display:flex;flex-direction:column;gap:12px' }, review),
207
- review.length ? el('div', { 'data-board-part': 'note', style: NOTE + ';font-size:12px' },
208
- 'Approval is a press on this machine, after you read the full text. It applies to this machine only.') : null,
209
- ];
210
- }
211
- const rightPane = el('div', { 'data-board-part': 'detail',
212
- style: 'flex:1 1 0;min-width:0;box-sizing:border-box;padding:22px 28px;display:flex;flex-direction:column;gap:12px;overflow-y:auto;background:var(--card);color:var(--foreground);font-size:13px;line-height:1.45' }, right);
213
-
214
- inner.appendChild(left);
215
- inner.appendChild(rightPane);
216
- }