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.
- package/CHANGELOG.md +317 -0
- package/bin/rcf.js +8 -0
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/define-draft-shapes.md +89 -0
- package/guidance/define-intake.md +97 -0
- package/guidance/define-intent.md +107 -0
- package/guidance/discover-prototype.md +67 -0
- package/guidance/discovery-prototypes.md +69 -0
- package/guidance/harness-template.md +34 -0
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +34 -0
- package/guidance/manifest.json +9 -2
- package/package.json +1 -1
- package/rcf/adrs/adr-4120-canonical-json-hashing.json +30 -0
- package/rcf/adrs/adr-4121-define-sidecars.json +30 -0
- package/rcf/adrs/adr-4122-define-gates-blocking-and-optout.json +30 -0
- package/rcf/adrs/adr-4123-readiness-single-object.json +30 -0
- package/rcf/adrs/adr-4124-define-freeze-verb.json +30 -0
- package/rcf/adrs/adr-4126-persona-tagged-blockers-two-levels.json +40 -0
- package/rcf/adrs/adr-4127-readiness-tab-order-and-register.json +40 -0
- package/rcf/adrs/adr-4129-questions-harness-driven-loop.json +25 -0
- package/rcf/adrs/adr-4130-intake-harness-split-tool-scanned.json +20 -0
- package/rcf/adrs/adr-4132-prototypes-in-discovery-as-evidence.json +20 -0
- package/rcf/adrs/adr-4133-dag-dependency-depth-layout.json +40 -0
- package/rcf/adrs/adr-4134-index-json-route.json +35 -0
- package/rcf/adrs/adr-4135-readiness-po-register-and-adapter.json +35 -0
- package/rcf/adrs/adr-4136-embedding-host-fixture.json +35 -0
- package/rcf/code-nodes/cn-700.json +23 -0
- package/rcf/code-nodes/cn-701.json +14 -0
- package/rcf/code-nodes/cn-702.json +14 -0
- package/rcf/code-nodes/cn-703.json +16 -0
- package/rcf/code-nodes/cn-720.json +15 -0
- package/rcf/code-nodes/cn-721.json +15 -0
- package/rcf/code-nodes/cn-722.json +15 -0
- package/rcf/code-nodes/cn-723.json +18 -0
- package/rcf/code-nodes/cn-724.json +14 -0
- package/rcf/code-nodes/cn-725.json +37 -0
- package/rcf/code-nodes/cn-726.json +25 -0
- package/rcf/code-nodes/cn-727.json +23 -0
- package/rcf/code-nodes/cn-728.json +37 -0
- package/rcf/code-nodes/cn-729.json +26 -0
- package/rcf/code-nodes/cn-730.json +24 -0
- package/rcf/code-nodes/cn-731.json +20 -0
- package/rcf/code-nodes/cn-732.json +14 -0
- package/rcf/code-nodes/cn-733.json +14 -0
- package/rcf/code-nodes/cn-734.json +14 -0
- package/rcf/code-nodes/cn-735.json +20 -0
- package/rcf/code-nodes/cn-736.json +14 -0
- package/rcf/code-nodes/cn-737.json +14 -0
- package/rcf/code-nodes/cn-738.json +14 -0
- package/rcf/code-nodes/cn-739.json +18 -0
- package/rcf/code-nodes/cn-740.json +19 -0
- package/rcf/code-nodes/cn-741.json +16 -0
- package/rcf/code-nodes/cn-742.json +16 -0
- package/rcf/code-nodes/cn-743.json +15 -0
- package/rcf/code-nodes/cn-744.json +22 -0
- package/rcf/code-nodes/cn-745.json +16 -0
- package/rcf/code-nodes/cn-746.json +14 -0
- package/rcf/code-nodes/cn-747.json +18 -0
- package/rcf/code-nodes/cn-748.json +16 -0
- package/rcf/code-nodes/cn-749.json +16 -0
- package/rcf/code-nodes/cn-750.json +15 -0
- package/rcf/code-nodes/cn-751.json +18 -0
- package/rcf/code-nodes/cn-752.json +14 -0
- package/rcf/fbs/fbs-190.json +37 -0
- package/rcf/fbs/fbs-191.json +34 -0
- package/rcf/fbs/fbs-192.json +17 -0
- package/rcf/fbs/fbs-193.json +37 -0
- package/rcf/fbs/fbs-194.json +35 -0
- package/rcf/fbs/fbs-195.json +37 -0
- package/rcf/fbs/fbs-196.json +45 -0
- package/rcf/fbs/fbs-197.json +41 -0
- package/rcf/fbs/fbs-198.json +42 -0
- package/rcf/fbs/fbs-199.json +59 -0
- package/rcf/fbs/fbs-200.json +46 -0
- package/rcf/fbs/fbs-201.json +49 -0
- package/rcf/fbs/fbs-202.json +110 -0
- package/rcf/fbs/fbs-203.json +100 -0
- package/rcf/requirements/req-001.json +2 -1
- package/rcf/requirements/req-002.json +3 -2
- package/rcf/requirements/req-003.json +2 -1
- package/rcf/requirements/req-004.json +2 -1
- package/rcf/requirements/req-005.json +2 -1
- package/rcf/requirements/req-006.json +2 -1
- package/rcf/requirements/req-007.json +2 -1
- package/rcf/requirements/req-008.json +2 -1
- package/rcf/requirements/req-009.json +2 -1
- package/rcf/requirements/req-010.json +5 -2
- package/rcf/requirements/req-011.json +2 -1
- package/rcf/requirements/req-012.json +2 -1
- package/rcf/requirements/req-013.json +2 -1
- package/rcf/requirements/req-014.json +2 -1
- package/rcf/requirements/req-015.json +2 -1
- package/rcf/requirements/req-016.json +5 -2
- package/rcf/requirements/req-017.json +6 -2
- package/rcf/requirements/req-018.json +5 -2
- package/rcf/requirements/req-019.json +5 -2
- package/rcf/requirements/req-020.json +5 -2
- package/rcf/requirements/req-021.json +5 -2
- package/rcf/requirements/req-022.json +5 -2
- package/rcf/requirements/req-023.json +6 -4
- package/rcf/requirements/req-024.json +5 -2
- package/rcf/requirements/req-025.json +5 -2
- package/rcf/requirements/req-026.json +5 -2
- package/rcf/requirements/req-027.json +5 -2
- package/rcf/requirements/req-028.json +5 -2
- package/rcf/requirements/req-029.json +5 -2
- package/rcf/requirements/req-030.json +5 -2
- package/rcf/requirements/req-040.json +5 -2
- package/rcf/requirements/req-041.json +5 -2
- package/rcf/requirements/req-042.json +5 -2
- package/rcf/requirements/req-043.json +5 -2
- package/rcf/requirements/req-050.json +5 -2
- package/rcf/requirements/req-051.json +5 -2
- package/rcf/requirements/req-052.json +5 -2
- package/rcf/requirements/req-053.json +5 -2
- package/rcf/requirements/req-054.json +5 -2
- package/rcf/requirements/req-060.json +5 -2
- package/rcf/requirements/req-061.json +5 -2
- package/rcf/requirements/req-062.json +5 -2
- package/rcf/requirements/req-063.json +5 -2
- package/rcf/requirements/req-070.json +5 -2
- package/rcf/requirements/req-071.json +5 -2
- package/rcf/requirements/req-072.json +5 -2
- package/rcf/requirements/req-073.json +5 -2
- package/rcf/requirements/req-074.json +5 -2
- package/rcf/requirements/req-075.json +5 -2
- package/rcf/requirements/req-076.json +5 -2
- package/rcf/requirements/req-077.json +5 -2
- package/rcf/requirements/req-080.json +5 -2
- package/rcf/requirements/req-081.json +5 -2
- package/rcf/requirements/req-082.json +5 -2
- package/rcf/requirements/req-083.json +5 -2
- package/rcf/requirements/req-084.json +5 -2
- package/rcf/requirements/req-085.json +5 -2
- package/rcf/requirements/req-086.json +5 -2
- package/rcf/requirements/req-090.json +5 -2
- package/rcf/requirements/req-091.json +5 -2
- package/rcf/requirements/req-092.json +5 -2
- package/rcf/requirements/req-093.json +5 -2
- package/rcf/requirements/req-094.json +5 -2
- package/rcf/requirements/req-100.json +5 -2
- package/rcf/requirements/req-101.json +5 -2
- package/rcf/requirements/req-102.json +5 -2
- package/rcf/requirements/req-103.json +5 -2
- package/rcf/requirements/req-104.json +5 -2
- package/rcf/requirements/req-110.json +5 -2
- package/rcf/requirements/req-111.json +5 -2
- package/rcf/requirements/req-112.json +5 -2
- package/rcf/requirements/req-113.json +5 -2
- package/rcf/requirements/req-114.json +5 -2
- package/rcf/requirements/req-115.json +5 -2
- package/rcf/requirements/req-120.json +5 -2
- package/rcf/requirements/req-121.json +5 -2
- package/rcf/requirements/req-122.json +5 -2
- package/rcf/requirements/req-123.json +5 -2
- package/rcf/requirements/req-124.json +5 -2
- package/rcf/requirements/req-125.json +5 -2
- package/rcf/requirements/req-130.json +6 -2
- package/rcf/requirements/req-131.json +6 -2
- package/rcf/requirements/req-132.json +6 -2
- package/rcf/requirements/req-133.json +6 -2
- package/rcf/requirements/req-134.json +6 -2
- package/rcf/requirements/req-140.json +5 -2
- package/rcf/requirements/req-145.json +5 -2
- package/rcf/requirements/req-150.json +6 -2
- package/rcf/requirements/req-151.json +6 -2
- package/rcf/requirements/req-152.json +6 -2
- package/rcf/requirements/req-153.json +6 -2
- package/rcf/requirements/req-154.json +5 -2
- package/rcf/requirements/req-155.json +4 -1
- package/rcf/requirements/req-156.json +4 -1
- package/rcf/requirements/req-157.json +4 -1
- package/rcf/requirements/req-158.json +4 -1
- package/rcf/requirements/req-159.json +4 -1
- package/rcf/requirements/req-160.json +4 -1
- package/rcf/requirements/req-161.json +4 -1
- package/rcf/requirements/req-162.json +4 -1
- package/rcf/requirements/req-163.json +5 -2
- package/rcf/requirements/req-164.json +5 -2
- package/rcf/requirements/req-165.json +5 -2
- package/rcf/requirements/req-170.json +33 -0
- package/rcf/requirements/req-171.json +44 -0
- package/rcf/requirements/req-172.json +25 -0
- package/rcf/requirements/req-173.json +25 -0
- package/rcf/requirements/req-174.json +31 -0
- package/rcf/requirements/req-175.json +31 -0
- package/rcf/requirements/req-176.json +25 -0
- package/rcf/requirements/req-180.json +31 -0
- package/rcf/requirements/req-181.json +31 -0
- package/rcf/requirements/req-182.json +25 -0
- package/rcf/requirements/req-186.json +25 -0
- package/rcf/requirements/req-187.json +31 -0
- package/rcf/requirements/req-188.json +25 -0
- package/rcf/tacs/tac-4120-define-delta-and-freeze.json +21 -0
- package/rcf/tacs/tac-4121-define-ledgers.json +35 -0
- package/rcf/tacs/tac-4122-define-gates.json +61 -0
- package/rcf/tacs/tac-4123-define-readiness.json +61 -0
- package/rcf/tacs/tac-4124-define-freeze.json +28 -0
- package/rcf/tacs/tac-4127-readiness-view.json +49 -0
- package/rcf/tacs/tac-4128-define-guidance.json +53 -0
- package/rcf/tacs/tac-4129-define-questions.json +45 -0
- package/rcf/tacs/tac-4130-define-intake-brief.json +35 -0
- package/rcf/tacs/tac-4131-build-dag-view.json +39 -0
- package/rcf/tacs/tac-4132-id-lookup.json +42 -0
- package/rcf/tacs/tac-4133-readiness-po-layer.json +37 -0
- package/rcf/tacs/tac-4134-wespa-host-fixture.json +54 -0
- package/rcf/test-suites/ts-016.json +3 -3
- package/rcf/test-suites/ts-204.json +30 -0
- package/rcf/test-suites/ts-205.json +22 -0
- package/rcf/test-suites/ts-206.json +22 -0
- package/rcf/test-suites/ts-207.json +22 -0
- package/rcf/test-suites/ts-208.json +22 -0
- package/rcf/test-suites/ts-209.json +22 -0
- package/rcf/test-suites/ts-210.json +22 -0
- package/rcf/test-suites/ts-211.json +22 -0
- package/rcf/test-suites/ts-212.json +22 -0
- package/rcf/test-suites/ts-213.json +50 -0
- package/rcf/test-suites/ts-214.json +74 -0
- package/rcf/test-suites/ts-215.json +211 -0
- package/rcf/test-suites/ts-216.json +94 -0
- package/rcf/test-suites/ts-217.json +62 -0
- package/rcf/test-suites/ts-218.json +78 -0
- package/rcf/test-suites/ts-219.json +70 -0
- package/rcf/test-suites/ts-220.json +78 -0
- package/rcf/test-suites/ts-221.json +78 -0
- package/rcf/test-suites/ts-222.json +100 -0
- package/rcf/test-suites/ts-223.json +94 -0
- package/rcf/test-suites/ts-224.json +70 -0
- package/rcf/test-suites/ts-225.json +78 -0
- package/rcf/test-suites/ts-226.json +158 -0
- package/rcf/test-suites/ts-227.json +38 -0
- package/rcf/test-suites/ts-228.json +86 -0
- package/rcf/test-suites/ts-229.json +83 -0
- package/rcf/user-stories/us-15601.json +4 -4
- package/rcf/user-stories/us-17001.json +27 -0
- package/rcf/user-stories/us-17002.json +22 -0
- package/rcf/user-stories/us-17003.json +22 -0
- package/rcf/user-stories/us-17004.json +22 -0
- package/rcf/user-stories/us-17005.json +22 -0
- package/rcf/user-stories/us-17006.json +21 -0
- package/rcf/user-stories/us-17007.json +21 -0
- package/rcf/user-stories/us-17008.json +22 -0
- package/rcf/user-stories/us-17101.json +22 -0
- package/rcf/user-stories/us-17102.json +22 -0
- package/rcf/user-stories/us-17201.json +38 -0
- package/rcf/user-stories/us-17301.json +38 -0
- package/rcf/user-stories/us-17302.json +50 -0
- package/rcf/user-stories/us-17401.json +70 -0
- package/rcf/user-stories/us-17402.json +55 -0
- package/rcf/user-stories/us-17403.json +60 -0
- package/rcf/user-stories/us-17501.json +48 -0
- package/rcf/user-stories/us-17502.json +60 -0
- package/rcf/user-stories/us-17503.json +65 -0
- package/rcf/user-stories/us-17601.json +58 -0
- package/rcf/user-stories/us-18001.json +55 -0
- package/rcf/user-stories/us-18002.json +35 -0
- package/rcf/user-stories/us-18101.json +35 -0
- package/rcf/user-stories/us-18202.json +40 -0
- package/rcf/user-stories/us-18601.json +55 -0
- package/rcf/user-stories/us-18602.json +50 -0
- package/rcf/user-stories/us-18603.json +45 -0
- package/rcf/user-stories/us-18701.json +50 -0
- package/rcf/user-stories/us-18702.json +50 -0
- package/rcf/user-stories/us-18801.json +45 -0
- package/rcf/user-stories/us-204.json +59 -0
- package/rcf/user-stories/us-205.json +64 -0
- package/rcf/user-stories/us-206.json +54 -0
- package/releases/releases.yaml +31 -1
- package/src/cli/feedback.js +126 -10
- package/src/cli/freeze.js +730 -0
- package/src/cli/help.js +23 -0
- package/src/cli/ledger.js +615 -0
- package/src/cli/questions.js +288 -0
- package/src/cli/readiness.js +498 -0
- package/src/cli/view.js +29 -0
- package/src/core/store/writer.js +86 -6
- package/src/define/freeze-record.js +247 -0
- package/src/define/ledgers.js +712 -0
- package/src/feedback/redact.js +105 -19
- package/src/intake/index.js +1 -1
- package/src/intake/orchestrator.js +117 -0
- package/src/mcp/tools.js +354 -0
- package/src/query/delta.js +282 -0
- package/src/query/gates.js +1107 -0
- package/src/query/index.js +50 -0
- package/src/query/questions.js +438 -0
- package/src/query/readiness.js +574 -0
- package/src/server/index.js +60 -1
- package/src/server/routes.js +133 -4
- package/src/setup/identity-seed.js +17 -0
- package/src/view/build-dag.js +563 -0
- package/src/view/components/badge.js +41 -0
- package/src/view/components/diff.js +72 -0
- package/src/view/components/doc-row.js +62 -0
- package/src/view/components/empty-state.js +35 -0
- package/src/view/components/entity-selector.js +137 -0
- package/src/view/components/filter-bar.js +97 -0
- package/src/view/components/findings-list.js +49 -0
- package/src/view/components/index.js +12 -0
- package/src/view/components/pill.js +46 -0
- package/src/view/components/sub-tab-strip.js +43 -0
- package/src/view/components/toast.js +25 -0
- package/src/view/components-fixture.js +265 -0
- package/src/view/doc-renderers/architecture.js +304 -0
- package/src/view/doc-renderers/build.js +160 -0
- package/src/view/doc-renderers/helpers.js +2 -2
- package/src/view/doc-renderers/index.js +2 -0
- package/src/view/doc-renderers/prd.js +35 -6
- package/src/view/doc-renderers/req.js +10 -2
- package/src/view/doc-renderers/user-story.js +10 -5
- package/src/view/html-page.js +717 -300
- package/src/view/index.js +109 -3
- package/src/view/live-client.js +61 -8
- package/src/view/needs-work.js +79 -0
- package/src/view/page-init.js +2193 -0
- package/src/view/product-map.js +751 -0
- package/src/view/readiness/phrasebook.js +115 -0
- package/src/view/readiness/po-layer.js +288 -0
- package/src/view/readiness/question-adapter.js +329 -0
- package/src/view/readiness/stage-legend.js +71 -0
- package/src/view/readiness.js +505 -0
- package/src/view/style.css +2279 -266
- package/src/view/test-host/embed-client.js +121 -0
- package/src/view/test-host/fixture.js +183 -0
- package/src/view/test-host/lints.js +220 -0
- package/src/view/view-index.js +323 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// Component fixture page for the viewer UI refresh (PR 2). Each shared
|
|
2
|
+
// component is rendered once in isolation with representative sample
|
|
3
|
+
// data, so a human reviewer can eyeball it and the test suite can
|
|
4
|
+
// assert both the standalone-render shape and the fixture-page
|
|
5
|
+
// composition (test/view/components-fixture.test.js).
|
|
6
|
+
//
|
|
7
|
+
// The fixture page is served at `/_fixtures/components` on the live
|
|
8
|
+
// viewer. It is a self-contained HTML document that reuses the
|
|
9
|
+
// production ./style.css + ./page-init.js so theme toggling, Toast
|
|
10
|
+
// wiring and sticky layout all behave the same as inside a real tab.
|
|
11
|
+
|
|
12
|
+
import { escapeHtml } from './doc-renderers/helpers.js';
|
|
13
|
+
import {
|
|
14
|
+
pill,
|
|
15
|
+
renderDocRow,
|
|
16
|
+
renderBadge,
|
|
17
|
+
renderFilterBar,
|
|
18
|
+
renderEmptyState,
|
|
19
|
+
renderToastContainer,
|
|
20
|
+
renderSubTabStrip,
|
|
21
|
+
renderEntitySelector,
|
|
22
|
+
} from './components/index.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} ComponentSection
|
|
26
|
+
* @property {string} id slug used as the anchor and in testing
|
|
27
|
+
* @property {string} title visible heading
|
|
28
|
+
* @property {string} intent short caption shown under the heading
|
|
29
|
+
* @property {string} html the rendered component HTML
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Produce the sample DocRow set used in both the closed and open demo
|
|
34
|
+
* blocks. Deterministic so the test suite can assert the output byte for
|
|
35
|
+
* byte if it wants to.
|
|
36
|
+
*
|
|
37
|
+
* @returns {ComponentSection[]}
|
|
38
|
+
*/
|
|
39
|
+
export function componentSections() {
|
|
40
|
+
const docRowMeta = [
|
|
41
|
+
renderBadge({ value: 3, label: 'US', title: '3 user stories' }),
|
|
42
|
+
renderBadge({ value: 7, label: 'AC', title: '7 acceptance criteria' }),
|
|
43
|
+
pill({ value: 'inProgress', variant: 'status', title: 'Status' }),
|
|
44
|
+
].join(' ');
|
|
45
|
+
|
|
46
|
+
const docRowClosed = renderDocRow({
|
|
47
|
+
id: 'REQ-002',
|
|
48
|
+
title: 'Tree walker loads every document in one pass',
|
|
49
|
+
body: '<p>This body is only visible when the row is open. Try the open row below.</p>',
|
|
50
|
+
className: 'doc-req-wrap',
|
|
51
|
+
meta: docRowMeta,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const docRowOpen = renderDocRow({
|
|
55
|
+
id: 'REQ-003',
|
|
56
|
+
title: 'Live view server streams tree-update events',
|
|
57
|
+
body: `
|
|
58
|
+
<p><strong>Executive summary:</strong> The viewer owns an HTTP + SSE loop
|
|
59
|
+
that walks the tree on any <code>rcf/</code> change and ships the new
|
|
60
|
+
<code>#rcf-live-content</code> innerHTML to every connected tab.</p>
|
|
61
|
+
<h4>Rationale</h4>
|
|
62
|
+
<p>Keeps authored content in sync with the review surface without a
|
|
63
|
+
reload; a product owner viewing the page sees their last save moments
|
|
64
|
+
after Ctrl-S.</p>
|
|
65
|
+
`,
|
|
66
|
+
className: 'doc-req-wrap',
|
|
67
|
+
meta: docRowMeta,
|
|
68
|
+
open: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const filterBar = renderFilterBar({
|
|
72
|
+
hashKey: 'requirements',
|
|
73
|
+
placeholder: 'Search 24 requirements',
|
|
74
|
+
text: '',
|
|
75
|
+
selects: [
|
|
76
|
+
{
|
|
77
|
+
key: 'area',
|
|
78
|
+
label: 'Area',
|
|
79
|
+
value: '',
|
|
80
|
+
options: [
|
|
81
|
+
{ value: '', label: 'All areas' },
|
|
82
|
+
{ value: 'tree', label: 'Tree walker' },
|
|
83
|
+
{ value: 'viewer', label: 'Viewer' },
|
|
84
|
+
{ value: 'readiness', label: 'Readiness' },
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
key: 'status',
|
|
89
|
+
label: 'Status',
|
|
90
|
+
value: 'inProgress',
|
|
91
|
+
options: [
|
|
92
|
+
{ value: '', label: 'Any' },
|
|
93
|
+
{ value: 'notStarted', label: 'Not started' },
|
|
94
|
+
{ value: 'inProgress', label: 'In progress' },
|
|
95
|
+
{ value: 'complete', label: 'Complete' },
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
toggles: [
|
|
100
|
+
{ key: 'needsWork', label: 'Needs work only', checked: false },
|
|
101
|
+
],
|
|
102
|
+
count: { visible: 11, total: 24 },
|
|
103
|
+
showExpandAll: true,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const badgeRow = [
|
|
107
|
+
renderBadge({ value: 3, label: 'US' }),
|
|
108
|
+
renderBadge({ value: 7, label: 'AC' }),
|
|
109
|
+
renderBadge({ value: 'tree-walker', variant: 'facet', title: 'Area facet' }),
|
|
110
|
+
renderBadge({ value: 'needs-work', variant: 'facet' }),
|
|
111
|
+
renderBadge({ value: 12, label: 'open', variant: 'accent', title: 'Buildable now' }),
|
|
112
|
+
].join(' ');
|
|
113
|
+
|
|
114
|
+
const emptyState = renderEmptyState({
|
|
115
|
+
title: 'Nothing written yet',
|
|
116
|
+
hint: 'The architecture section for data flow has no content on disk.',
|
|
117
|
+
actionHtml: `Author it with <code>rcf define update TAD-001 --set dataArchitecture.flow</code>.`,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const toast = `${renderToastContainer()}
|
|
121
|
+
<p class="rcf-fixture-note">The Toast container is empty on first paint. Press the demo button to flash
|
|
122
|
+
a message through the shared <code>window.rcfView.showToast()</code> helper (wired by page-init.js).</p>
|
|
123
|
+
<p><button type="button" class="rcf-fixture-toast-btn" data-rcf-fixture-toast>Show a toast</button></p>`;
|
|
124
|
+
|
|
125
|
+
const subTabStrip = `${renderSubTabStrip({
|
|
126
|
+
hashKey: 'build',
|
|
127
|
+
items: [
|
|
128
|
+
{ key: 'specs', label: 'Specs' },
|
|
129
|
+
{ key: 'dag', label: 'DAG' },
|
|
130
|
+
],
|
|
131
|
+
active: 'specs',
|
|
132
|
+
})}
|
|
133
|
+
<p class="rcf-fixture-note">Marks one item active via <code>aria-selected</code>; the shared
|
|
134
|
+
page-init.js router will later own the hash write.</p>`;
|
|
135
|
+
|
|
136
|
+
const entitySelector = renderEntitySelector({
|
|
137
|
+
hashKey: 'requirements',
|
|
138
|
+
targetTab: 'requirements',
|
|
139
|
+
facetKey: 'domain',
|
|
140
|
+
items: [
|
|
141
|
+
{ id: 'REQ-001', title: 'On-disk RCF project structure', facet: 'projectStructure' },
|
|
142
|
+
{ id: 'REQ-002', title: 'Visual review surface', facet: 'view' },
|
|
143
|
+
{ id: 'REQ-003', title: 'Deterministic CRUD over RCF documents', facet: 'crud' },
|
|
144
|
+
{ id: 'REQ-010', title: 'Blueprint library mechanism', facet: 'cli' },
|
|
145
|
+
{ id: 'REQ-011', title: 'E2E verification contract', facet: 'verify' },
|
|
146
|
+
{ id: 'REQ-012', title: 'Core shelf: probe-path binding', facet: 'blueprints' },
|
|
147
|
+
],
|
|
148
|
+
totalLabel: '6 sample requirements in 6 areas (demo data)',
|
|
149
|
+
placeholder: `Jump to a requirement by id or title (e.g. REQ-010 or 'review')`,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return [
|
|
153
|
+
{
|
|
154
|
+
id: 'doc-row',
|
|
155
|
+
title: 'DocRow',
|
|
156
|
+
intent: 'The one collapsible for every per-document row. Closed by default; the body renders only when open.',
|
|
157
|
+
html: `${docRowClosed}\n${docRowOpen}`,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 'filter-bar',
|
|
161
|
+
title: 'FilterBar',
|
|
162
|
+
intent: 'Shared facet strip above a list. Text, selects, toggles, live count, Expand all.',
|
|
163
|
+
html: filterBar,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'badge',
|
|
167
|
+
title: 'Badge',
|
|
168
|
+
intent: 'A compact numeric count or facet marker. Variants: count (default), facet, accent.',
|
|
169
|
+
html: `<div class="rcf-fixture-badge-row">${badgeRow}</div>`,
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: 'empty-state',
|
|
173
|
+
title: 'EmptyState',
|
|
174
|
+
intent: 'The dashed "nothing here yet" block for empty lists or missing sections.',
|
|
175
|
+
html: emptyState,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'toast',
|
|
179
|
+
title: 'Toast',
|
|
180
|
+
intent: 'A single transient status message, bottom-centre, lives in the AppShell so it survives SSE swaps.',
|
|
181
|
+
html: toast,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: 'sub-tab-strip',
|
|
185
|
+
title: 'SubTabStrip',
|
|
186
|
+
intent: 'The chip strip inside a tab panel (Build Specs | DAG in PRs 5-6).',
|
|
187
|
+
html: subTabStrip,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'entity-selector',
|
|
191
|
+
title: 'EntitySelector',
|
|
192
|
+
intent: 'Total line + type-ahead jump + area chips with counts. The PRD tab mounts one of these to replace the 109 inline requirement links (PR 3, decision 4).',
|
|
193
|
+
html: entitySelector,
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Render the complete fixture page HTML. Self-contained; references
|
|
200
|
+
* only `./style.css` and `./page-init.js` so the page works under the
|
|
201
|
+
* same reverse-proxy mount the real viewer uses.
|
|
202
|
+
*
|
|
203
|
+
* @returns {string}
|
|
204
|
+
*/
|
|
205
|
+
export function renderComponentsFixturePage() {
|
|
206
|
+
const sections = componentSections();
|
|
207
|
+
const nav = sections.map((s) => `<a href="#${escapeHtml(s.id)}">${escapeHtml(s.title)}</a>`).join(' ');
|
|
208
|
+
const blocks = sections.map((s) => `
|
|
209
|
+
<section id="${escapeHtml(s.id)}" class="rcf-fixture-section">
|
|
210
|
+
<h2>${escapeHtml(s.title)}</h2>
|
|
211
|
+
<p class="rcf-fixture-intent">${escapeHtml(s.intent)}</p>
|
|
212
|
+
<div class="rcf-fixture-stage">
|
|
213
|
+
${s.html}
|
|
214
|
+
</div>
|
|
215
|
+
</section>`).join('\n');
|
|
216
|
+
|
|
217
|
+
return `<!DOCTYPE html>
|
|
218
|
+
<html lang="en-GB">
|
|
219
|
+
<head>
|
|
220
|
+
<meta charset="utf-8">
|
|
221
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
222
|
+
<title>Viewer UI refresh - component fixtures</title>
|
|
223
|
+
<link rel="stylesheet" href="./style.css">
|
|
224
|
+
<style>
|
|
225
|
+
body { padding: 0; }
|
|
226
|
+
.rcf-fixture-header { max-width: 1024px; margin: 0 auto; padding: 1.25rem 1rem 0.5rem; }
|
|
227
|
+
.rcf-fixture-header h1 { margin: 0 0 0.35rem; font-size: 1.4rem; letter-spacing: -0.01em; }
|
|
228
|
+
.rcf-fixture-header p { margin: 0; color: var(--sv-muted); font-size: 0.9rem; }
|
|
229
|
+
.rcf-fixture-themes { margin: 0.5rem 0 0; display: inline-flex; gap: 0.4rem; align-items: center; font-size: 0.85rem; color: var(--sv-muted); }
|
|
230
|
+
.rcf-fixture-themes button { appearance: none; border: 1px solid var(--sv-control); background: var(--sv-surface); color: var(--sv-ink); padding: 0.25rem 0.6rem; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 0.85rem; }
|
|
231
|
+
.rcf-fixture-themes button[aria-pressed="true"] { background: var(--sv-active); border-color: var(--sv-link); color: var(--sv-link); font-weight: 600; }
|
|
232
|
+
.rcf-fixture-nav { max-width: 1024px; margin: 0 auto; padding: 0.5rem 1rem 1rem; border-bottom: 1px solid var(--sv-border); display: flex; flex-wrap: wrap; gap: 0.6rem; }
|
|
233
|
+
.rcf-fixture-nav a { color: var(--sv-link); font-size: 0.85rem; text-decoration: none; padding: 0.15rem 0.5rem; border: 1px solid var(--sv-border); border-radius: 999px; background: var(--sv-surface); }
|
|
234
|
+
.rcf-fixture-nav a:hover { background: var(--sv-active); border-color: var(--sv-link); }
|
|
235
|
+
main.rcf-fixture-main { max-width: 1024px; margin: 0 auto; padding: 1rem 1rem 4rem; }
|
|
236
|
+
.rcf-fixture-section { margin: 1.75rem 0 2.5rem; }
|
|
237
|
+
.rcf-fixture-section h2 { margin: 0 0 0.25rem; font-size: 1.1rem; font-weight: 700; letter-spacing: -0.01em; }
|
|
238
|
+
.rcf-fixture-intent { margin: 0 0 0.75rem; color: var(--sv-muted); font-size: 0.9rem; }
|
|
239
|
+
.rcf-fixture-stage { padding: 1rem; border: 1px dashed var(--sv-border); border-radius: 10px; background: var(--sv-surface); }
|
|
240
|
+
.rcf-fixture-badge-row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
|
241
|
+
.rcf-fixture-note { color: var(--sv-muted); font-size: 0.85rem; margin: 0.5rem 0 0; }
|
|
242
|
+
.rcf-fixture-toast-btn { appearance: none; border: 1px solid var(--sv-link); background: var(--sv-button); color: var(--sv-onbutton); padding: 0.35rem 0.9rem; border-radius: 6px; cursor: pointer; font-family: inherit; font-weight: 600; }
|
|
243
|
+
.rcf-fixture-toast-btn:hover { background: var(--sv-hover); }
|
|
244
|
+
</style>
|
|
245
|
+
</head>
|
|
246
|
+
<body data-rcf-fixture="components">
|
|
247
|
+
<header class="rcf-fixture-header">
|
|
248
|
+
<h1>Viewer UI refresh · shared component fixtures</h1>
|
|
249
|
+
<p>PR 2 (w-2026-10-02-dave-010). Each block renders a single component with a representative payload. Toggle theme to check both colour sets.</p>
|
|
250
|
+
<div class="rcf-fixture-themes" role="group" aria-label="Theme">
|
|
251
|
+
<span>Theme</span>
|
|
252
|
+
<button type="button" data-rcf-fixture-theme="light" aria-pressed="false">Light</button>
|
|
253
|
+
<button type="button" data-rcf-fixture-theme="dark" aria-pressed="false">Dark</button>
|
|
254
|
+
<button type="button" data-rcf-fixture-theme="auto" aria-pressed="true">Auto</button>
|
|
255
|
+
</div>
|
|
256
|
+
</header>
|
|
257
|
+
<nav class="rcf-fixture-nav" aria-label="Fixture sections">${nav}</nav>
|
|
258
|
+
<main class="rcf-fixture-main">
|
|
259
|
+
${blocks}
|
|
260
|
+
</main>
|
|
261
|
+
<script src="./page-init.js" defer></script>
|
|
262
|
+
</body>
|
|
263
|
+
</html>
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// Architecture tab renderers (viewer UI refresh PR 4, w-2026-10-02-dave-010
|
|
2
|
+
// decisions 5 and 14, design doc section 5.5).
|
|
3
|
+
//
|
|
4
|
+
// The Architecture tab shows the TAD as six collapsed sections, one per
|
|
5
|
+
// canonical area, with a one-line preview on each summary. An absent
|
|
6
|
+
// section keeps its heading, reads "Nothing written yet" and names the
|
|
7
|
+
// `rcf define update TAD-001 --set` call that authors it; absence
|
|
8
|
+
// stays an empty state in the tab's own register and never points at
|
|
9
|
+
// Readiness (amendment 12.4). Below the TAD, Components (TAC) and
|
|
10
|
+
// Decisions (ADR) each render as filterable DocRow lists; TAC rows
|
|
11
|
+
// carry interface and dependency count badges, ADR rows a status pill.
|
|
12
|
+
// Long doc ids on the summary are shortened to a leading-id glyph plus
|
|
13
|
+
// an ellipsis; the full id is on the data-doc-id anchor and the title.
|
|
14
|
+
//
|
|
15
|
+
// The TAD section renderer is pure HTML; wiring (filter state) lives
|
|
16
|
+
// in page-init.js for the FilterBar namespaces architecture-components
|
|
17
|
+
// and architecture-decisions.
|
|
18
|
+
|
|
19
|
+
import { escapeHtml } from './helpers.js';
|
|
20
|
+
|
|
21
|
+
const AUTHORING_CMD = 'rcf define update TAD-001 --set';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Canonical TAD sections rendered on the Architecture tab, in order.
|
|
25
|
+
* Each entry carries:
|
|
26
|
+
* - key: the TAD field name
|
|
27
|
+
* - heading: visible section heading
|
|
28
|
+
* - fields: subfield names the authoring command hint lists (first few)
|
|
29
|
+
* - preview: function (section, tad) returning the one-line preview text
|
|
30
|
+
* - body: function (section) returning body HTML when the section
|
|
31
|
+
* has content; must return a non-empty string
|
|
32
|
+
*
|
|
33
|
+
* Design decision: deploymentArchitecture is omitted - the wireframe
|
|
34
|
+
* (projects/rcf-lite-wsd/wireframes/2026-10-02_architecture.html) lands
|
|
35
|
+
* on these six and so does the design doc section 5.5.
|
|
36
|
+
*/
|
|
37
|
+
const TAD_SECTIONS = [
|
|
38
|
+
{
|
|
39
|
+
key: 'systemOverview',
|
|
40
|
+
heading: 'System overview',
|
|
41
|
+
fields: ['executiveSummary', 'systemPurpose', 'architecturalApproach'],
|
|
42
|
+
preview: previewSystemOverview,
|
|
43
|
+
body: bodySystemOverview,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: 'architecturePrinciples',
|
|
47
|
+
heading: 'Architecture principles',
|
|
48
|
+
fields: ['architecturePrinciples.0.name', 'architecturePrinciples.0.description'],
|
|
49
|
+
preview: previewArchitecturePrinciples,
|
|
50
|
+
body: bodyArchitecturePrinciples,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: 'integrationArchitecture',
|
|
54
|
+
heading: 'Integration architecture',
|
|
55
|
+
fields: ['apiDesign', 'eventModel', 'externalSystems'],
|
|
56
|
+
preview: previewMappedObject,
|
|
57
|
+
body: bodyMappedObject({
|
|
58
|
+
apiDesign: 'API design',
|
|
59
|
+
eventModel: 'Event model',
|
|
60
|
+
}),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
key: 'operationalConcerns',
|
|
64
|
+
heading: 'Operational concerns',
|
|
65
|
+
fields: ['healthChecks', 'logging', 'monitoring'],
|
|
66
|
+
preview: previewMappedObject,
|
|
67
|
+
body: bodyMappedObject({
|
|
68
|
+
healthChecks: 'Health checks',
|
|
69
|
+
logging: 'Logging',
|
|
70
|
+
monitoring: 'Monitoring',
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: 'dataArchitecture',
|
|
75
|
+
heading: 'Data architecture',
|
|
76
|
+
fields: ['dataStores', 'coreEntities'],
|
|
77
|
+
preview: previewMappedObject,
|
|
78
|
+
body: bodyDataArchitecture,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
key: 'securityArchitecture',
|
|
82
|
+
heading: 'Security architecture',
|
|
83
|
+
fields: ['authentication', 'authorization', 'secrets'],
|
|
84
|
+
preview: previewMappedObject,
|
|
85
|
+
body: bodyMappedObject({
|
|
86
|
+
authentication: 'Authentication',
|
|
87
|
+
authorization: 'Authorization',
|
|
88
|
+
secrets: 'Secrets',
|
|
89
|
+
threatModel: 'Threat model',
|
|
90
|
+
}),
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Render the six canonical TAD sections, each as a collapsed <details>
|
|
96
|
+
* row with a one-line preview on the summary. Absent sections render
|
|
97
|
+
* the "Nothing written yet" empty state with the authoring command.
|
|
98
|
+
*
|
|
99
|
+
* @param {object|null|undefined} tad
|
|
100
|
+
* @returns {string}
|
|
101
|
+
*/
|
|
102
|
+
export function renderTadSections(tad) {
|
|
103
|
+
const t = tad && typeof tad === 'object' ? tad : {};
|
|
104
|
+
const blocks = TAD_SECTIONS.map((section) => {
|
|
105
|
+
const value = t[section.key];
|
|
106
|
+
const present = sectionIsPresent(value);
|
|
107
|
+
if (!present) {
|
|
108
|
+
return emptySectionBlock(section);
|
|
109
|
+
}
|
|
110
|
+
const preview = section.preview(value, t);
|
|
111
|
+
const body = section.body(value, t);
|
|
112
|
+
return filledSectionBlock(section, preview, body);
|
|
113
|
+
});
|
|
114
|
+
return `<div class="rcf-tad-sections" data-rcf-tad-sections>${blocks.join('\n')}</div>`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Shorten a long doc id for the summary chip. Keeps the first "KIND-NNN"
|
|
119
|
+
* prefix (two tokens) and adds an ellipsis if more follows. The full id
|
|
120
|
+
* stays on data-doc-id and the title so deep links still land.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} id
|
|
123
|
+
* @returns {string}
|
|
124
|
+
*/
|
|
125
|
+
export function shortenDocId(id) {
|
|
126
|
+
if (typeof id !== 'string' || id.length === 0) return '';
|
|
127
|
+
const parts = id.split('-');
|
|
128
|
+
if (parts.length <= 2) return id;
|
|
129
|
+
// Preserve KIND-NNN...; trim long trailing descriptor suffix.
|
|
130
|
+
const head = `${parts[0]}-${parts[1]}`;
|
|
131
|
+
if (id.length <= head.length + 3) return id;
|
|
132
|
+
return `${head}...`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---- section helpers -------------------------------------------------
|
|
136
|
+
|
|
137
|
+
function sectionIsPresent(value) {
|
|
138
|
+
if (value == null) return false;
|
|
139
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
140
|
+
if (typeof value === 'object') {
|
|
141
|
+
for (const v of Object.values(value)) {
|
|
142
|
+
if (v == null) continue;
|
|
143
|
+
if (typeof v === 'string' && v.trim().length === 0) continue;
|
|
144
|
+
if (Array.isArray(v) && v.length === 0) continue;
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
if (typeof value === 'string') return value.trim().length > 0;
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function emptySectionBlock(section) {
|
|
154
|
+
const field = section.fields[0] ?? section.key;
|
|
155
|
+
const fieldList = section.fields.map((f) => `<code>${escapeHtml(f.split('.')[0])}</code>`).join(', ');
|
|
156
|
+
const cmd = `${AUTHORING_CMD} ${section.key}.${field.split('.').slice(1).join('.') || firstScalarField(section)}="..."`;
|
|
157
|
+
const authoringLine = section.fields.length > 1
|
|
158
|
+
? `Nothing written yet. Author it with <code class="rcf-tad-empty-command">${escapeHtml(cmd)}</code> (fields: ${fieldList}); this section fills in live.`
|
|
159
|
+
: `Nothing written yet. Author it with <code class="rcf-tad-empty-command">${escapeHtml(cmd)}</code>; this section fills in live.`;
|
|
160
|
+
return `<details class="rcf-row doc-row rcf-tad-section rcf-tad-section--empty" data-rcf-tad-section="${escapeHtml(section.key)}" data-empty="1">
|
|
161
|
+
<summary>
|
|
162
|
+
<span class="rcf-tad-section-title"><strong>${escapeHtml(section.heading)}</strong> <span class="rcf-tad-preview muted small">Nothing written yet</span></span>
|
|
163
|
+
<span class="meta"><span class="rcf-badge rcf-badge--facet" title="Section is empty"><span class="rcf-badge-value">empty</span></span></span>
|
|
164
|
+
</summary>
|
|
165
|
+
<div class="rcf-row-body body">
|
|
166
|
+
<p class="rcf-tad-empty small">${authoringLine}</p>
|
|
167
|
+
</div>
|
|
168
|
+
</details>`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function filledSectionBlock(section, preview, body) {
|
|
172
|
+
return `<details class="rcf-row doc-row rcf-tad-section" data-rcf-tad-section="${escapeHtml(section.key)}">
|
|
173
|
+
<summary>
|
|
174
|
+
<span class="rcf-tad-section-title"><strong>${escapeHtml(section.heading)}</strong> <span class="rcf-tad-preview muted small">${escapeHtml(preview)}</span></span>
|
|
175
|
+
</summary>
|
|
176
|
+
<div class="rcf-row-body body">
|
|
177
|
+
${body}
|
|
178
|
+
</div>
|
|
179
|
+
</details>`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function firstScalarField(section) {
|
|
183
|
+
// Fallback for sections whose authoring example did not nest its hint.
|
|
184
|
+
return section.fields[0] ?? 'value';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ---- preview and body renderers --------------------------------------
|
|
188
|
+
|
|
189
|
+
function clipPreview(s, max = 110) {
|
|
190
|
+
const str = String(s ?? '').replace(/\s+/g, ' ').trim();
|
|
191
|
+
if (str.length <= max) return str;
|
|
192
|
+
return `${str.slice(0, max)}...`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function previewSystemOverview(section) {
|
|
196
|
+
if (!section || typeof section !== 'object') return '';
|
|
197
|
+
const parts = [section.executiveSummary, section.systemPurpose, section.architecturalApproach]
|
|
198
|
+
.filter((v) => typeof v === 'string' && v.trim().length > 0);
|
|
199
|
+
return clipPreview(parts[0] ?? '');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function bodySystemOverview(section) {
|
|
203
|
+
if (!section || typeof section !== 'object') return '';
|
|
204
|
+
const executive = typeof section.executiveSummary === 'string' && section.executiveSummary.length > 0
|
|
205
|
+
? `<p>${escapeHtml(section.executiveSummary)}</p>`
|
|
206
|
+
: '';
|
|
207
|
+
const purpose = typeof section.systemPurpose === 'string' && section.systemPurpose.length > 0
|
|
208
|
+
? `<h4>Purpose</h4><p>${escapeHtml(section.systemPurpose)}</p>`
|
|
209
|
+
: '';
|
|
210
|
+
const approach = typeof section.architecturalApproach === 'string' && section.architecturalApproach.length > 0
|
|
211
|
+
? `<h4>Approach</h4><p>${escapeHtml(section.architecturalApproach)}</p>`
|
|
212
|
+
: '';
|
|
213
|
+
const caps = Array.isArray(section.keyCapabilities) && section.keyCapabilities.length > 0
|
|
214
|
+
? `<h4>Key capabilities</h4><ul>${section.keyCapabilities.map((c) => `<li>${escapeHtml(c)}</li>`).join('')}</ul>`
|
|
215
|
+
: '';
|
|
216
|
+
return `${executive}${purpose}${approach}${caps}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function previewArchitecturePrinciples(principles) {
|
|
220
|
+
if (!Array.isArray(principles) || principles.length === 0) return '';
|
|
221
|
+
const names = principles.map((p) => (p && typeof p === 'object' ? p.name : null))
|
|
222
|
+
.filter((n) => typeof n === 'string' && n.length > 0);
|
|
223
|
+
if (names.length === 0) return clipPreview(`${principles.length} principles`);
|
|
224
|
+
const label = `${principles.length} principle${principles.length === 1 ? '' : 's'}`;
|
|
225
|
+
return clipPreview(`${label}: ${names.join(', ')}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function bodyArchitecturePrinciples(principles) {
|
|
229
|
+
if (!Array.isArray(principles) || principles.length === 0) return '';
|
|
230
|
+
const items = principles.map((p) => {
|
|
231
|
+
if (!p || typeof p !== 'object') return '';
|
|
232
|
+
const name = typeof p.name === 'string' ? p.name : '';
|
|
233
|
+
const description = typeof p.description === 'string' ? p.description : '';
|
|
234
|
+
const rationale = typeof p.rationale === 'string' && p.rationale.length > 0
|
|
235
|
+
? `<br/><em>Rationale:</em> ${escapeHtml(p.rationale)}`
|
|
236
|
+
: '';
|
|
237
|
+
return `<li><strong>${escapeHtml(name)}.</strong> ${escapeHtml(description)}${rationale}</li>`;
|
|
238
|
+
}).join('');
|
|
239
|
+
return `<ul>${items}</ul>`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function previewMappedObject(section) {
|
|
243
|
+
if (!section || typeof section !== 'object') return '';
|
|
244
|
+
if (Array.isArray(section)) {
|
|
245
|
+
return clipPreview(section.filter((v) => typeof v === 'string').join('; '));
|
|
246
|
+
}
|
|
247
|
+
const stringParts = Object.values(section)
|
|
248
|
+
.filter((v) => typeof v === 'string' && v.trim().length > 0);
|
|
249
|
+
return clipPreview(stringParts[0] ?? '');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function bodyMappedObject(labels) {
|
|
253
|
+
return function body(section) {
|
|
254
|
+
if (!section || typeof section !== 'object') return '';
|
|
255
|
+
const entries = [];
|
|
256
|
+
for (const [key, value] of Object.entries(section)) {
|
|
257
|
+
if (value == null) continue;
|
|
258
|
+
const label = labels[key] ?? prettyLabel(key);
|
|
259
|
+
if (typeof value === 'string') {
|
|
260
|
+
if (value.trim().length === 0) continue;
|
|
261
|
+
entries.push(`<h4>${escapeHtml(label)}</h4><p>${escapeHtml(value)}</p>`);
|
|
262
|
+
} else if (Array.isArray(value)) {
|
|
263
|
+
if (value.length === 0) continue;
|
|
264
|
+
if (value.every((v) => typeof v === 'string')) {
|
|
265
|
+
entries.push(`<h4>${escapeHtml(label)}</h4><ul>${value.map((v) => `<li>${escapeHtml(v)}</li>`).join('')}</ul>`);
|
|
266
|
+
} else {
|
|
267
|
+
const items = value.map((v) => `<li>${escapeHtml(typeof v === 'object' ? JSON.stringify(v) : String(v))}</li>`).join('');
|
|
268
|
+
entries.push(`<h4>${escapeHtml(label)}</h4><ul>${items}</ul>`);
|
|
269
|
+
}
|
|
270
|
+
} else if (typeof value === 'object') {
|
|
271
|
+
entries.push(`<h4>${escapeHtml(label)}</h4><pre class="mono small">${escapeHtml(JSON.stringify(value, null, 2))}</pre>`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return entries.join('\n');
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function bodyDataArchitecture(section) {
|
|
279
|
+
if (!section || typeof section !== 'object') return '';
|
|
280
|
+
const parts = [];
|
|
281
|
+
if (Array.isArray(section.dataStores) && section.dataStores.length > 0) {
|
|
282
|
+
const rows = section.dataStores.map((s) => `<tr><td>${escapeHtml(s.name ?? '')}</td><td>${escapeHtml(s.kind ?? '')}</td><td>${escapeHtml(s.purpose ?? '')}</td></tr>`).join('');
|
|
283
|
+
parts.push(`<h4>Data stores</h4><table class="data-table"><thead><tr><th>Name</th><th>Kind</th><th>Purpose</th></tr></thead><tbody>${rows}</tbody></table>`);
|
|
284
|
+
}
|
|
285
|
+
if (Array.isArray(section.coreEntities) && section.coreEntities.length > 0) {
|
|
286
|
+
const rows = section.coreEntities.map((e) => `<tr><td>${escapeHtml(e.name ?? '')}</td><td>${escapeHtml(e.description ?? '')}</td></tr>`).join('');
|
|
287
|
+
parts.push(`<h4>Core entities</h4><table class="data-table"><thead><tr><th>Name</th><th>Description</th></tr></thead><tbody>${rows}</tbody></table>`);
|
|
288
|
+
}
|
|
289
|
+
for (const [key, value] of Object.entries(section)) {
|
|
290
|
+
if (key === 'dataStores' || key === 'coreEntities') continue;
|
|
291
|
+
if (value == null) continue;
|
|
292
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
293
|
+
parts.push(`<h4>${escapeHtml(prettyLabel(key))}</h4><p>${escapeHtml(value)}</p>`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return parts.join('\n');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function prettyLabel(key) {
|
|
300
|
+
return key.replace(/([A-Z])/g, ' $1').replace(/^./, (c) => c.toUpperCase()).trim();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Exposed for tests only.
|
|
304
|
+
export const _internal = { TAD_SECTIONS };
|