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,323 @@
|
|
|
1
|
+
// Viewer UI refresh PR 7 (TAC-4132, ADR-4134): the ID lookup index
|
|
2
|
+
// built once per rewalk from BuiltTreeModel. One row per document id
|
|
3
|
+
// plus one row per AC under each US, with the minimum metadata the
|
|
4
|
+
// LookupModal needs to rank and route: `{ id, kind, title, snippet,
|
|
5
|
+
// parent, tab }`. The server serves this via `GET /index.json` so
|
|
6
|
+
// wespa's reverse proxy sees it as a new relative route under the
|
|
7
|
+
// mount point (release-noted; ADR-4134 names the alternatives rejected).
|
|
8
|
+
//
|
|
9
|
+
// Pure projection: reads nothing beyond the model and returns a plain
|
|
10
|
+
// JSON-safe object. The viewer client fetches it lazily on first open
|
|
11
|
+
// and refreshes it when the SSE tree-update broadcast lands.
|
|
12
|
+
|
|
13
|
+
const SNIPPET_LIMIT = 110;
|
|
14
|
+
|
|
15
|
+
const TAB_BY_KIND = {
|
|
16
|
+
prd: 'overview',
|
|
17
|
+
req: 'requirements',
|
|
18
|
+
us: 'requirements',
|
|
19
|
+
ac: 'requirements',
|
|
20
|
+
tad: 'architecture',
|
|
21
|
+
tac: 'architecture',
|
|
22
|
+
adr: 'architecture',
|
|
23
|
+
bs: 'build',
|
|
24
|
+
fbs: 'build',
|
|
25
|
+
ts: 'requirements',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} IndexRow
|
|
30
|
+
* @property {string} id canonical document / AC id
|
|
31
|
+
* @property {'prd'|'req'|'us'|'ac'|'tad'|'tac'|'adr'|'bs'|'fbs'|'ts'} kind
|
|
32
|
+
* @property {string} title display title (empty string on a doc with no title)
|
|
33
|
+
* @property {string} snippet first SNIPPET_LIMIT chars of the kind's prose field with whitespace collapsed
|
|
34
|
+
* @property {string} parent parent document id (empty string when none; e.g. the PRD has no parent)
|
|
35
|
+
* @property {'overview'|'requirements'|'architecture'|'build'} tab
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Collapse an arbitrary string to the first SNIPPET_LIMIT characters
|
|
40
|
+
* with every whitespace run normalised to a single space, so the
|
|
41
|
+
* modal rows render consistently and the JSON payload stays small.
|
|
42
|
+
*
|
|
43
|
+
* @param {unknown} raw
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
function snippetOf(raw) {
|
|
47
|
+
if (typeof raw !== 'string' || raw.length === 0) return '';
|
|
48
|
+
const flat = raw.replace(/\s+/g, ' ').trim();
|
|
49
|
+
if (flat.length <= SNIPPET_LIMIT) return flat;
|
|
50
|
+
return flat.slice(0, SNIPPET_LIMIT);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build the ID lookup index from a BuiltTreeModel. One row per
|
|
55
|
+
* document id (PRD, REQ, US, TAD, TAC, ADR, BS, FBS, TS) plus one row
|
|
56
|
+
* per AC on each US. Rows carry `{ id, kind, title, snippet, parent,
|
|
57
|
+
* tab }` and nothing else; the client renders every surface detail
|
|
58
|
+
* (count badge, kind pill) from this core shape.
|
|
59
|
+
*
|
|
60
|
+
* @param {import('./tree-model.js').BuiltTreeModel} model
|
|
61
|
+
* @returns {{ rows: IndexRow[] }}
|
|
62
|
+
*/
|
|
63
|
+
export function buildViewIndex(model) {
|
|
64
|
+
const rows = [];
|
|
65
|
+
|
|
66
|
+
const prd = model?.prd;
|
|
67
|
+
if (prd?.prdId) {
|
|
68
|
+
rows.push({
|
|
69
|
+
id: prd.prdId,
|
|
70
|
+
kind: 'prd',
|
|
71
|
+
title: typeof prd.productName === 'string' ? prd.productName : '',
|
|
72
|
+
snippet: snippetOf(prd.executiveSummary ?? prd.problemStatement ?? ''),
|
|
73
|
+
parent: '',
|
|
74
|
+
tab: TAB_BY_KIND.prd,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const r of model?.requirements ?? []) {
|
|
79
|
+
if (!r?.reqId) continue;
|
|
80
|
+
rows.push({
|
|
81
|
+
id: r.reqId,
|
|
82
|
+
kind: 'req',
|
|
83
|
+
title: typeof r.title === 'string' ? r.title : '',
|
|
84
|
+
snippet: snippetOf(r.description ?? r.rationale ?? ''),
|
|
85
|
+
parent: typeof r.prdId === 'string' ? r.prdId : '',
|
|
86
|
+
tab: TAB_BY_KIND.req,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const us of model?.userStories ?? []) {
|
|
91
|
+
if (!us?.usId) continue;
|
|
92
|
+
rows.push({
|
|
93
|
+
id: us.usId,
|
|
94
|
+
kind: 'us',
|
|
95
|
+
title: typeof us.title === 'string' ? us.title : '',
|
|
96
|
+
snippet: snippetOf(us.description ?? us.iWant ?? ''),
|
|
97
|
+
parent: typeof us.reqId === 'string' ? us.reqId : '',
|
|
98
|
+
tab: TAB_BY_KIND.us,
|
|
99
|
+
});
|
|
100
|
+
for (const ac of us.acceptanceCriteria ?? []) {
|
|
101
|
+
if (!ac?.id) continue;
|
|
102
|
+
rows.push({
|
|
103
|
+
id: ac.id,
|
|
104
|
+
kind: 'ac',
|
|
105
|
+
title: '',
|
|
106
|
+
snippet: snippetOf(ac.description ?? ''),
|
|
107
|
+
parent: us.usId,
|
|
108
|
+
tab: TAB_BY_KIND.ac,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const tad = model?.tad;
|
|
114
|
+
if (tad?.tadId) {
|
|
115
|
+
rows.push({
|
|
116
|
+
id: tad.tadId,
|
|
117
|
+
kind: 'tad',
|
|
118
|
+
title: typeof tad.title === 'string' ? tad.title : '',
|
|
119
|
+
snippet: snippetOf(tad.systemOverview ?? ''),
|
|
120
|
+
parent: typeof tad.prdId === 'string' ? tad.prdId : '',
|
|
121
|
+
tab: TAB_BY_KIND.tad,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const t of model?.tacs ?? []) {
|
|
126
|
+
if (!t?.tacId) continue;
|
|
127
|
+
rows.push({
|
|
128
|
+
id: t.tacId,
|
|
129
|
+
kind: 'tac',
|
|
130
|
+
title: typeof t.name === 'string' ? t.name : '',
|
|
131
|
+
snippet: snippetOf(t.purpose ?? ''),
|
|
132
|
+
parent: typeof t.tadId === 'string' ? t.tadId : '',
|
|
133
|
+
tab: TAB_BY_KIND.tac,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const a of model?.adrs ?? []) {
|
|
138
|
+
if (!a?.adrId) continue;
|
|
139
|
+
rows.push({
|
|
140
|
+
id: a.adrId,
|
|
141
|
+
kind: 'adr',
|
|
142
|
+
title: typeof a.title === 'string' ? a.title : '',
|
|
143
|
+
snippet: snippetOf(a.decision ?? a.context ?? ''),
|
|
144
|
+
parent: typeof a.tadId === 'string' ? a.tadId : '',
|
|
145
|
+
tab: TAB_BY_KIND.adr,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const bs = model?.bs;
|
|
150
|
+
if (bs?.bsId) {
|
|
151
|
+
rows.push({
|
|
152
|
+
id: bs.bsId,
|
|
153
|
+
kind: 'bs',
|
|
154
|
+
title: typeof bs.title === 'string' ? bs.title : '',
|
|
155
|
+
snippet: snippetOf(bs.buildPhilosophy ?? ''),
|
|
156
|
+
parent: typeof bs.prdId === 'string' ? bs.prdId : '',
|
|
157
|
+
tab: TAB_BY_KIND.bs,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for (const f of model?.fbsItems ?? []) {
|
|
162
|
+
if (!f?.fbsId) continue;
|
|
163
|
+
rows.push({
|
|
164
|
+
id: f.fbsId,
|
|
165
|
+
kind: 'fbs',
|
|
166
|
+
title: typeof f.title === 'string' ? f.title : '',
|
|
167
|
+
snippet: snippetOf(f.summary ?? ''),
|
|
168
|
+
parent: typeof f.bsId === 'string' ? f.bsId : '',
|
|
169
|
+
tab: TAB_BY_KIND.fbs,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const ts of model?.testSuites ?? []) {
|
|
174
|
+
const id = ts?.id ?? ts?.tsId;
|
|
175
|
+
if (!id) continue;
|
|
176
|
+
rows.push({
|
|
177
|
+
id,
|
|
178
|
+
kind: 'ts',
|
|
179
|
+
title: typeof ts.title === 'string' ? ts.title : '',
|
|
180
|
+
snippet: snippetOf(ts.purpose ?? ''),
|
|
181
|
+
parent: typeof ts.usId === 'string' ? ts.usId : '',
|
|
182
|
+
tab: TAB_BY_KIND.ts,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { rows };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Build the JSON string the server returns on `GET /index.json`. Kept
|
|
191
|
+
* next to buildViewIndex so the Content-Length can be set from the
|
|
192
|
+
* byte length of the exact string the client receives.
|
|
193
|
+
*
|
|
194
|
+
* @param {import('./tree-model.js').BuiltTreeModel} model
|
|
195
|
+
* @returns {string}
|
|
196
|
+
*/
|
|
197
|
+
export function serialiseViewIndex(model) {
|
|
198
|
+
return JSON.stringify(buildViewIndex(model));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Pure ranking helper mirrored into page-init.js's wireLookup. Kept
|
|
203
|
+
* here so the server-side tests can exercise the exact ranking rules
|
|
204
|
+
* the browser applies. Design doc section 6 rules:
|
|
205
|
+
* - A single-character query searches ids only; the title / snippet
|
|
206
|
+
* word-match pass is skipped.
|
|
207
|
+
* - Otherwise ids rank first (exact > prefix > contains).
|
|
208
|
+
* - Then every query word must match title or snippet, title above
|
|
209
|
+
* snippet.
|
|
210
|
+
* - 20 results max; stable by id on ties.
|
|
211
|
+
*
|
|
212
|
+
* @param {string} query
|
|
213
|
+
* @param {IndexRow[]} rows
|
|
214
|
+
* @returns {Array<{ row: IndexRow, score: number, needles: string[] }>}
|
|
215
|
+
*/
|
|
216
|
+
export function rankLookupRows(query, rows) {
|
|
217
|
+
if (!query || !Array.isArray(rows)) return [];
|
|
218
|
+
const q = String(query).trim().toLowerCase();
|
|
219
|
+
if (!q) return [];
|
|
220
|
+
const scored = [];
|
|
221
|
+
if (q.length === 1) {
|
|
222
|
+
for (const r of rows) {
|
|
223
|
+
const idLow = (r.id || '').toLowerCase();
|
|
224
|
+
if (idLow.indexOf(q) === -1) continue;
|
|
225
|
+
let score = 100;
|
|
226
|
+
if (idLow === q) score = 1000;
|
|
227
|
+
else if (idLow.indexOf(q) === 0) score = 500;
|
|
228
|
+
scored.push({ row: r, score, needles: [q] });
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
const words = q.split(/\s+/).filter(Boolean);
|
|
232
|
+
for (const r of rows) {
|
|
233
|
+
const idLow = (r.id || '').toLowerCase();
|
|
234
|
+
const titleLow = (r.title || '').toLowerCase();
|
|
235
|
+
const snipLow = (r.snippet || '').toLowerCase();
|
|
236
|
+
let score = 0;
|
|
237
|
+
if (idLow === q) score = 10000;
|
|
238
|
+
else if (idLow.indexOf(q) === 0) score = 5000;
|
|
239
|
+
else if (idLow.indexOf(q) !== -1) score = 2500;
|
|
240
|
+
else {
|
|
241
|
+
let titleHits = 0;
|
|
242
|
+
let snipHits = 0;
|
|
243
|
+
let allHit = true;
|
|
244
|
+
for (const w of words) {
|
|
245
|
+
const inTitle = titleLow.indexOf(w) !== -1;
|
|
246
|
+
const inSnip = snipLow.indexOf(w) !== -1;
|
|
247
|
+
if (inTitle) titleHits += 1;
|
|
248
|
+
else if (inSnip) snipHits += 1;
|
|
249
|
+
else { allHit = false; break; }
|
|
250
|
+
}
|
|
251
|
+
if (!allHit) continue;
|
|
252
|
+
score = 1000 + titleHits * 50 + snipHits * 10;
|
|
253
|
+
}
|
|
254
|
+
if (score > 0) scored.push({ row: r, score, needles: words });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
scored.sort((a, b) => {
|
|
258
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
259
|
+
return (a.row.id || '').localeCompare(b.row.id || '');
|
|
260
|
+
});
|
|
261
|
+
return scored.slice(0, 20);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* The LookupModal shell HTML. Rendered once at the top of the shell
|
|
266
|
+
* (outside #rcf-live-content so the SSE innerHTML swap cannot blow it
|
|
267
|
+
* away). The modal is hidden by default; wireLookup toggles it. The
|
|
268
|
+
* results list is rendered client-side after the index fetch resolves,
|
|
269
|
+
* so this emits only the input, the status / empty-state slot, the
|
|
270
|
+
* reload affordance and the live-region hooks screen readers expect.
|
|
271
|
+
*
|
|
272
|
+
* No `<div>` tags are used: the toast container uses `<output>` for
|
|
273
|
+
* exactly this reason - the Phase 3.8 D13a shell invariant checked by
|
|
274
|
+
* `test/view/html-page.test.js` reads the last `</div>` to find the
|
|
275
|
+
* live-content wrapper close, so the modal uses semantic elements
|
|
276
|
+
* (`<aside>` / `<section>` / `<header>` / `<ul>` / `<p>`) throughout.
|
|
277
|
+
*
|
|
278
|
+
* @returns {string}
|
|
279
|
+
*/
|
|
280
|
+
export function renderLookupModal() {
|
|
281
|
+
return `<aside class="rcf-lookup" data-rcf-lookup hidden role="dialog" aria-modal="true" aria-labelledby="rcf-lookup-title">
|
|
282
|
+
<button type="button" class="rcf-lookup-backdrop" data-rcf-lookup-backdrop aria-label="Close id lookup backdrop" tabindex="-1"></button>
|
|
283
|
+
<section class="rcf-lookup-panel" role="document">
|
|
284
|
+
<header class="rcf-lookup-head">
|
|
285
|
+
<h2 id="rcf-lookup-title" class="rcf-lookup-heading">Find any document or AC</h2>
|
|
286
|
+
<button type="button" class="rcf-lookup-close" data-rcf-lookup-close aria-label="Close id lookup">Close</button>
|
|
287
|
+
</header>
|
|
288
|
+
<label class="rcf-lookup-field">
|
|
289
|
+
<span class="rcf-lookup-label">Type an id or title</span>
|
|
290
|
+
<input type="text" class="rcf-lookup-input" data-rcf-lookup-input
|
|
291
|
+
autocomplete="off" spellcheck="false" aria-controls="rcf-lookup-results"
|
|
292
|
+
aria-describedby="rcf-lookup-status" placeholder="REQ-002, US-304, LookupModal, ...">
|
|
293
|
+
</label>
|
|
294
|
+
<p class="rcf-lookup-status" id="rcf-lookup-status" data-rcf-lookup-status role="status" aria-live="polite"></p>
|
|
295
|
+
<ul class="rcf-lookup-results" id="rcf-lookup-results" data-rcf-lookup-results role="listbox" aria-label="Lookup results"></ul>
|
|
296
|
+
<section class="rcf-lookup-empty" data-rcf-lookup-empty hidden>
|
|
297
|
+
<p class="rcf-lookup-empty-msg" data-rcf-lookup-empty-msg>No matches.</p>
|
|
298
|
+
<button type="button" class="rcf-lookup-reload" data-rcf-lookup-reload hidden>Reload index</button>
|
|
299
|
+
</section>
|
|
300
|
+
</section>
|
|
301
|
+
</aside>`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* The SearchButton for the shell's `<div class="tools">`. One
|
|
306
|
+
* <button> with a search mark glyph; opens the LookupModal on click.
|
|
307
|
+
* Carries the Cmd/Ctrl+F mnemonic in the title/aria-label so a
|
|
308
|
+
* keyboard-first reader knows the shortcut.
|
|
309
|
+
*
|
|
310
|
+
* @returns {string}
|
|
311
|
+
*/
|
|
312
|
+
export function renderSearchButton() {
|
|
313
|
+
return `<button type="button" class="rcf-search-btn" data-rcf-search
|
|
314
|
+
aria-label="Find any document or AC (Cmd/Ctrl+F)"
|
|
315
|
+
title="Find any document or AC (Cmd/Ctrl+F)">
|
|
316
|
+
<svg class="rcf-search-icon" aria-hidden="true" viewBox="0 0 20 20" width="16" height="16">
|
|
317
|
+
<circle cx="9" cy="9" r="6" fill="none" stroke="currentColor" stroke-width="1.8"/>
|
|
318
|
+
<path d="M13.5 13.5l4 4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
|
|
319
|
+
</svg>
|
|
320
|
+
<span class="rcf-search-label">Find</span>
|
|
321
|
+
<span class="rcf-search-kbd" aria-hidden="true"><kbd>Ctrl</kbd>+<kbd>F</kbd></span>
|
|
322
|
+
</button>`;
|
|
323
|
+
}
|