rcf-lite 0.28.2 → 0.29.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 (327) hide show
  1. package/CHANGELOG.md +317 -0
  2. package/bin/rcf.js +8 -0
  3. package/fixtures/canary-manifest.json +9 -9
  4. package/guidance/define-draft-shapes.md +89 -0
  5. package/guidance/define-intake.md +97 -0
  6. package/guidance/define-intent.md +107 -0
  7. package/guidance/discover-prototype.md +67 -0
  8. package/guidance/discovery-prototypes.md +69 -0
  9. package/guidance/harness-template.md +34 -0
  10. package/guidance/managed/agent-instructions-block.hash +1 -1
  11. package/guidance/managed/agent-instructions-block.md +34 -0
  12. package/guidance/manifest.json +9 -2
  13. package/package.json +1 -1
  14. package/rcf/adrs/adr-4120-canonical-json-hashing.json +30 -0
  15. package/rcf/adrs/adr-4121-define-sidecars.json +30 -0
  16. package/rcf/adrs/adr-4122-define-gates-blocking-and-optout.json +30 -0
  17. package/rcf/adrs/adr-4123-readiness-single-object.json +30 -0
  18. package/rcf/adrs/adr-4124-define-freeze-verb.json +30 -0
  19. package/rcf/adrs/adr-4126-persona-tagged-blockers-two-levels.json +40 -0
  20. package/rcf/adrs/adr-4127-readiness-tab-order-and-register.json +40 -0
  21. package/rcf/adrs/adr-4129-questions-harness-driven-loop.json +25 -0
  22. package/rcf/adrs/adr-4130-intake-harness-split-tool-scanned.json +20 -0
  23. package/rcf/adrs/adr-4132-prototypes-in-discovery-as-evidence.json +20 -0
  24. package/rcf/adrs/adr-4133-dag-dependency-depth-layout.json +40 -0
  25. package/rcf/adrs/adr-4134-index-json-route.json +35 -0
  26. package/rcf/adrs/adr-4135-readiness-po-register-and-adapter.json +35 -0
  27. package/rcf/adrs/adr-4136-embedding-host-fixture.json +35 -0
  28. package/rcf/code-nodes/cn-700.json +23 -0
  29. package/rcf/code-nodes/cn-701.json +14 -0
  30. package/rcf/code-nodes/cn-702.json +14 -0
  31. package/rcf/code-nodes/cn-703.json +16 -0
  32. package/rcf/code-nodes/cn-720.json +15 -0
  33. package/rcf/code-nodes/cn-721.json +15 -0
  34. package/rcf/code-nodes/cn-722.json +15 -0
  35. package/rcf/code-nodes/cn-723.json +18 -0
  36. package/rcf/code-nodes/cn-724.json +14 -0
  37. package/rcf/code-nodes/cn-725.json +37 -0
  38. package/rcf/code-nodes/cn-726.json +25 -0
  39. package/rcf/code-nodes/cn-727.json +23 -0
  40. package/rcf/code-nodes/cn-728.json +37 -0
  41. package/rcf/code-nodes/cn-729.json +26 -0
  42. package/rcf/code-nodes/cn-730.json +24 -0
  43. package/rcf/code-nodes/cn-731.json +20 -0
  44. package/rcf/code-nodes/cn-732.json +14 -0
  45. package/rcf/code-nodes/cn-733.json +14 -0
  46. package/rcf/code-nodes/cn-734.json +14 -0
  47. package/rcf/code-nodes/cn-735.json +20 -0
  48. package/rcf/code-nodes/cn-736.json +14 -0
  49. package/rcf/code-nodes/cn-737.json +14 -0
  50. package/rcf/code-nodes/cn-738.json +14 -0
  51. package/rcf/code-nodes/cn-739.json +18 -0
  52. package/rcf/code-nodes/cn-740.json +19 -0
  53. package/rcf/code-nodes/cn-741.json +16 -0
  54. package/rcf/code-nodes/cn-742.json +16 -0
  55. package/rcf/code-nodes/cn-743.json +15 -0
  56. package/rcf/code-nodes/cn-744.json +22 -0
  57. package/rcf/code-nodes/cn-745.json +16 -0
  58. package/rcf/code-nodes/cn-746.json +14 -0
  59. package/rcf/code-nodes/cn-747.json +18 -0
  60. package/rcf/code-nodes/cn-748.json +16 -0
  61. package/rcf/code-nodes/cn-749.json +16 -0
  62. package/rcf/code-nodes/cn-750.json +15 -0
  63. package/rcf/code-nodes/cn-751.json +18 -0
  64. package/rcf/code-nodes/cn-752.json +14 -0
  65. package/rcf/fbs/fbs-190.json +37 -0
  66. package/rcf/fbs/fbs-191.json +34 -0
  67. package/rcf/fbs/fbs-192.json +17 -0
  68. package/rcf/fbs/fbs-193.json +37 -0
  69. package/rcf/fbs/fbs-194.json +35 -0
  70. package/rcf/fbs/fbs-195.json +37 -0
  71. package/rcf/fbs/fbs-196.json +45 -0
  72. package/rcf/fbs/fbs-197.json +41 -0
  73. package/rcf/fbs/fbs-198.json +42 -0
  74. package/rcf/fbs/fbs-199.json +59 -0
  75. package/rcf/fbs/fbs-200.json +46 -0
  76. package/rcf/fbs/fbs-201.json +49 -0
  77. package/rcf/fbs/fbs-202.json +110 -0
  78. package/rcf/fbs/fbs-203.json +100 -0
  79. package/rcf/requirements/req-001.json +2 -1
  80. package/rcf/requirements/req-002.json +3 -2
  81. package/rcf/requirements/req-003.json +2 -1
  82. package/rcf/requirements/req-004.json +2 -1
  83. package/rcf/requirements/req-005.json +2 -1
  84. package/rcf/requirements/req-006.json +2 -1
  85. package/rcf/requirements/req-007.json +2 -1
  86. package/rcf/requirements/req-008.json +2 -1
  87. package/rcf/requirements/req-009.json +2 -1
  88. package/rcf/requirements/req-010.json +5 -2
  89. package/rcf/requirements/req-011.json +2 -1
  90. package/rcf/requirements/req-012.json +2 -1
  91. package/rcf/requirements/req-013.json +2 -1
  92. package/rcf/requirements/req-014.json +2 -1
  93. package/rcf/requirements/req-015.json +2 -1
  94. package/rcf/requirements/req-016.json +5 -2
  95. package/rcf/requirements/req-017.json +6 -2
  96. package/rcf/requirements/req-018.json +5 -2
  97. package/rcf/requirements/req-019.json +5 -2
  98. package/rcf/requirements/req-020.json +5 -2
  99. package/rcf/requirements/req-021.json +5 -2
  100. package/rcf/requirements/req-022.json +5 -2
  101. package/rcf/requirements/req-023.json +6 -4
  102. package/rcf/requirements/req-024.json +5 -2
  103. package/rcf/requirements/req-025.json +5 -2
  104. package/rcf/requirements/req-026.json +5 -2
  105. package/rcf/requirements/req-027.json +5 -2
  106. package/rcf/requirements/req-028.json +5 -2
  107. package/rcf/requirements/req-029.json +5 -2
  108. package/rcf/requirements/req-030.json +5 -2
  109. package/rcf/requirements/req-040.json +5 -2
  110. package/rcf/requirements/req-041.json +5 -2
  111. package/rcf/requirements/req-042.json +5 -2
  112. package/rcf/requirements/req-043.json +5 -2
  113. package/rcf/requirements/req-050.json +5 -2
  114. package/rcf/requirements/req-051.json +5 -2
  115. package/rcf/requirements/req-052.json +5 -2
  116. package/rcf/requirements/req-053.json +5 -2
  117. package/rcf/requirements/req-054.json +5 -2
  118. package/rcf/requirements/req-060.json +5 -2
  119. package/rcf/requirements/req-061.json +5 -2
  120. package/rcf/requirements/req-062.json +5 -2
  121. package/rcf/requirements/req-063.json +5 -2
  122. package/rcf/requirements/req-070.json +5 -2
  123. package/rcf/requirements/req-071.json +5 -2
  124. package/rcf/requirements/req-072.json +5 -2
  125. package/rcf/requirements/req-073.json +5 -2
  126. package/rcf/requirements/req-074.json +5 -2
  127. package/rcf/requirements/req-075.json +5 -2
  128. package/rcf/requirements/req-076.json +5 -2
  129. package/rcf/requirements/req-077.json +5 -2
  130. package/rcf/requirements/req-080.json +5 -2
  131. package/rcf/requirements/req-081.json +5 -2
  132. package/rcf/requirements/req-082.json +5 -2
  133. package/rcf/requirements/req-083.json +5 -2
  134. package/rcf/requirements/req-084.json +5 -2
  135. package/rcf/requirements/req-085.json +5 -2
  136. package/rcf/requirements/req-086.json +5 -2
  137. package/rcf/requirements/req-090.json +5 -2
  138. package/rcf/requirements/req-091.json +5 -2
  139. package/rcf/requirements/req-092.json +5 -2
  140. package/rcf/requirements/req-093.json +5 -2
  141. package/rcf/requirements/req-094.json +5 -2
  142. package/rcf/requirements/req-100.json +5 -2
  143. package/rcf/requirements/req-101.json +5 -2
  144. package/rcf/requirements/req-102.json +5 -2
  145. package/rcf/requirements/req-103.json +5 -2
  146. package/rcf/requirements/req-104.json +5 -2
  147. package/rcf/requirements/req-110.json +5 -2
  148. package/rcf/requirements/req-111.json +5 -2
  149. package/rcf/requirements/req-112.json +5 -2
  150. package/rcf/requirements/req-113.json +5 -2
  151. package/rcf/requirements/req-114.json +5 -2
  152. package/rcf/requirements/req-115.json +5 -2
  153. package/rcf/requirements/req-120.json +5 -2
  154. package/rcf/requirements/req-121.json +5 -2
  155. package/rcf/requirements/req-122.json +5 -2
  156. package/rcf/requirements/req-123.json +5 -2
  157. package/rcf/requirements/req-124.json +5 -2
  158. package/rcf/requirements/req-125.json +5 -2
  159. package/rcf/requirements/req-130.json +6 -2
  160. package/rcf/requirements/req-131.json +6 -2
  161. package/rcf/requirements/req-132.json +6 -2
  162. package/rcf/requirements/req-133.json +6 -2
  163. package/rcf/requirements/req-134.json +6 -2
  164. package/rcf/requirements/req-140.json +5 -2
  165. package/rcf/requirements/req-145.json +5 -2
  166. package/rcf/requirements/req-150.json +6 -2
  167. package/rcf/requirements/req-151.json +6 -2
  168. package/rcf/requirements/req-152.json +6 -2
  169. package/rcf/requirements/req-153.json +6 -2
  170. package/rcf/requirements/req-154.json +5 -2
  171. package/rcf/requirements/req-155.json +4 -1
  172. package/rcf/requirements/req-156.json +4 -1
  173. package/rcf/requirements/req-157.json +4 -1
  174. package/rcf/requirements/req-158.json +4 -1
  175. package/rcf/requirements/req-159.json +4 -1
  176. package/rcf/requirements/req-160.json +4 -1
  177. package/rcf/requirements/req-161.json +4 -1
  178. package/rcf/requirements/req-162.json +4 -1
  179. package/rcf/requirements/req-163.json +5 -2
  180. package/rcf/requirements/req-164.json +5 -2
  181. package/rcf/requirements/req-165.json +5 -2
  182. package/rcf/requirements/req-170.json +33 -0
  183. package/rcf/requirements/req-171.json +44 -0
  184. package/rcf/requirements/req-172.json +25 -0
  185. package/rcf/requirements/req-173.json +25 -0
  186. package/rcf/requirements/req-174.json +31 -0
  187. package/rcf/requirements/req-175.json +31 -0
  188. package/rcf/requirements/req-176.json +25 -0
  189. package/rcf/requirements/req-180.json +31 -0
  190. package/rcf/requirements/req-181.json +31 -0
  191. package/rcf/requirements/req-182.json +25 -0
  192. package/rcf/requirements/req-186.json +25 -0
  193. package/rcf/requirements/req-187.json +31 -0
  194. package/rcf/requirements/req-188.json +25 -0
  195. package/rcf/tacs/tac-4120-define-delta-and-freeze.json +21 -0
  196. package/rcf/tacs/tac-4121-define-ledgers.json +35 -0
  197. package/rcf/tacs/tac-4122-define-gates.json +61 -0
  198. package/rcf/tacs/tac-4123-define-readiness.json +61 -0
  199. package/rcf/tacs/tac-4124-define-freeze.json +28 -0
  200. package/rcf/tacs/tac-4127-readiness-view.json +49 -0
  201. package/rcf/tacs/tac-4128-define-guidance.json +53 -0
  202. package/rcf/tacs/tac-4129-define-questions.json +45 -0
  203. package/rcf/tacs/tac-4130-define-intake-brief.json +35 -0
  204. package/rcf/tacs/tac-4131-build-dag-view.json +39 -0
  205. package/rcf/tacs/tac-4132-id-lookup.json +42 -0
  206. package/rcf/tacs/tac-4133-readiness-po-layer.json +37 -0
  207. package/rcf/tacs/tac-4134-wespa-host-fixture.json +54 -0
  208. package/rcf/test-suites/ts-016.json +3 -3
  209. package/rcf/test-suites/ts-204.json +30 -0
  210. package/rcf/test-suites/ts-205.json +22 -0
  211. package/rcf/test-suites/ts-206.json +22 -0
  212. package/rcf/test-suites/ts-207.json +22 -0
  213. package/rcf/test-suites/ts-208.json +22 -0
  214. package/rcf/test-suites/ts-209.json +22 -0
  215. package/rcf/test-suites/ts-210.json +22 -0
  216. package/rcf/test-suites/ts-211.json +22 -0
  217. package/rcf/test-suites/ts-212.json +22 -0
  218. package/rcf/test-suites/ts-213.json +50 -0
  219. package/rcf/test-suites/ts-214.json +74 -0
  220. package/rcf/test-suites/ts-215.json +211 -0
  221. package/rcf/test-suites/ts-216.json +94 -0
  222. package/rcf/test-suites/ts-217.json +62 -0
  223. package/rcf/test-suites/ts-218.json +78 -0
  224. package/rcf/test-suites/ts-219.json +70 -0
  225. package/rcf/test-suites/ts-220.json +78 -0
  226. package/rcf/test-suites/ts-221.json +78 -0
  227. package/rcf/test-suites/ts-222.json +100 -0
  228. package/rcf/test-suites/ts-223.json +94 -0
  229. package/rcf/test-suites/ts-224.json +70 -0
  230. package/rcf/test-suites/ts-225.json +78 -0
  231. package/rcf/test-suites/ts-226.json +158 -0
  232. package/rcf/test-suites/ts-227.json +38 -0
  233. package/rcf/test-suites/ts-228.json +86 -0
  234. package/rcf/test-suites/ts-229.json +83 -0
  235. package/rcf/user-stories/us-15601.json +4 -4
  236. package/rcf/user-stories/us-17001.json +27 -0
  237. package/rcf/user-stories/us-17002.json +22 -0
  238. package/rcf/user-stories/us-17003.json +22 -0
  239. package/rcf/user-stories/us-17004.json +22 -0
  240. package/rcf/user-stories/us-17005.json +22 -0
  241. package/rcf/user-stories/us-17006.json +21 -0
  242. package/rcf/user-stories/us-17007.json +21 -0
  243. package/rcf/user-stories/us-17008.json +22 -0
  244. package/rcf/user-stories/us-17101.json +22 -0
  245. package/rcf/user-stories/us-17102.json +22 -0
  246. package/rcf/user-stories/us-17201.json +38 -0
  247. package/rcf/user-stories/us-17301.json +38 -0
  248. package/rcf/user-stories/us-17302.json +50 -0
  249. package/rcf/user-stories/us-17401.json +70 -0
  250. package/rcf/user-stories/us-17402.json +55 -0
  251. package/rcf/user-stories/us-17403.json +60 -0
  252. package/rcf/user-stories/us-17501.json +48 -0
  253. package/rcf/user-stories/us-17502.json +60 -0
  254. package/rcf/user-stories/us-17503.json +65 -0
  255. package/rcf/user-stories/us-17601.json +58 -0
  256. package/rcf/user-stories/us-18001.json +55 -0
  257. package/rcf/user-stories/us-18002.json +35 -0
  258. package/rcf/user-stories/us-18101.json +35 -0
  259. package/rcf/user-stories/us-18202.json +40 -0
  260. package/rcf/user-stories/us-18601.json +55 -0
  261. package/rcf/user-stories/us-18602.json +50 -0
  262. package/rcf/user-stories/us-18603.json +45 -0
  263. package/rcf/user-stories/us-18701.json +50 -0
  264. package/rcf/user-stories/us-18702.json +50 -0
  265. package/rcf/user-stories/us-18801.json +45 -0
  266. package/rcf/user-stories/us-204.json +59 -0
  267. package/rcf/user-stories/us-205.json +64 -0
  268. package/rcf/user-stories/us-206.json +54 -0
  269. package/releases/releases.yaml +31 -1
  270. package/src/cli/feedback.js +126 -10
  271. package/src/cli/freeze.js +730 -0
  272. package/src/cli/help.js +23 -0
  273. package/src/cli/ledger.js +615 -0
  274. package/src/cli/questions.js +288 -0
  275. package/src/cli/readiness.js +498 -0
  276. package/src/cli/view.js +29 -0
  277. package/src/core/store/writer.js +86 -6
  278. package/src/define/freeze-record.js +247 -0
  279. package/src/define/ledgers.js +712 -0
  280. package/src/feedback/redact.js +105 -19
  281. package/src/intake/index.js +1 -1
  282. package/src/intake/orchestrator.js +117 -0
  283. package/src/mcp/tools.js +354 -0
  284. package/src/query/delta.js +282 -0
  285. package/src/query/gates.js +1107 -0
  286. package/src/query/index.js +50 -0
  287. package/src/query/questions.js +438 -0
  288. package/src/query/readiness.js +574 -0
  289. package/src/server/index.js +60 -1
  290. package/src/server/routes.js +133 -4
  291. package/src/setup/identity-seed.js +17 -0
  292. package/src/view/build-dag.js +563 -0
  293. package/src/view/components/badge.js +41 -0
  294. package/src/view/components/diff.js +72 -0
  295. package/src/view/components/doc-row.js +62 -0
  296. package/src/view/components/empty-state.js +35 -0
  297. package/src/view/components/entity-selector.js +137 -0
  298. package/src/view/components/filter-bar.js +97 -0
  299. package/src/view/components/findings-list.js +49 -0
  300. package/src/view/components/index.js +12 -0
  301. package/src/view/components/pill.js +46 -0
  302. package/src/view/components/sub-tab-strip.js +43 -0
  303. package/src/view/components/toast.js +25 -0
  304. package/src/view/components-fixture.js +265 -0
  305. package/src/view/doc-renderers/architecture.js +304 -0
  306. package/src/view/doc-renderers/build.js +160 -0
  307. package/src/view/doc-renderers/helpers.js +2 -2
  308. package/src/view/doc-renderers/index.js +2 -0
  309. package/src/view/doc-renderers/prd.js +35 -6
  310. package/src/view/doc-renderers/req.js +10 -2
  311. package/src/view/doc-renderers/user-story.js +10 -5
  312. package/src/view/html-page.js +717 -300
  313. package/src/view/index.js +109 -3
  314. package/src/view/live-client.js +61 -8
  315. package/src/view/needs-work.js +79 -0
  316. package/src/view/page-init.js +2193 -0
  317. package/src/view/product-map.js +751 -0
  318. package/src/view/readiness/phrasebook.js +115 -0
  319. package/src/view/readiness/po-layer.js +288 -0
  320. package/src/view/readiness/question-adapter.js +329 -0
  321. package/src/view/readiness/stage-legend.js +71 -0
  322. package/src/view/readiness.js +505 -0
  323. package/src/view/style.css +2279 -266
  324. package/src/view/test-host/embed-client.js +121 -0
  325. package/src/view/test-host/fixture.js +183 -0
  326. package/src/view/test-host/lints.js +220 -0
  327. package/src/view/view-index.js +323 -0
