rcf-lite 0.28.4 → 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 +107 -1
- package/bin/rcf.js +4 -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-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-725.json +18 -2
- package/rcf/code-nodes/cn-726.json +10 -2
- package/rcf/code-nodes/cn-727.json +13 -4
- package/rcf/code-nodes/cn-728.json +18 -2
- package/rcf/code-nodes/cn-729.json +10 -2
- package/rcf/code-nodes/cn-730.json +13 -3
- 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-196.json +13 -5
- 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-002.json +2 -2
- package/rcf/requirements/req-173.json +2 -2
- package/rcf/requirements/req-174.json +10 -4
- package/rcf/requirements/req-175.json +10 -4
- 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-4121-define-ledgers.json +15 -3
- package/rcf/tacs/tac-4122-define-gates.json +40 -4
- package/rcf/tacs/tac-4123-define-readiness.json +39 -3
- 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-215.json +148 -4
- package/rcf/test-suites/ts-216.json +19 -3
- 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-17302.json +50 -0
- package/rcf/user-stories/us-17401.json +14 -2
- package/rcf/user-stories/us-17402.json +55 -0
- package/rcf/user-stories/us-17403.json +60 -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 +11 -1
- package/src/cli/freeze.js +730 -0
- package/src/cli/help.js +12 -0
- package/src/cli/ledger.js +228 -12
- package/src/cli/questions.js +288 -0
- package/src/cli/readiness.js +267 -49
- package/src/cli/view.js +29 -0
- package/src/define/freeze-record.js +30 -15
- package/src/define/ledgers.js +210 -11
- 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 +6 -0
- package/src/query/gates.js +314 -39
- package/src/query/index.js +16 -2
- package/src/query/questions.js +438 -0
- package/src/query/readiness.js +248 -7
- package/src/server/index.js +24 -2
- package/src/server/routes.js +68 -1
- 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/index.js +2 -0
- package/src/view/doc-renderers/prd.js +35 -6
- package/src/view/html-page.js +700 -602
- package/src/view/index.js +96 -3
- package/src/view/live-client.js +39 -6
- package/src/view/needs-work.js +79 -0
- package/src/view/page-init.js +2193 -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 +2017 -382
- 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,160 @@
|
|
|
1
|
+
// Build-tab renderers (viewer UI refresh PR 5, w-2026-10-02-dave-010
|
|
2
|
+
// decisions 2, 3, 6, 12, 14; design doc section 5.6).
|
|
3
|
+
//
|
|
4
|
+
// This module ships the Build tab's own small pieces:
|
|
5
|
+
// - renderBuildStats(stats): the one-line totals card above the
|
|
6
|
+
// sub-tabstrip. Totals come from the pre-computed BuildStats
|
|
7
|
+
// payload the panel assembles from `computeQueue` (Build Queue is
|
|
8
|
+
// the authoritative source for "buildable now", decision 6 + the
|
|
9
|
+
// brief evidence row).
|
|
10
|
+
// - renderBuildPageHead(bs): the one-line page head carrying the BS
|
|
11
|
+
// id and its generation strategy.
|
|
12
|
+
// - renderSpecBody(fbs, model, buildable): the body shown inside an
|
|
13
|
+
// expanded Spec row - summary, approach, AC and dependency links,
|
|
14
|
+
// deliverables, context links, "Show in the DAG".
|
|
15
|
+
//
|
|
16
|
+
// The list row chrome (DocRow summary with order / AC / deps / size /
|
|
17
|
+
// buildable pill / status) is composed in html-page.js so the row
|
|
18
|
+
// renderer stays close to the FilterBar and the sub-tabstrip wiring.
|
|
19
|
+
//
|
|
20
|
+
// The Test suites section and the FBS slots block are dropped (design
|
|
21
|
+
// doc section 3, decision 6); suites stay reachable through AC links
|
|
22
|
+
// and the ID lookup once PR 7 ships.
|
|
23
|
+
|
|
24
|
+
import { escapeHtml, rawJsonDisclosure } from './helpers.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* BuildStats payload shape consumed by renderBuildStats.
|
|
28
|
+
*
|
|
29
|
+
* @typedef {object} BuildStatsPayload
|
|
30
|
+
* @property {number} items
|
|
31
|
+
* @property {number} verified
|
|
32
|
+
* @property {number} complete
|
|
33
|
+
* @property {number} inProgress
|
|
34
|
+
* @property {number} notStarted
|
|
35
|
+
* @property {number} buildableNow actionable count from computeQueue
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {BuildStatsPayload} stats
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
export function renderBuildStats(stats) {
|
|
43
|
+
const row = [
|
|
44
|
+
{ n: stats.items ?? 0, l: 'build specs' },
|
|
45
|
+
{ n: stats.verified ?? 0, l: 'verified' },
|
|
46
|
+
{ n: stats.complete ?? 0, l: 'complete' },
|
|
47
|
+
{ n: stats.inProgress ?? 0, l: 'in progress' },
|
|
48
|
+
{ n: stats.notStarted ?? 0, l: 'not started' },
|
|
49
|
+
{ n: stats.buildableNow ?? 0, l: 'buildable now' },
|
|
50
|
+
];
|
|
51
|
+
const items = stats.items > 0 ? stats.items : 1;
|
|
52
|
+
const pct = (n) => Math.max(0, Math.min(100, Math.round((n / items) * 100)));
|
|
53
|
+
const verifiedPct = pct(stats.verified ?? 0);
|
|
54
|
+
const completePct = pct(stats.complete ?? 0);
|
|
55
|
+
const inProgressPct = pct(stats.inProgress ?? 0);
|
|
56
|
+
const statRow = row.map((s) => (
|
|
57
|
+
`<div class="rcf-build-stat"><span class="rcf-build-stat-n">${escapeHtml(String(s.n))}</span><span class="rcf-build-stat-l">${escapeHtml(s.l)}</span></div>`
|
|
58
|
+
)).join('');
|
|
59
|
+
return `<div class="rcf-build-stats card" role="group" aria-label="Build totals">
|
|
60
|
+
<div class="rcf-build-stats-row">${statRow}</div>
|
|
61
|
+
<div class="rcf-build-progress" aria-hidden="true">
|
|
62
|
+
<span class="rcf-build-progress-seg rcf-build-progress-seg--verified" style="width:${verifiedPct}%"></span>
|
|
63
|
+
<span class="rcf-build-progress-seg rcf-build-progress-seg--complete" style="width:${completePct}%"></span>
|
|
64
|
+
<span class="rcf-build-progress-seg rcf-build-progress-seg--inprogress" style="width:${inProgressPct}%"></span>
|
|
65
|
+
</div>
|
|
66
|
+
</div>`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {object | null | undefined} bs - the Build Sequence doc from the tree
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
export function renderBuildPageHead(bs) {
|
|
74
|
+
if (!bs) return `<div class="rcf-build-head"></div>`;
|
|
75
|
+
const title = typeof bs.title === 'string' ? bs.title : '';
|
|
76
|
+
const strategy = typeof bs.generationStrategy === 'string' ? bs.generationStrategy : '';
|
|
77
|
+
const parts = [`<span class="rcf-build-head-id mono">${escapeHtml(bs.bsId ?? 'BS')}</span>`];
|
|
78
|
+
const detail = [title, strategy].filter((v) => typeof v === 'string' && v.length > 0).join(' - ');
|
|
79
|
+
if (detail) parts.push(`<span class="muted small">${escapeHtml(detail)}</span>`);
|
|
80
|
+
return `<div class="rcf-build-head">${parts.join('')}</div>`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Spec row body: summary, approach, AC and dependency links, deliverables,
|
|
85
|
+
* context links, and the "Show in the DAG" pointer (DAG panel ships in
|
|
86
|
+
* PR 6; the hash target is #tab=build&sub=dag&entity=<id>).
|
|
87
|
+
*
|
|
88
|
+
* @param {object} fbs
|
|
89
|
+
* @param {import('../tree-model.js').BuiltTreeModel} model
|
|
90
|
+
* @param {string} [raw] raw JSON string for the <details class="raw-json"> disclosure
|
|
91
|
+
* @returns {string}
|
|
92
|
+
*/
|
|
93
|
+
export function renderSpecBody(fbs, model, raw) {
|
|
94
|
+
const summary = typeof fbs.summary === 'string' && fbs.summary.length > 0
|
|
95
|
+
? `<p class="rcf-spec-summary">${escapeHtml(fbs.summary)}</p>`
|
|
96
|
+
: '';
|
|
97
|
+
const approach = typeof fbs.approach === 'string' && fbs.approach.length > 0
|
|
98
|
+
? `<section class="rcf-spec-block"><h4>Approach</h4><p>${escapeHtml(fbs.approach)}</p></section>`
|
|
99
|
+
: '';
|
|
100
|
+
|
|
101
|
+
const acIds = Array.isArray(fbs.acIds) ? fbs.acIds : [];
|
|
102
|
+
const acBlock = acIds.length > 0
|
|
103
|
+
? `<section class="rcf-spec-block"><h4>Acceptance criteria (${acIds.length})</h4><p class="rcf-spec-link-row">${acIds.map((id) => renderAcLink(id)).join(', ')}</p></section>`
|
|
104
|
+
: '';
|
|
105
|
+
|
|
106
|
+
const depIds = Array.isArray(fbs.dependsOnFbsIds) ? fbs.dependsOnFbsIds : [];
|
|
107
|
+
const depBlock = depIds.length > 0
|
|
108
|
+
? `<section class="rcf-spec-block"><h4>Depends on</h4><ul class="rcf-spec-deps">${depIds.map((id) => renderDepLine(id, model)).join('')}</ul></section>`
|
|
109
|
+
: '';
|
|
110
|
+
|
|
111
|
+
const deliverables = Array.isArray(fbs.deliverables) ? fbs.deliverables.filter((d) => typeof d === 'string' && d.length > 0) : [];
|
|
112
|
+
const deliverableBlock = deliverables.length > 0
|
|
113
|
+
? `<section class="rcf-spec-block"><h4>Deliverables</h4><ul class="rcf-spec-deliverables">${deliverables.map((d) => `<li>${escapeHtml(d)}</li>`).join('')}</ul></section>`
|
|
114
|
+
: '';
|
|
115
|
+
|
|
116
|
+
const contextBlock = renderContextLinks(fbs.contextRequirements);
|
|
117
|
+
|
|
118
|
+
const dagLink = `<p class="rcf-spec-dag"><a href="#tab=build&sub=dag&entity=${escapeHtml(fbs.fbsId ?? '')}">Show in the DAG</a></p>`;
|
|
119
|
+
|
|
120
|
+
const rawBlock = rawJsonDisclosure(raw, fbs, fbs.fbsId);
|
|
121
|
+
|
|
122
|
+
return `${summary}${approach}${acBlock}${depBlock}${deliverableBlock}${contextBlock}${dagLink}${rawBlock}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function renderAcLink(id) {
|
|
126
|
+
const esc = escapeHtml(id);
|
|
127
|
+
return `<a class="mono" href="#tab=requirements&entity=${esc}">${esc}</a>`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function renderDepLine(id, model) {
|
|
131
|
+
const esc = escapeHtml(id);
|
|
132
|
+
const dep = model.byId?.get(id);
|
|
133
|
+
const title = typeof dep?.title === 'string' ? dep.title : '';
|
|
134
|
+
const titleFrag = title ? ` <span class="muted">${escapeHtml(title)}</span>` : '';
|
|
135
|
+
return `<li><a class="mono" href="#tab=build&sub=specs&entity=${esc}">${esc}</a>${titleFrag}</li>`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function renderContextLinks(ctx) {
|
|
139
|
+
if (!ctx || typeof ctx !== 'object') return '';
|
|
140
|
+
const parts = [];
|
|
141
|
+
const tacIds = Array.isArray(ctx.tacIds) ? ctx.tacIds : [];
|
|
142
|
+
const adrIds = Array.isArray(ctx.adrIds) ? ctx.adrIds : [];
|
|
143
|
+
const tadSections = Array.isArray(ctx.tadSections) ? ctx.tadSections : [];
|
|
144
|
+
if (tacIds.length === 0 && adrIds.length === 0 && tadSections.length === 0) return '';
|
|
145
|
+
if (tacIds.length > 0) {
|
|
146
|
+
parts.push(`<p class="rcf-spec-link-row"><strong>TACs:</strong> ${tacIds.map((id) => archLink(id)).join(', ')}</p>`);
|
|
147
|
+
}
|
|
148
|
+
if (adrIds.length > 0) {
|
|
149
|
+
parts.push(`<p class="rcf-spec-link-row"><strong>ADRs:</strong> ${adrIds.map((id) => archLink(id)).join(', ')}</p>`);
|
|
150
|
+
}
|
|
151
|
+
if (tadSections.length > 0) {
|
|
152
|
+
parts.push(`<p class="rcf-spec-link-row"><strong>TAD sections:</strong> ${tadSections.map((k) => `<a class="mono" href="#tab=architecture&open=${escapeHtml(k)}">${escapeHtml(k)}</a>`).join(', ')}</p>`);
|
|
153
|
+
}
|
|
154
|
+
return `<section class="rcf-spec-block"><h4>Context</h4>${parts.join('')}</section>`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function archLink(id) {
|
|
158
|
+
const esc = escapeHtml(id);
|
|
159
|
+
return `<a class="mono" href="#tab=architecture&entity=${esc}">${esc}</a>`;
|
|
160
|
+
}
|
|
@@ -5,8 +5,10 @@ export { renderPrd } from './prd.js';
|
|
|
5
5
|
export { renderReq } from './req.js';
|
|
6
6
|
export { renderUserStory } from './user-story.js';
|
|
7
7
|
export { renderTad } from './tad.js';
|
|
8
|
+
export { renderTadSections, shortenDocId } from './architecture.js';
|
|
8
9
|
export { renderTac } from './tac.js';
|
|
9
10
|
export { renderAdr } from './adr.js';
|
|
10
11
|
export { renderBuildSequence } from './build-sequence.js';
|
|
11
12
|
export { renderFbs } from './fbs.js';
|
|
12
13
|
export { renderTestSuite } from './test-suite.js';
|
|
14
|
+
export { renderBuildStats, renderBuildPageHead, renderSpecBody } from './build.js';
|
|
@@ -18,6 +18,16 @@ import {
|
|
|
18
18
|
* @param {string|undefined} ctx.raw
|
|
19
19
|
* @param {import('#core/errors').RcfError[]} [ctx.errors]
|
|
20
20
|
* @param {string[]} [ctx.requirementIds] - computed REQ children for this PRD
|
|
21
|
+
* @param {boolean} [ctx.suppressRequirementList] - viewer UI refresh PR 3
|
|
22
|
+
* (decision 4): the PRD tab replaces the inline doc-link paragraph with
|
|
23
|
+
* an EntitySelector mounted outside this article, so renderPrd no
|
|
24
|
+
* longer emits the 109 inline requirement anchors when that flag is on.
|
|
25
|
+
* Non-tab callers (if any) still see the legacy paragraph by default.
|
|
26
|
+
* @param {boolean} [ctx.collapsibleLists] - viewer UI refresh PR 3 (design
|
|
27
|
+
* section 5): the PRD tab collapses Target users / In scope / Out of
|
|
28
|
+
* scope / Objectives / Constraints each with a count badge; Executive
|
|
29
|
+
* summary and Problem statement stay open. Non-tab callers (if any)
|
|
30
|
+
* still see the open sections by default.
|
|
21
31
|
* @returns {string}
|
|
22
32
|
*/
|
|
23
33
|
export function renderPrd(prd, ctx) {
|
|
@@ -25,15 +35,19 @@ export function renderPrd(prd, ctx) {
|
|
|
25
35
|
const anchor = anchorIdFor(prd.prdId ?? 'PRD');
|
|
26
36
|
const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
|
|
27
37
|
const requirementIds = ctx.requirementIds ?? [];
|
|
38
|
+
const requirementSection = ctx.suppressRequirementList
|
|
39
|
+
? ''
|
|
40
|
+
: `<section class="field-list"><h4>Requirements</h4><p>${docLinkList(requirementIds)}</p></section>`;
|
|
41
|
+
const renderList = ctx.collapsibleLists ? collapsibleFieldList : fieldList;
|
|
28
42
|
const sections = [
|
|
29
43
|
fieldPara('Executive summary', prd.executiveSummary),
|
|
30
44
|
fieldPara('Problem statement', prd.problemStatement),
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
renderList('Target users', prd.targetUsers),
|
|
46
|
+
renderList('In scope', prd.inScope),
|
|
47
|
+
renderList('Out of scope', prd.outOfScope),
|
|
48
|
+
renderList('Objectives', prd.objectives),
|
|
49
|
+
renderList('Constraints', prd.constraints),
|
|
50
|
+
requirementSection,
|
|
37
51
|
].filter(Boolean).join('\n');
|
|
38
52
|
return `
|
|
39
53
|
<article id="${anchor}" class="doc doc-prd">
|
|
@@ -43,3 +57,18 @@ export function renderPrd(prd, ctx) {
|
|
|
43
57
|
${rawJsonDisclosure(ctx.raw, prd, prd.prdId)}
|
|
44
58
|
</article>`.trim();
|
|
45
59
|
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Collapsible variant of `fieldList`: a <details> whose summary names
|
|
63
|
+
* the field and carries the count badge, body is the same `<ul>` list.
|
|
64
|
+
* The count badge uses the shared Badge class names so it looks the
|
|
65
|
+
* same as the Requirements-tab counts.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} label
|
|
68
|
+
* @param {string[] | undefined} items
|
|
69
|
+
*/
|
|
70
|
+
function collapsibleFieldList(label, items) {
|
|
71
|
+
if (!Array.isArray(items) || items.length === 0) return '';
|
|
72
|
+
const li = items.map((s) => `<li>${escapeHtml(s)}</li>`).join('');
|
|
73
|
+
return `<details class="rcf-prd-list field-list"><summary><strong>${escapeHtml(label)}</strong> <span class="rcf-badge rcf-badge--count"><span class="rcf-badge-value">${items.length}</span></span></summary><div class="rcf-prd-list-body"><ul>${li}</ul></div></details>`;
|
|
74
|
+
}
|