@@ -0,0 +1,62 @@
1
+ // Shared DocRow collapsible (viewer UI refresh PR 2, decision 2 / w-
2
+ // 2026-10-02-dave-010): one markup shape for every per-document row on
3
+ // every list tab (REQ, US, TAC, ADR, FBS). Closed by default, hidden-
4
+ // by-filter via `[hidden]`, deep-link-navigable via `data-doc-id` and
5
+ // the raw document id as the anchor.
6
+ //
7
+ // Shape:
8
+ // <details class="rcf-row doc-row {className}" data-doc-id="{id}" [open]>
9
+ // <summary>
10
+ // <span class="id">{id}</span>
11
+ // <span class="title">{title}</span>
12
+ // <span class="meta">{meta badges / pill}</span>
13
+ // </summary>
14
+ // <div class="rcf-row-body body">
15
+ // {body}
16
+ // </div>
17
+ // </details>
18
+ //
19
+ // The .doc-row prefix lets later tab-side migrations (PR 3-6) swap
20
+ // `detailsWrap` for `renderDocRow` while leaving the existing CSS
21
+ // selectors (`details.doc-details`) alone until each tab lands. PR 2
22
+ // ships the markup and the matching .rcf-row stylesheet rules; no tab
23
+ // is cut over here. The fixture page (/_fixtures/components) is where
24
+ // the component is exercised in isolation.
25
+
26
+ import { escapeHtml } from '../doc-renderers/helpers.js';
27
+
28
+ /**
29
+ * @param {object} args
30
+ * @param {string} args.id display doc id (e.g. "REQ-002")
31
+ * @param {string} args.title one-line label (right of the id)
32
+ * @param {string} [args.body] rendered body HTML (empty for a header-only row)
33
+ * @param {string} [args.className] extra class applied to the <details>
34
+ * @param {boolean} [args.open] initial open state (default: closed)
35
+ * @param {string} [args.dataDocId] override the id used for `data-doc-id` (defaults to args.id)
36
+ * @param {string} [args.meta] pre-rendered meta HTML (badges + pill) shown right of the title
37
+ * @returns {string}
38
+ */
39
+ export function renderDocRow({
40
+ id,
41
+ title,
42
+ body = '',
43
+ className = '',
44
+ open = false,
45
+ dataDocId,
46
+ meta = '',
47
+ }) {
48
+ const classes = ['rcf-row', 'doc-row'];
49
+ if (className) classes.push(String(className));
50
+ const openAttr = open ? ' open' : '';
51
+ const anchorId = dataDocId ?? id;
52
+ return `<details class="${classes.join(' ')}" data-doc-id="${escapeHtml(anchorId)}"${openAttr}>
53
+ <summary>
54
+ <span class="id">${escapeHtml(id)}</span>
55
+ <span class="title">${escapeHtml(title ?? '')}</span>
56
+ ${meta ? `<span class="meta">${meta}</span>` : ''}
57
+ </summary>
58
+ <div class="rcf-row-body body">
59
+ ${body}
60
+ </div>
61
+ </details>`;
62
+ }
@@ -0,0 +1,35 @@
1
+ // Shared EmptyState (viewer UI refresh PR 2, design doc section 4):
2
+ // one dashed-outline, muted-text block for "nothing here yet" cases
3
+ // across every tab (Architecture section without content, a Needs-work
4
+ // table with no rows, a filter that excludes every item, etc).
5
+ //
6
+ // Shape:
7
+ // <div class="rcf-empty-state empty" role="status">
8
+ // <p class="rcf-empty-state-title">{title}</p>
9
+ // [<p class="rcf-empty-state-hint">{hint}</p>]
10
+ // [<p class="rcf-empty-state-action">{actionHtml}</p>]
11
+ // </div>
12
+ //
13
+ // `hint` is plain text; `actionHtml` is an escape hatch for the one
14
+ // case the design calls out (an authoring CLI call under a missing TAD
15
+ // section) and must already be safe HTML - callers that pass raw user
16
+ // text should use `hint` instead.
17
+
18
+ import { escapeHtml } from '../doc-renderers/helpers.js';
19
+
20
+ /**
21
+ * @param {object} args
22
+ * @param {string} args.title
23
+ * @param {string} [args.hint]
24
+ * @param {string} [args.actionHtml]
25
+ * @returns {string}
26
+ */
27
+ export function renderEmptyState({ title, hint, actionHtml }) {
28
+ const hintBlock = hint ? `<p class="rcf-empty-state-hint">${escapeHtml(hint)}</p>` : '';
29
+ const actionBlock = actionHtml ? `<p class="rcf-empty-state-action">${actionHtml}</p>` : '';
30
+ return `<div class="rcf-empty-state empty" role="status">
31
+ <p class="rcf-empty-state-title">${escapeHtml(title)}</p>
32
+ ${hintBlock}
33
+ ${actionBlock}
34
+ </div>`;
35
+ }
@@ -0,0 +1,137 @@
1
+ // Shared EntitySelector (viewer UI refresh PR 3, decision 4): replaces
2
+ // the PRD's 109 inline requirement links with a compact header showing
3
+ // the total, a type-ahead jump input, and a row of area chips with
4
+ // counts that deep-link into the Requirements tab filtered by domain.
5
+ // Pure HTML string helper; the type-ahead behaviour (filter the list,
6
+ // Enter to jump) is wired by page-init.js.
7
+ //
8
+ // Markup shape:
9
+ //
10
+ // <div class="rcf-entity-selector" data-rcf-entity-selector="requirements"
11
+ // data-tab="requirements">
12
+ // <div class="rcf-entity-selector-head">
13
+ // <span class="rcf-entity-selector-total">109 requirements in 21 areas</span>
14
+ // </div>
15
+ // <div class="rcf-entity-selector-jump">
16
+ // <input class="rcf-entity-selector-input" type="search"
17
+ // placeholder="..." autocomplete="off"
18
+ // aria-label="Jump to a requirement by id or title">
19
+ // <div class="rcf-entity-selector-results" hidden></div>
20
+ // </div>
21
+ // <div class="rcf-entity-selector-facets" aria-label="Areas">
22
+ // <a class="rcf-entity-selector-chip" href="#tab=requirements&domain=platform"
23
+ // data-facet-key="domain" data-facet-value="platform">
24
+ // platform <span class="rcf-badge rcf-badge--count"><span class="rcf-badge-value">17</span></span>
25
+ // </a>
26
+ // ...
27
+ // </div>
28
+ // </div>
29
+ //
30
+ // The payload (every entity's id, title, facet value) is serialised
31
+ // onto the root `<div>` as a `data-items` attribute (JSON, HTML-escaped)
32
+ // so the jump input can filter without a network call. The container
33
+ // carries all state so the shell page has no inline `<script>` body
34
+ // (PR 1 contract, decision 10).
35
+ // Decision 11 names `#entity=<id>` as the deep link target so a jump
36
+ // writes `#tab=<targetTab>&entity=<id>` (preserving the host query via
37
+ // the existing urlWithHash helper).
38
+
39
+ import { escapeHtml } from '../doc-renderers/helpers.js';
40
+ import { renderBadge } from './badge.js';
41
+
42
+ /**
43
+ * @typedef {object} EntitySelectorItem
44
+ * @property {string} id document id (e.g. "REQ-002")
45
+ * @property {string} title display title
46
+ * @property {string} [facet] facet value (e.g. "platform")
47
+ */
48
+
49
+ /**
50
+ * @typedef {object} EntitySelectorFacet
51
+ * @property {string} value facet value (e.g. "platform")
52
+ * @property {number} count count of items in this facet
53
+ * @property {string} [label] optional display label (defaults to value)
54
+ */
55
+
56
+ /**
57
+ * @param {object} args
58
+ * @param {string} args.hashKey selector slot name (e.g. "requirements")
59
+ * @param {string} args.targetTab tab name used in hash writes (e.g. "requirements")
60
+ * @param {string} args.facetKey hash key for area chips (e.g. "domain")
61
+ * @param {EntitySelectorItem[]} args.items every entity (for the jump input)
62
+ * @param {EntitySelectorFacet[]} [args.facets] ordered facet chips (if omitted, derived from items)
63
+ * @param {string} [args.totalLabel] pre-rendered total line (e.g. "109 requirements in 21 areas")
64
+ * @param {string} [args.placeholder] input placeholder
65
+ * @param {string} [args.chipsLabel] muted lead-in above the chip row
66
+ * @returns {string}
67
+ */
68
+ export function renderEntitySelector({
69
+ hashKey,
70
+ targetTab,
71
+ facetKey,
72
+ items,
73
+ facets,
74
+ totalLabel,
75
+ placeholder = 'Jump to an entity by id or title',
76
+ chipsLabel = 'By area (click to open the tab filtered):',
77
+ }) {
78
+ const safeItems = Array.isArray(items) ? items : [];
79
+ const resolvedFacets = Array.isArray(facets) && facets.length > 0
80
+ ? facets
81
+ : deriveFacets(safeItems);
82
+ const total = totalLabel
83
+ ?? `${safeItems.length} ${safeItems.length === 1 ? 'item' : 'items'} in ${resolvedFacets.length} ${resolvedFacets.length === 1 ? 'area' : 'areas'}`;
84
+
85
+ const chipBlocks = resolvedFacets.map((f) => {
86
+ const label = f.label ?? f.value;
87
+ const hash = `#tab=${encodeURIComponent(targetTab)}&${encodeURIComponent(facetKey)}=${encodeURIComponent(f.value)}`;
88
+ const count = renderBadge({ value: f.count, variant: 'count' });
89
+ return `<a class="rcf-entity-selector-chip" href="${escapeHtml(hash)}" data-facet-key="${escapeHtml(facetKey)}" data-facet-value="${escapeHtml(f.value)}"><span class="rcf-entity-selector-chip-label">${escapeHtml(label)}</span> ${count}</a>`;
90
+ }).join('\n ');
91
+
92
+ const payload = JSON.stringify(safeItems.map((it) => ({
93
+ id: it.id,
94
+ title: it.title ?? '',
95
+ facet: it.facet ?? '',
96
+ })));
97
+ // The payload rides on the container as a `data-items` attribute.
98
+ // escapeHtml encodes `&`, `<`, `>`, `"` and `'` so the attribute
99
+ // boundary cannot be broken by any id, title or facet value, and
100
+ // no `<script>` element is emitted (PR 1 "no inline script body").
101
+ const itemsAttr = escapeHtml(payload);
102
+
103
+ return `<div class="rcf-entity-selector" data-rcf-entity-selector="${escapeHtml(hashKey)}" data-target-tab="${escapeHtml(targetTab)}" data-facet-key="${escapeHtml(facetKey)}" data-items="${itemsAttr}">
104
+ <div class="rcf-entity-selector-head">
105
+ <span class="rcf-entity-selector-total">${escapeHtml(total)}</span>
106
+ </div>
107
+ <div class="rcf-entity-selector-jump">
108
+ <input class="rcf-entity-selector-input" type="search" placeholder="${escapeHtml(placeholder)}" autocomplete="off" aria-label="${escapeHtml(placeholder)}">
109
+ <div class="rcf-entity-selector-results" hidden role="listbox"></div>
110
+ </div>
111
+ <div class="rcf-entity-selector-facets-lead muted small">${escapeHtml(chipsLabel)}</div>
112
+ <div class="rcf-entity-selector-facets" aria-label="Areas">
113
+ ${chipBlocks}
114
+ </div>
115
+ </div>`;
116
+ }
117
+
118
+ /**
119
+ * Derive facet chips (value + count) from the item list when the caller
120
+ * does not pass an explicit facet order. Facets sort by count desc,
121
+ * then by value asc. Items with an empty facet are dropped.
122
+ *
123
+ * @param {EntitySelectorItem[]} items
124
+ * @returns {EntitySelectorFacet[]}
125
+ */
126
+ function deriveFacets(items) {
127
+ const counts = new Map();
128
+ for (const it of items) {
129
+ const v = it.facet;
130
+ if (typeof v !== 'string' || v.length === 0) continue;
131
+ counts.set(v, (counts.get(v) ?? 0) + 1);
132
+ }
133
+ const out = [];
134
+ for (const [value, count] of counts.entries()) out.push({ value, count });
135
+ out.sort((a, b) => (b.count - a.count) || a.value.localeCompare(b.value));
136
+ return out;
137
+ }
@@ -0,0 +1,97 @@
1
+ // Shared FilterBar (viewer UI refresh PR 2, decision 3): sticky bar
2
+ // under the tabstrip with a text query, facet selects, toggles, a live
3
+ // row count and an Expand / Collapse all control. Filter state lives
4
+ // in the hash so a filtered view is a link; the hashKey argument lets
5
+ // each tab own its own slice of the hash namespace (e.g. "requirements"
6
+ // writes #tab=requirements&q=...&area=...).
7
+ //
8
+ // Markup shape:
9
+ //
10
+ // <div class="rcf-filterbar filterbar" data-rcf-filterbar="{hashKey}">
11
+ // <input type="search" class="rcf-filter-text" placeholder="...">
12
+ // <select class="rcf-filter-select" data-filter-key="area">...</select>
13
+ // <label class="rcf-filter-toggle">
14
+ // <input type="checkbox" data-filter-key="needsWork"> Needs work
15
+ // </label>
16
+ // <span class="rcf-filter-count count">N of M visible</span>
17
+ // <button type="button" class="rcf-filter-expand linkish">Expand all</button>
18
+ // </div>
19
+ //
20
+ // PR 2 ships the markup helper only; the hash-sync + list-filter wiring
21
+ // lives in page-init.js and ships with the first list tab that adopts
22
+ // FilterBar (PR 3, Requirements). The helper is pure HTML and has no
23
+ // behaviour of its own, so a later tab can mount it without a Router
24
+ // handshake.
25
+
26
+ import { escapeHtml } from '../doc-renderers/helpers.js';
27
+
28
+ /**
29
+ * @typedef {object} FilterSelectSpec
30
+ * @property {string} key facet key (e.g. "area", "status")
31
+ * @property {string} label visible label
32
+ * @property {Array<{ value: string, label: string }>} options
33
+ * @property {string} [value] initial selected value
34
+ */
35
+
36
+ /**
37
+ * @typedef {object} FilterToggleSpec
38
+ * @property {string} key toggle key (e.g. "needsWork")
39
+ * @property {string} label visible label
40
+ * @property {boolean} [checked] initial state
41
+ */
42
+
43
+ /**
44
+ * @param {object} args
45
+ * @param {string} args.hashKey owning tab, used as the hash namespace
46
+ * @param {string} [args.placeholder] text input placeholder
47
+ * @param {string} [args.text] initial text value
48
+ * @param {FilterSelectSpec[]} [args.selects]
49
+ * @param {FilterToggleSpec[]} [args.toggles]
50
+ * @param {{ visible: number, total: number }} [args.count] initial count readout
51
+ * @param {boolean} [args.showExpandAll] render the Expand / Collapse control
52
+ * @returns {string}
53
+ */
54
+ export function renderFilterBar({
55
+ hashKey,
56
+ placeholder = 'Search this list',
57
+ text = '',
58
+ selects = [],
59
+ toggles = [],
60
+ count,
61
+ showExpandAll = true,
62
+ }) {
63
+ const selectBlocks = selects.map((s) => {
64
+ const options = (s.options ?? []).map((o) => {
65
+ const selected = o.value === s.value ? ' selected' : '';
66
+ return `<option value="${escapeHtml(o.value)}"${selected}>${escapeHtml(o.label)}</option>`;
67
+ }).join('');
68
+ return `<label class="rcf-filter-label">
69
+ <span>${escapeHtml(s.label)}</span>
70
+ <select class="rcf-filter-select" data-filter-key="${escapeHtml(s.key)}">${options}</select>
71
+ </label>`;
72
+ }).join('\n ');
73
+
74
+ const toggleBlocks = toggles.map((t) => {
75
+ const checked = t.checked ? ' checked' : '';
76
+ return `<label class="rcf-filter-toggle">
77
+ <input type="checkbox" data-filter-key="${escapeHtml(t.key)}"${checked}>
78
+ <span>${escapeHtml(t.label)}</span>
79
+ </label>`;
80
+ }).join('\n ');
81
+
82
+ const countBlock = count
83
+ ? `<span class="rcf-filter-count count">${escapeHtml(String(count.visible))} of ${escapeHtml(String(count.total))} visible</span>`
84
+ : '';
85
+
86
+ const expandBlock = showExpandAll
87
+ ? `<button type="button" class="rcf-filter-expand linkish" data-expand-state="collapsed">Expand all</button>`
88
+ : '';
89
+
90
+ return `<div class="rcf-filterbar filterbar" data-rcf-filterbar="${escapeHtml(hashKey)}">
91
+ <input type="search" class="rcf-filter-text" value="${escapeHtml(text)}" placeholder="${escapeHtml(placeholder)}">
92
+ ${selectBlocks}
93
+ ${toggleBlocks}
94
+ ${countBlock}
95
+ ${expandBlock}
96
+ </div>`;
97
+ }
@@ -0,0 +1,49 @@
1
+ // Shared findings-list component (REQ-181, TAC-4127-readiness-view).
2
+ // Renders a heading with a count badge plus an unordered list of
3
+ // `{ id, why, href? }` items. Ids are capped at 20, and the tail
4
+ // becomes a `... N more suppressed` line so a huge failing list stays
5
+ // legible. Pure; no I/O. (AC-18101-2.)
6
+
7
+ import { escapeHtml } from '../doc-renderers/helpers.js';
8
+
9
+ /**
10
+ * @typedef {object} FindingsListItem
11
+ * @property {string} id - document id (also the anchor label)
12
+ * @property {string} [why] - one-line reason
13
+ * @property {string} [href] - override anchor target; defaults to `#<id>`
14
+ */
15
+
16
+ /**
17
+ * @param {object} args
18
+ * @param {string} args.heading - section heading text (plain)
19
+ * @param {number} args.count - the total count to show in the badge
20
+ * (may exceed `items.length`; the component renders the suppressed
21
+ * tail from the delta)
22
+ * @param {FindingsListItem[]} args.items - items to render (will be
23
+ * capped at the display limit)
24
+ * @returns {string}
25
+ */
26
+ export function findingsList({ heading, count, items }) {
27
+ const cap = 20;
28
+ const list = Array.isArray(items) ? items : [];
29
+ const total = typeof count === 'number' && Number.isFinite(count) ? count : list.length;
30
+ const shown = list.slice(0, cap);
31
+ const liHtml = shown.map((item) => {
32
+ const id = String(item?.id ?? '');
33
+ const anchorHref = typeof item?.href === 'string' && item.href.length > 0
34
+ ? item.href
35
+ : `#${id}`;
36
+ const why = typeof item?.why === 'string' && item.why.length > 0
37
+ ? `: ${escapeHtml(item.why)}`
38
+ : '';
39
+ return `<li><a href="${escapeHtml(anchorHref)}">${escapeHtml(id)}</a>${why}</li>`;
40
+ }).join('');
41
+ const suppressedCount = Math.max(0, total - shown.length);
42
+ const suppressedLi = suppressedCount > 0
43
+ ? `<li class="rcf-findings-list__suppressed"><em>... ${suppressedCount} more suppressed</em></li>`
44
+ : '';
45
+ return `<div class="rcf-findings-list">`
46
+ + `<h4>${escapeHtml(heading)} <span class="rcf-findings-list__count">${total}</span></h4>`
47
+ + `<ul>${liHtml}${suppressedLi}</ul>`
48
+ + `</div>`;
49
+ }
@@ -0,0 +1,12 @@
1
+ // Shared component barrel (viewer UI refresh PR 2). Re-exports every
2
+ // per-component string helper so downstream tabs can import a single
3
+ // well-known path. New components added in later PRs land here too.
4
+
5
+ export { pill } from './pill.js';
6
+ export { renderDocRow } from './doc-row.js';
7
+ export { renderBadge } from './badge.js';
8
+ export { renderFilterBar } from './filter-bar.js';
9
+ export { renderEmptyState } from './empty-state.js';
10
+ export { renderToastContainer } from './toast.js';
11
+ export { renderSubTabStrip } from './sub-tab-strip.js';
12
+ export { renderEntitySelector } from './entity-selector.js';
@@ -0,0 +1,46 @@
1
+ // Shared pill component (REQ-181, TAC-4127-readiness-view). One module,
2
+ // one markup shape: `<span class="rcf-pill rcf-pill--<variant>
3
+ // rcf-pill--<normalisedValue>" [title?]>{value}</span>`, or an `<a>`
4
+ // with the same classes when `href` is passed. The vocabulary families
5
+ // (gate state / level verdict / persona / AC class) all ride the same
6
+ // shape so a status reads the same on every tab (AC-18101-1).
7
+
8
+ import { escapeHtml } from '../doc-renderers/helpers.js';
9
+
10
+ /**
11
+ * Normalise a value into a kebab-cased class suffix: lowercased with
12
+ * `/`, whitespace and `_` collapsed to `-`. Everything outside
13
+ * `[a-z0-9-]` is stripped. The caller controls what the display label
14
+ * reads; this only touches the class suffix.
15
+ *
16
+ * @param {string} raw
17
+ * @returns {string}
18
+ */
19
+ function normaliseValue(raw) {
20
+ const s = String(raw ?? '').toLowerCase().replace(/[\s/_]+/g, '-');
21
+ return s.replace(/[^a-z0-9-]/g, '').replace(/-+/g, '-').replace(/^-+|-+$/g, '');
22
+ }
23
+
24
+ /**
25
+ * @param {object} args
26
+ * @param {string} args.value - display label (also drives the class suffix)
27
+ * @param {string} args.variant - vocabulary family (e.g. `gate-state`,
28
+ * `level-verdict`, `persona`, `ac-class`). Also normalised into the
29
+ * class suffix for the family.
30
+ * @param {string} [args.title] - optional tooltip text
31
+ * @param {string} [args.href] - optional href; when set, renders an `<a>`
32
+ * @returns {string}
33
+ */
34
+ export function pill({ value, variant, title, href }) {
35
+ const valueText = String(value ?? '');
36
+ const variantClass = `rcf-pill--${normaliseValue(variant)}`;
37
+ const valueClass = `rcf-pill--${normaliseValue(valueText)}`;
38
+ const classes = `rcf-pill ${variantClass} ${valueClass}`.trim();
39
+ const titleAttr = typeof title === 'string' && title.length > 0
40
+ ? ` title="${escapeHtml(title)}"`
41
+ : '';
42
+ if (typeof href === 'string' && href.length > 0) {
43
+ return `<a class="${classes}" href="${escapeHtml(href)}"${titleAttr}>${escapeHtml(valueText)}</a>`;
44
+ }
45
+ return `<span class="${classes}"${titleAttr}>${escapeHtml(valueText)}</span>`;
46
+ }
@@ -0,0 +1,43 @@
1
+ // Shared SubTabStrip (viewer UI refresh PR 2, design doc section 4):
2
+ // the one-row chip strip that sits inside a tab panel and switches
3
+ // sub-views (Build tab's Specs | DAG in PR 5-6). Writes `sub=` into
4
+ // the owning tab's hash slot via the `hashKey` argument; the Router
5
+ // in page-init.js owns the read.
6
+ //
7
+ // Markup shape:
8
+ // <nav class="rcf-subtabs subtabs" aria-label="Sub-sections"
9
+ // data-rcf-subtabstrip="{hashKey}">
10
+ // <button type="button" role="tab" data-sub="{item.key}"
11
+ // aria-selected="true|false">{item.label}</button>
12
+ // ...
13
+ // </nav>
14
+
15
+ import { escapeHtml } from '../doc-renderers/helpers.js';
16
+
17
+ /**
18
+ * @typedef {object} SubTabItem
19
+ * @property {string} key value written into `sub=`
20
+ * @property {string} label
21
+ * @property {string} [controls] optional id of the panel this item controls
22
+ */
23
+
24
+ /**
25
+ * @param {object} args
26
+ * @param {string} args.hashKey owning tab (e.g. "build")
27
+ * @param {SubTabItem[]} args.items
28
+ * @param {string} [args.active] key of the item to mark as active
29
+ * @returns {string}
30
+ */
31
+ export function renderSubTabStrip({ hashKey, items, active }) {
32
+ const effectiveActive = active ?? (items[0]?.key ?? '');
33
+ const buttons = items.map((item) => {
34
+ const isActive = item.key === effectiveActive;
35
+ const controlsAttr = item.controls
36
+ ? ` aria-controls="${escapeHtml(item.controls)}"`
37
+ : '';
38
+ return `<button type="button" role="tab" data-sub="${escapeHtml(item.key)}"${controlsAttr} aria-selected="${isActive ? 'true' : 'false'}">${escapeHtml(item.label)}</button>`;
39
+ }).join('\n ');
40
+ return `<nav class="rcf-subtabs subtabs" role="tablist" aria-label="Sub-sections" data-rcf-subtabstrip="${escapeHtml(hashKey)}">
41
+ ${buttons}
42
+ </nav>`;
43
+ }
@@ -0,0 +1,25 @@
1
+ // Shared Toast container (viewer UI refresh PR 2, design doc section 4):
2
+ // a single bottom-centred transient message block that lives in the
3
+ // AppShell. PR 2 ships the container markup and a tiny vanilla showToast
4
+ // helper on `window.rcfView`. The container is empty on first paint;
5
+ // the helper writes the text, flips the `.show` class, auto-hides after
6
+ // a brief delay and clears the content.
7
+ //
8
+ // Shape (uses <output> rather than <div> so the Phase 3.8 D13a
9
+ // `lastIndexOf("</div>")` live-content wrapper check in
10
+ // test/view/html-page.test.js stays unambiguous; <output> carries the
11
+ // implicit role="status" we need anyway):
12
+ //
13
+ // <output class="rcf-toast toast" aria-live="polite" aria-atomic="true"
14
+ // data-toast></output>
15
+ //
16
+ // Idempotent client wiring is in page-init.js; the container is
17
+ // rendered once by the AppShell so the SSE innerHTML swap (which
18
+ // replaces #rcf-live-content only) does not blow it away.
19
+
20
+ /**
21
+ * @returns {string}
22
+ */
23
+ export function renderToastContainer() {
24
+ return '<output class="rcf-toast toast" aria-live="polite" aria-atomic="true" data-toast></output>';
25
+ }