rcf-lite 0.31.0 → 0.32.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 +21 -0
- package/blueprints/deploy-cloudflare-workers/contributions/user-stories/deploy-cloudflare-workers-us-12113.json +1 -1
- package/fixtures/canary-manifest.json +9 -9
- package/package.json +1 -1
- package/rcf/code-nodes/cn-017.json +4 -2
- package/rcf/code-nodes/cn-771.json +2 -1
- package/rcf/code-nodes/cn-778.json +14 -0
- package/rcf/fbs/fbs-004.json +4 -1
- package/rcf/fbs/fbs-201.json +2 -1
- package/rcf/tacs/tac-4127-readiness-view.json +3 -2
- package/rcf/test-suites/ts-004.json +82 -2
- package/rcf/test-suites/ts-223.json +24 -2
- package/rcf/user-stories/us-18001.json +6 -1
- package/rcf/user-stories/us-202.json +25 -1
- package/releases/releases.yaml +10 -1
- package/src/cli/freeze.js +8 -0
- package/src/cli/readiness.js +2 -38
- package/src/define/interface-paths.js +47 -0
- package/src/query/gates.js +48 -13
- package/src/view/html-page.js +29 -11
- package/src/view/live-client.js +18 -10
- package/src/view/style.css +10 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.32.0] - 2026-10-06
|
|
10
|
+
|
|
11
|
+
0.32.0 ships four readiness and viewer fixes merged since 0.31.0 (merge ec9464db) as one release. D6 consistency:orphanInterfaces now accepts the schema-canonical object-shape REQ.deliveredBy (issue 301), D3 shapes:pathsResolve inside rcf define freeze pre-resolves TAC interface path tokens the way readiness does (issue 302), the deploy-cloudflare-workers blueprint reworded AC-12113-2 so D6 consistency:unsatisfiable stops reading "absent" as a field name (PR 300), the viewer's SSE tree-update resync preserves the active tab for every main tab including Readiness (issue 297), and the audit view header keeps the tabs visible under long project names and prefers prd.productName (issue 295). minAgentAction: null; the managed agent-instructions block hash did not move.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **D6 consistency:orphanInterfaces now accepts the schema-canonical object-shape REQ.deliveredBy (issue 301, w-2026-10-06-dave-020).** `collectReachedInterfaces` in `src/query/gates.js` previously accepted only array-shape `deliveredBy` and dropped the `{tacId, adrId?, field?}` object form that `@stravica-ai/rcf-schemas` 0.6.3 defines as canonical. Every schema-conforming REQ (including every blueprint-contributed REQ) therefore had its `deliveredBy` silently skipped by the orphanInterfaces reach-walk, and interfaces on blueprint TACs with no AC ownerRef read as orphans. Hit on ai-baseline define/kickoff (115/173 failing on nine applied blueprints). Fix: the walk now also handles the object form, routing `field: interfaces[<name>]` to a single interface via `parseOwnerRefInterfaceField` and a bare `tacId` to every interface on the named TAC (the "delivered by this TAC" meaning carried over from the array-shape bare pointer). Legacy array-shape pointers are unchanged. Regression fixtures in `test/query/gates.test.js` cover the object-shape interface pointer, the bare-tacId object form, and the legacy array shape.
|
|
17
|
+
|
|
18
|
+
- **D3 shapes:pathsResolve inside `rcf define freeze` now pre-resolves TAC interface `path:` tokens the way `rcf define readiness` does (issue 302, w-2026-10-06-dave-022).** `src/cli/freeze.js` called `computeReadiness` without `resolvedPaths`, so the D3 gate fell back to an empty set and false-failed every repo-relative `path:` token the engineer named on a TAC interface description, even when the file existed on disk and the readiness CLI (which pre-resolves via `resolveInterfacePaths` in `src/cli/readiness.js`) passed D3 4/4. Hit on ai-baseline define/kickoff: readiness green on D3, freeze refused with four shapes:pathsResolve findings. Fix: lifted `resolveInterfacePaths` out of `src/cli/readiness.js` into `src/define/interface-paths.js` so freeze can share it. Freeze now runs the resolve walk against `projectRoot` before `computeReadiness` and passes the resulting `resolvedPaths` set to the compute. The pure gate reads it unchanged. Regression test in `test/cli/freeze.test.js` seeds a TAC interface whose `path:` token points at a real file and asserts freeze succeeds, then flips to a missing path and asserts freeze refuses with exit 4 and surfaces the shapes:pathsResolve finding.
|
|
19
|
+
|
|
20
|
+
- **Blueprint deploy-cloudflare-workers: AC-12113-2 reworded so D6 consistency:unsatisfiable stops reading "absent" as a field name.** The description parenthetical "Cloudflare's runtime treats the absent field as the default asset-lookup-first shape" tripped extractThenFieldTokens pattern 2 (`<name> field`) which captured "absent" and no recordShape defines that field. Reworded to "treats a missing run_worker_first as the default asset-lookup-first shape"; meaning preserved, the field reference now names the real WranglerAssetsBlock field. Hit on ai-baseline define/kickoff.
|
|
21
|
+
|
|
22
|
+
- **Viewer: SSE tree-update preserves the active tab for every main tab, Readiness included (issue 297, w-2026-10-06-dave-003).** `resyncTabsAfterSwap` in `src/view/live-client.js` hard-coded a five-entry TABS array (`overview`, `requirements`, `architecture`, `build`, `product-map`) that omitted `readiness`. When Readiness was the active tab and an SSE tree-update swapped `#rcf-live-content` innerHTML (which happens about every 30s since issue 236 writes a heartbeat into `rcf/manifest.json`), the resync `indexOf` returned -1, fell back to Overview, unhid `#tab-overview` and never visited `#tab-readiness`, so both panels were visible. The viewer's standalone context still recovered a tick later when `rcfPage.init()` re-resolved the hash, but WESPA's admin SPA embed (`iframe /rcf-viewer/?embed=1&theme=...`, no hash) saw the Readiness tab silently switch to Overview. The parallel array was removed: the tab list is now derived from the server-rendered `nav.tabs` itself (every `[role="tab"][data-tab]` present at swap time) so a future tab added to the renderer cannot be forgotten by live-client. The overview fallback stays for a genuinely unknown or missing selection. AC-18001-8, CN-778. Issue 236 (heartbeat into git-tracked manifest.json) is unchanged.
|
|
23
|
+
- **Chain: live-client resync responsibility named on TAC-4127-readiness-view.** The TAC previously named `src/view/html-page.js` (now `src/view/page-init.js`) as the owner of the viewer-side tab list but did not name the live-client's post-swap resync. Added a responsibility bullet so a future reader finds both ends of the chain from one node. The TAC's substantive behaviour is unchanged.
|
|
24
|
+
- **Audit view header: tabs always visible under long project names (issue 295).** Dex (WSD) reported on the WESPA project (`manifest.projectName` = `WESPA - WSD Enterprise Semantic Platform for Analytics`, 54 chars) that the sixth main tab (Build) was clipped at 1440 and 1920 and the Find button in `.tools` sat on top of it. The .bar is `max-width: 1280px` with flex children `.brand`, `.product`, `[role=tablist]`, `.tools`; the pre-fix tablist carried `flex: 1 1 auto; min-width: 0; overflow-x: auto` so it was free to shrink below its intrinsic width while `.product` did not yield. The fix moves the shrink budget onto `.product` (via `min-width: 0` on both the flex item and on `.product .name`, so `text-overflow: ellipsis` triggers inside the flex container) and pins `nav.tabs` and `.tools` to `flex-shrink: 0` so the six tabs and the tools group hold their intrinsic widths. `.product .name` now also carries `title="<full projectName>"` so the ellipsis is reversible on hover; the attribute is HTML-escaped on the same path as the inner text. The fix holds in embed and non-embed, light and dark, with the 0.31.0 theme control present. `src/view/style.css`, `src/view/html-page.js`; US-202 gains AC-202-4 (layout rules) and AC-202-5 (title attribute); TS-004 gains the matching passing TCs; CN-017 and CN-771 updated to implement the new ACs; FBS-004 `acIds` updated.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **Audit view header: project display name prefers `prd.productName` (issue 295).** Pre-fix the header read `manifest.projectName`, then fell back to `prd.productName` and `prd.productTitle`. `rcf define update MANIFEST` is not addressable (the manifest has no id in the family grammar), so `manifest.projectName` was a one-shot init value and the only way to rename the audit chrome without re-initialising was a hand edit of `manifest.json`. `prd.productName` is editable via `rcf define update PRD-001 --set productName=<value>`; preferring it in the header makes the display name lite-native without touching the manifest, which keeps owning ids and paths. `src/view/html-page.js` `resolveProductHeader`; US-202 gains AC-202-6; TS-004 gains the matching passing TCs; CN-017 updated. Follow-up for Dave: a lite-native `rcf define update MANIFEST --set projectName=<value>` (Part 2 cut a from the brief) is a separate, larger change (manifest is not a family id; the update verb would grow a MANIFEST special-case alongside the one `rcf define read` already carries) and is NOT shipped here.
|
|
29
|
+
|
|
9
30
|
## [0.31.0] - 2026-10-05
|
|
10
31
|
|
|
11
32
|
0.31.0 ships three themes from rcf-lite PRs 291, 292 and 293. The viewer gains a standalone Light | Dark | Auto theme control (ADR-4137), readable FIND and LookupModal hover and focus states in dark, and a WCAG 2.1 AA contrast audit test that blocks a merge dropping any colour pair below the floor in either theme. DEFINE step 3 rulings R1 to R8 and R13 land, with every tree-wide readiness check evaluated before any scope short-circuit through one shared helper (ADR-4138). And `--id` write-backs are idempotent: `rcf define ledger <name> add --id` and `rcf define create <kind> --id` treat a same-id replay with identical content as a no-op. Breaking under the pre-1.0 convention for three reasons: `rcf define create <kind> --id` and `rcf define ledger <name> add --id` now exit 3 on same-id-different-content; the readiness envelope for a scope-empty stage with a failing tree-wide check is `failing` (or `acknowledged` when acked at the current hash on a warn-with-ack stage), not `notApplicable`; and `shapes:draftSettled` is tree-wide, so a `[draft]` marker outside the D3 scope now fails the check. The managed agent-instructions block did not change, so no agent re-init is needed (minAgentAction null).
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"id": "AC-12113-2",
|
|
24
|
-
"description": "Given the operator answers run-worker-first with true alongside a non-empty assets-directory, when the manifest is generated, then the [assets] block carries run_worker_first = true. Given the operator answers run-worker-first with false or leaves it unanswered, when the manifest is generated, then the [assets] block does NOT carry a run_worker_first key (Cloudflare's runtime treats
|
|
24
|
+
"description": "Given the operator answers run-worker-first with true alongside a non-empty assets-directory, when the manifest is generated, then the [assets] block carries run_worker_first = true. Given the operator answers run-worker-first with false or leaves it unanswered, when the manifest is generated, then the [assets] block does NOT carry a run_worker_first key (Cloudflare's runtime treats a missing run_worker_first as the default asset-lookup-first shape). Anchored to TAC-1302; probed by assets-manifest-scan.mjs with anchorAcId AC-12113-1 (the same module surfaces both facets of the shape).",
|
|
25
25
|
"given": "an applied fixture whose operator answered assets-directory = ./dist and run-worker-first = true",
|
|
26
26
|
"when": "the probe module parses the fixture wrangler.toml",
|
|
27
27
|
"then": "the parsed [assets] table carries run_worker_first equal to true; when the fixture is switched with SIMULATE_EMPTY_ASSETS=true the run_worker_first field is absent (the whole [assets] block is absent) and the probe still returns verdict pass because the bare-Worker shape is legal",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"registerCanary": [
|
|
3
3
|
{
|
|
4
|
-
"id": "rc-2026-10-
|
|
5
|
-
"createdAt": "2026-10-
|
|
6
|
-
"buildVersion": "0.
|
|
4
|
+
"id": "rc-2026-10-06-001",
|
|
5
|
+
"createdAt": "2026-10-06T16:00:03.482Z",
|
|
6
|
+
"buildVersion": "0.32.0-mockdriver",
|
|
7
7
|
"fixturePromptId": "canary-prompt-01",
|
|
8
8
|
"responseWordCount": 55,
|
|
9
9
|
"grades": {
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
|
-
"id": "rc-2026-10-
|
|
38
|
-
"createdAt": "2026-10-
|
|
39
|
-
"buildVersion": "0.
|
|
37
|
+
"id": "rc-2026-10-06-002",
|
|
38
|
+
"createdAt": "2026-10-06T16:00:03.483Z",
|
|
39
|
+
"buildVersion": "0.32.0-mockdriver",
|
|
40
40
|
"fixturePromptId": "canary-prompt-02",
|
|
41
41
|
"responseWordCount": 55,
|
|
42
42
|
"grades": {
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
|
-
"id": "rc-2026-10-
|
|
71
|
-
"createdAt": "2026-10-
|
|
72
|
-
"buildVersion": "0.
|
|
70
|
+
"id": "rc-2026-10-06-003",
|
|
71
|
+
"createdAt": "2026-10-06T16:00:03.483Z",
|
|
72
|
+
"buildVersion": "0.32.0-mockdriver",
|
|
73
73
|
"fixturePromptId": "canary-prompt-03",
|
|
74
74
|
"responseWordCount": 55,
|
|
75
75
|
"grades": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rcf-lite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "One-install tooling for the Requirements Confidence Framework (RCF): the unified `rcf` CLI grouped into the five RCF tool groups (discover, define, build, verify, audit) plus a small core set (init, doctor, guidance, mcp), an MCP server, the live tree viewer and the fresh-context adversarial ship-gate verifier. Consumes @stravica-ai/rcf-schemas.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
"implementsAcIds": [
|
|
7
7
|
"AC-202-1",
|
|
8
8
|
"AC-202-2",
|
|
9
|
-
"AC-202-3"
|
|
9
|
+
"AC-202-3",
|
|
10
|
+
"AC-202-5",
|
|
11
|
+
"AC-202-6"
|
|
10
12
|
],
|
|
11
13
|
"dependencies": [],
|
|
12
14
|
"version": "0.1.0",
|
|
13
15
|
"status": "draft",
|
|
14
16
|
"createdAt": "2026-07-10T11:15:32.956Z",
|
|
15
|
-
"updatedAt": "2026-
|
|
17
|
+
"updatedAt": "2026-10-06T10:24:19.149Z"
|
|
16
18
|
}
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
"title": "SearchButton hover/focus/active + three-state theme control styling (AA contrast in both themes)",
|
|
5
5
|
"description": "Baz 2026-10-04 (w-2026-10-04-dave-001): the FIND SearchButton hover previously fell back to a hard-coded light grey (`var(--sv-surface-hover, #f0f3f8)`) because `--sv-surface-hover` was never declared, which painted dark ink on a light grey in dark mode. The hover + focus-visible + active states now use `--sv-active` with the `--sv-control` border and the `--sv-focus` outline so the pair stays above WCAG AA in both themes (AC-205-9). The three-state theme control (`.rcf-theme`, `.rcf-theme-btn`) sits right of the SearchButton in `.tools`; `aria-pressed=\"true\"` uses `--sv-button` / `--sv-onbutton`, hover and focus-visible use `--sv-active`, and the group is hidden under `html[data-embed=\"1\"]`.",
|
|
6
6
|
"implementsAcIds": [
|
|
7
|
+
"AC-202-4",
|
|
7
8
|
"AC-205-9"
|
|
8
9
|
],
|
|
9
10
|
"dependencies": [],
|
|
10
11
|
"version": "0.1.0",
|
|
11
12
|
"status": "draft",
|
|
12
13
|
"createdAt": "2026-10-05T09:59:33Z",
|
|
13
|
-
"updatedAt": "2026-10-
|
|
14
|
+
"updatedAt": "2026-10-06T10:24:19.675Z"
|
|
14
15
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cnId": "CN-778",
|
|
3
|
+
"path": "src/view/live-client.js#resyncTabsAfterSwap",
|
|
4
|
+
"title": "SSE tree-update tab resync derives the tab list from the rendered nav.tabs (SSOT)",
|
|
5
|
+
"description": "After the SSE tree-update swaps `#rcf-live-content` innerHTML, the newly-inserted panels carry the Phase 3.6 server-rendered default (one visible, the rest hidden). resyncTabsAfterSwap reads every [role=\"tab\"][data-tab] under the surviving `nav.tabs`, finds the aria-selected button, and aligns `#tab-<name>` visibility so exactly the active tab is visible. The tab list is derived from the DOM rather than a parallel array in this file: before issue 297 the parallel array omitted 'readiness' and a swap while Readiness was active fell back to Overview with both panels visible. The overview fallback is kept only for a genuinely unknown or missing selection. Pairs with CN-744 and CN-745 (which this file also hosts) and with page-init.js's activateTab for the non-swap routing path (AC-18001-8).",
|
|
6
|
+
"implementsAcIds": [
|
|
7
|
+
"AC-18001-8"
|
|
8
|
+
],
|
|
9
|
+
"dependencies": [],
|
|
10
|
+
"version": "0.1.0",
|
|
11
|
+
"status": "draft",
|
|
12
|
+
"createdAt": "2026-10-06T12:00:00Z",
|
|
13
|
+
"updatedAt": "2026-10-06T12:00:00Z"
|
|
14
|
+
}
|
package/rcf/fbs/fbs-004.json
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"AC-202-1",
|
|
10
10
|
"AC-202-2",
|
|
11
11
|
"AC-202-3",
|
|
12
|
+
"AC-202-4",
|
|
13
|
+
"AC-202-5",
|
|
14
|
+
"AC-202-6",
|
|
12
15
|
"AC-203-1",
|
|
13
16
|
"AC-203-2"
|
|
14
17
|
],
|
|
@@ -30,7 +33,7 @@
|
|
|
30
33
|
"domain": "view",
|
|
31
34
|
"notes": "The page is generated fresh each run; no cached prior state lingers between regenerations.",
|
|
32
35
|
"createdAt": "2026-06-25T00:00:00Z",
|
|
33
|
-
"updatedAt": "2026-
|
|
36
|
+
"updatedAt": "2026-10-06T10:24:20.204Z",
|
|
34
37
|
"buildOrder": 4,
|
|
35
38
|
"executionStatus": "complete",
|
|
36
39
|
"dependsOnFbsIds": [
|
package/rcf/fbs/fbs-201.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"createdAt": "2026-10-01T18:00:00Z",
|
|
3
|
-
"updatedAt": "2026-10-
|
|
3
|
+
"updatedAt": "2026-10-06T12:00:00Z",
|
|
4
4
|
"acIds": [
|
|
5
5
|
"AC-18001-1",
|
|
6
6
|
"AC-18001-2",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"AC-18001-5",
|
|
10
10
|
"AC-18001-6",
|
|
11
11
|
"AC-18001-7",
|
|
12
|
+
"AC-18001-8",
|
|
12
13
|
"AC-18101-1",
|
|
13
14
|
"AC-18101-2",
|
|
14
15
|
"AC-18101-3"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"createdAt": "2026-10-01T18:00:00Z",
|
|
3
|
-
"updatedAt": "2026-10-
|
|
3
|
+
"updatedAt": "2026-10-06T12:00:00Z",
|
|
4
4
|
"tacId": "TAC-4127-readiness-view",
|
|
5
5
|
"prdId": "PRD-001",
|
|
6
6
|
"tadId": "TAD-001",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"src/view/components/findings-list.js: findingsList({ heading, count, items }) returns `<div class=\"rcf-findings-list\"><h4>{heading} <span class=\"rcf-findings-list__count\">{count}</span></h4><ul>...<li><a href={anchor}>{id}</a>: {why}</li>...</ul></div>` with ids capped at 20 and a `... N more suppressed` line beyond.",
|
|
16
16
|
"src/view/components/diff.js: diff(before, after) returns a two-column rendering of two document bodies for the delta list; criterion-level before-and-after is deferred per ADR-4127.",
|
|
17
17
|
"src/view/html-page.js: Readiness button and tabpanel are the first entries; the former Overview button and panel are relabelled PRD; the data-tab=overview and id=tab-overview identifiers are preserved so existing hash anchors still resolve; the inline-script TABS array gains 'readiness' as the first entry (default active tab) and keeps the existing four entries in order.",
|
|
18
|
-
"src/view/index.js: renderModelToPage loads the freeze record, ledgers, test pointers and profile text once, invokes computeReadiness via the pure src/query/ seam, and threads the result into the model so renderPage and renderContent carry one computed object; the SSE tree-update broadcast rewalks and recomputes on every change under rcf/."
|
|
18
|
+
"src/view/index.js: renderModelToPage loads the freeze record, ledgers, test pointers and profile text once, invokes computeReadiness via the pure src/query/ seam, and threads the result into the model so renderPage and renderContent carry one computed object; the SSE tree-update broadcast rewalks and recomputes on every change under rcf/.",
|
|
19
|
+
"src/view/live-client.js: resyncTabsAfterSwap derives the list of tabs from the server-rendered nav.tabs (every [role=tab][data-tab] under it) rather than carrying a parallel array. The SSE swap re-emits every panel with the Phase 3.6 hidden-by-default layout; the resync reads the aria-selected button from the surviving header and makes exactly that panel visible for every tab the server rendered, Readiness included (AC-18001-8). The overview fallback stays for the genuinely-unknown-or-missing selection case."
|
|
19
20
|
],
|
|
20
21
|
"internalStructure": "readiness.js is a sequence of eleven small block builders calling the three shared components; each block consumes exactly one top-level field of the readiness result so a wire-level JSON diff ties to a visible block. The register ordering is picked once at the top via pickRegister (same helper the CLI uses) and threaded down. Hash anchors are built with the same `#<docId>` convention the existing tabs use so blocker-id links open the right tab and expand the right <details> through the existing resolveHash routine.",
|
|
21
22
|
"interfaces": [
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"createdAt": "2026-07-29T12:57:18.038Z",
|
|
3
|
-
"updatedAt": "2026-
|
|
3
|
+
"updatedAt": "2026-10-06T10:12:36.226Z",
|
|
4
4
|
"purpose": "Verify documents render as readable curated fields with working inter-document links. AC-202-1 is pending: its static-files-no-server clause predates the Phase 3.8 server-only view (see test-suites/PENDING.md).",
|
|
5
5
|
"testLevel": "unit",
|
|
6
6
|
"acIds": [
|
|
7
7
|
"AC-202-2",
|
|
8
|
-
"AC-202-3"
|
|
8
|
+
"AC-202-3",
|
|
9
|
+
"AC-202-4",
|
|
10
|
+
"AC-202-5",
|
|
11
|
+
"AC-202-6"
|
|
9
12
|
],
|
|
10
13
|
"id": "TS-004",
|
|
11
14
|
"usId": "US-202",
|
|
@@ -38,6 +41,83 @@
|
|
|
38
41
|
"description": "the view serves from a local server process and never writes files to disk",
|
|
39
42
|
"status": "pending",
|
|
40
43
|
"testPointer": "test/view/cli.test.js::rcf audit view never writes any files to disk (regression against static mode)"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "TC-004-tabs-cannot-shrink",
|
|
47
|
+
"acId": "AC-202-4",
|
|
48
|
+
"description": "style.css nav.tabs has flex-shrink: 0 and no min-width: 0, so the tablist never clips the Build tab under a long project name",
|
|
49
|
+
"status": "passing",
|
|
50
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-4: nav.tabs cannot shrink below its intrinsic width"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "TC-004-tools-cannot-shrink",
|
|
54
|
+
"acId": "AC-202-4",
|
|
55
|
+
"description": "style.css .tools has flex-shrink: 0 so the Find button and theme control never shrink into the tablist",
|
|
56
|
+
"status": "passing",
|
|
57
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-4: .tools cannot shrink below its intrinsic width"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "TC-004-product-yields-first",
|
|
61
|
+
"acId": "AC-202-4",
|
|
62
|
+
"description": "style.css .product has min-width: 0 so it yields to the tablist and tools before clipping anything",
|
|
63
|
+
"status": "passing",
|
|
64
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-4: .product yields first via min-width:0"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "TC-004-name-min-width-zero",
|
|
68
|
+
"acId": "AC-202-4",
|
|
69
|
+
"description": "style.css .product .name has min-width: 0 so text-overflow: ellipsis triggers inside the flex .product",
|
|
70
|
+
"status": "passing",
|
|
71
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-4: .product .name has min-width:0 so the ellipsis triggers"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "TC-004-name-title-attribute",
|
|
75
|
+
"acId": "AC-202-5",
|
|
76
|
+
"description": "renderPage emits a title=\"<full name>\" attribute on .product .name so hover reveals the whole name under ellipsis",
|
|
77
|
+
"status": "passing",
|
|
78
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-5: renderPage emits title=\"<full name>\" on .product .name"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "TC-004-name-title-escaped",
|
|
82
|
+
"acId": "AC-202-5",
|
|
83
|
+
"description": "the title attribute shares the escapeHtml path with the inner text so a projectName of \"<script>\" renders as title=\"<script>\"",
|
|
84
|
+
"status": "passing",
|
|
85
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-5: the title attribute is HTML-escaped alongside the body"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "TC-004-name-title-absent-on-collapse",
|
|
89
|
+
"acId": "AC-202-5",
|
|
90
|
+
"description": "no title attribute is emitted when the product block collapses (no projectName)",
|
|
91
|
+
"status": "passing",
|
|
92
|
+
"testPointer": "test/view/shell-header-layout.test.js::AC-202-5: no title attribute when the product block collapses"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "TC-004-prd-productname-wins",
|
|
96
|
+
"acId": "AC-202-6",
|
|
97
|
+
"description": "resolveProductHeader prefers prd.productName over manifest.projectName when both are set",
|
|
98
|
+
"status": "passing",
|
|
99
|
+
"testPointer": "test/view/product-name-precedence.test.js::AC-202-6: prd.productName wins over manifest.projectName when both are set"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": "TC-004-manifest-fallback",
|
|
103
|
+
"acId": "AC-202-6",
|
|
104
|
+
"description": "resolveProductHeader falls back to manifest.projectName when the PRD carries no productName",
|
|
105
|
+
"status": "passing",
|
|
106
|
+
"testPointer": "test/view/product-name-precedence.test.js::AC-202-6: manifest.projectName is the first fallback when PRD has no productName"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"id": "TC-004-prd-producttitle-fallback",
|
|
110
|
+
"acId": "AC-202-6",
|
|
111
|
+
"description": "resolveProductHeader falls back to prd.productTitle when neither productName nor manifest is set",
|
|
112
|
+
"status": "passing",
|
|
113
|
+
"testPointer": "test/view/product-name-precedence.test.js::AC-202-6: prd.productTitle is the last fallback (brief-stage trees without productName or manifest)"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "TC-004-render-reflects-precedence",
|
|
117
|
+
"acId": "AC-202-6",
|
|
118
|
+
"description": "the renderPage DOM reflects the flipped precedence; the manifest value does not reach the header slice when prd.productName is set",
|
|
119
|
+
"status": "passing",
|
|
120
|
+
"testPointer": "test/view/product-name-precedence.test.js::AC-202-6: renderPage reflects the flipped precedence in the DOM"
|
|
41
121
|
}
|
|
42
122
|
],
|
|
43
123
|
"status": "draft"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"createdAt": "2026-10-01T18:00:00Z",
|
|
3
|
-
"updatedAt": "2026-10-
|
|
4
|
-
"purpose": "Cover US-18001 (Readiness as tab 1 and the PRD rename) and US-18101 (shared pill, findings-list and diff components). Shares the readiness fixture builders with TS-222 so the tab and the CLI assertions ride the same shapes.",
|
|
3
|
+
"updatedAt": "2026-10-06T12:00:00Z",
|
|
4
|
+
"purpose": "Cover US-18001 (Readiness as tab 1 and the PRD rename, plus the live-client resync SSOT guard added for issue 297) and US-18101 (shared pill, findings-list and diff components). Shares the readiness fixture builders with TS-222 so the tab and the CLI assertions ride the same shapes.",
|
|
5
5
|
"testLevel": "unit",
|
|
6
6
|
"acIds": [
|
|
7
7
|
"AC-18001-1",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"AC-18001-5",
|
|
12
12
|
"AC-18001-6",
|
|
13
13
|
"AC-18001-7",
|
|
14
|
+
"AC-18001-8",
|
|
14
15
|
"AC-18101-1",
|
|
15
16
|
"AC-18101-2",
|
|
16
17
|
"AC-18101-3"
|
|
@@ -88,6 +89,27 @@
|
|
|
88
89
|
"description": "No existing Product Map or Requirements test expectation changes; only the Readiness-specific additions land.",
|
|
89
90
|
"status": "pending",
|
|
90
91
|
"testPointer": "test/view/readiness-tab.test.js::readiness tab: AC-18101-3 existing Product Map and Requirements tests retain their expectations"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "TC-223-swap-preserves-readiness",
|
|
95
|
+
"acId": "AC-18001-8",
|
|
96
|
+
"description": "resyncTabsAfterSwap keeps tab-readiness as the sole visible panel when Readiness is aria-selected and the swap left tab-overview visible (the Phase 3.6 server default). Pins issue 297: before the fix, the hard-coded TABS list in live-client.js omitted 'readiness', indexOf returned -1, selected fell back to overview, and both tab-readiness and tab-overview went visible.",
|
|
97
|
+
"status": "passing",
|
|
98
|
+
"testPointer": "test/view/live-client.test.js::AC-18001-8 resyncTabsAfterSwap keeps Readiness visible on the embed-no-hash default (issue 297)"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "TC-223-swap-ssot-every-tab",
|
|
102
|
+
"acId": "AC-18001-8",
|
|
103
|
+
"description": "resyncTabsAfterSwap honours every nav.tabs [role=tab][data-tab] the server rendered as the SSOT for the tab list. Iterating the six main tabs with each as aria-selected and the swap-default visible panel set to tab-overview, exactly the aria-selected tab's panel is visible after resync. Guards against future drift: a seventh tab added to the renderer survives a swap without an edit on live-client.",
|
|
104
|
+
"status": "passing",
|
|
105
|
+
"testPointer": "test/view/live-client.test.js::AC-18001-8 resyncTabsAfterSwap preserves every tab the DOM renders (SSOT invariant)"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "TC-223-swap-product-map-regression",
|
|
109
|
+
"acId": "AC-18001-8",
|
|
110
|
+
"description": "resyncTabsAfterSwap preserves product-map as the sole visible panel through a swap. Guards the one tab that the pre-issue-297 TABS list did include, so the SSOT rewrite does not regress the behaviour it previously had right.",
|
|
111
|
+
"status": "passing",
|
|
112
|
+
"testPointer": "test/view/live-client.test.js::AC-18001-8 resyncTabsAfterSwap preserves Product Map through a swap (regression guard)"
|
|
91
113
|
}
|
|
92
114
|
],
|
|
93
115
|
"status": "draft"
|
|
@@ -44,6 +44,11 @@
|
|
|
44
44
|
"id": "AC-18001-7",
|
|
45
45
|
"description": "[happy] Given the tab and the CLI --json output for the same tree hash, when compared, then every id rendered on the tab appears in the JSON for that hash.",
|
|
46
46
|
"testable": true
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "AC-18001-8",
|
|
50
|
+
"description": "[must-not] Given any main tab (readiness, overview, requirements, architecture, build, product-map) is the active tab, when an SSE tree-update swaps #rcf-live-content innerHTML and resyncTabsAfterSwap runs, then exactly one tabpanel is visible and it is the active tab's panel. The live-client derives the tab list from the server-rendered nav.tabs rather than carrying a parallel array that can drift from page-init: every nav.tabs [role=tab][data-tab] present at swap time is honoured. Readiness is covered as issue 297's regression.",
|
|
51
|
+
"testable": true
|
|
47
52
|
}
|
|
48
53
|
],
|
|
49
54
|
"tacIds": [
|
|
@@ -51,5 +56,5 @@
|
|
|
51
56
|
],
|
|
52
57
|
"tags": [],
|
|
53
58
|
"createdAt": "2026-10-01T18:00:00Z",
|
|
54
|
-
"updatedAt": "2026-10-
|
|
59
|
+
"updatedAt": "2026-10-06T12:00:00Z"
|
|
55
60
|
}
|
|
@@ -33,8 +33,32 @@
|
|
|
33
33
|
"when": "a document links to another",
|
|
34
34
|
"then": "the link navigates to that document's section",
|
|
35
35
|
"testable": true
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "AC-202-4",
|
|
39
|
+
"description": "[happy] The audit view header keeps every main tab visible and the .tools group right of them at any viewport the chrome supports: the tablist and .tools cannot shrink below their intrinsic width (flex-shrink: 0) and .product carries the shrink budget via min-width: 0 (both on the flex item and on .product .name) so the project name ellipsizes first.",
|
|
40
|
+
"given": "the viewer rendered in the standalone (non-embed) shell",
|
|
41
|
+
"when": "any viewport >= 721px the chrome supports (above the responsive @media that re-rows the tabs under 720px)",
|
|
42
|
+
"then": "the six main tabs are fully visible and the .tools group sits right of them; .product .name ellipsizes when the project name is longer than its slot and nothing in .tools overlaps a clipped tab",
|
|
43
|
+
"testable": true
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "AC-202-5",
|
|
47
|
+
"description": "[happy] The .product .name span carries a title=\"<full name>\" attribute so hovering reveals the whole name when the ellipsis has clipped it; the title is HTML-escaped on the same path as the inner text, and when the product block collapses (no project name on the tree) no title attribute is emitted.",
|
|
48
|
+
"given": "a project tree with a projectName",
|
|
49
|
+
"when": "the viewer renders the header",
|
|
50
|
+
"then": "the .name span has title=\"<the full projectName>\" with the same HTML-escaping as the inner text",
|
|
51
|
+
"testable": true
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "AC-202-6",
|
|
55
|
+
"description": "[happy] The header display name prefers prd.productName, then manifest.projectName, then prd.productTitle. A rename via `rcf define update PRD-001 --set productName=...` therefore reaches the audit chrome without touching manifest.projectName (which the chain keeps for ids and paths).",
|
|
56
|
+
"given": "a tree where both manifest.projectName and prd.productName are set",
|
|
57
|
+
"when": "resolveProductHeader runs",
|
|
58
|
+
"then": "the header projectName is prd.productName; when the PRD carries no productName the fallback is manifest.projectName, then prd.productTitle",
|
|
59
|
+
"testable": true
|
|
36
60
|
}
|
|
37
61
|
],
|
|
38
62
|
"createdAt": "2026-06-25T00:00:00Z",
|
|
39
|
-
"updatedAt": "2026-
|
|
63
|
+
"updatedAt": "2026-10-06T10:12:07.288Z"
|
|
40
64
|
}
|
package/releases/releases.yaml
CHANGED
|
@@ -40,8 +40,17 @@
|
|
|
40
40
|
# `npm install rcf-lite`.
|
|
41
41
|
|
|
42
42
|
feedVersion: 1
|
|
43
|
-
latest: "0.
|
|
43
|
+
latest: "0.32.0"
|
|
44
44
|
releases:
|
|
45
|
+
- version: "0.32.0"
|
|
46
|
+
date: "2026-10-06"
|
|
47
|
+
breaking: false
|
|
48
|
+
headlines:
|
|
49
|
+
- "rcf-lite 0.32.0 fixes two readiness and freeze gates that false-tripped under schema-canonical content: D6 consistency:orphanInterfaces now accepts the object-shape REQ.deliveredBy (issue 301), and D3 shapes:pathsResolve inside rcf define freeze pre-resolves TAC interface path tokens the way readiness does (issue 302). The deploy-cloudflare-workers blueprint reworded AC-12113-2 so D6 consistency:unsatisfiable stops reading 'absent' as a field name (PR 300)."
|
|
50
|
+
- "Viewer fixes: SSE tree-update resync preserves the active tab for every main tab including Readiness (issue 297), and the audit view header keeps the six tabs visible under long project names and prefers prd.productName (issue 295)."
|
|
51
|
+
minAgentAction: null
|
|
52
|
+
notesUrl: "https://stravica.ai/docs/rcf/changelog/"
|
|
53
|
+
|
|
45
54
|
- version: "0.31.0"
|
|
46
55
|
date: "2026-10-05"
|
|
47
56
|
breaking: true
|
package/src/cli/freeze.js
CHANGED
|
@@ -35,6 +35,7 @@ import { checkCodeNodeResolution } from '#core/store';
|
|
|
35
35
|
import { findProjectRoot } from '../view/index.js';
|
|
36
36
|
import { loadFreezeRecord, saveFreezeRecord } from '../define/freeze-record.js';
|
|
37
37
|
import { loadAllLedgers } from '../define/ledgers.js';
|
|
38
|
+
import { resolveInterfacePaths } from '../define/interface-paths.js';
|
|
38
39
|
import {
|
|
39
40
|
STAGE_ALIASES,
|
|
40
41
|
STAGE_GATES,
|
|
@@ -368,6 +369,12 @@ async function runFreeze({
|
|
|
368
369
|
// freezeableAfterAck check below is the real gate (freeze still
|
|
369
370
|
// exits 4 on an unfrozen tree); the wrap's refusal is informational
|
|
370
371
|
// here as it is on readiness.
|
|
372
|
+
// Issue 302 (2026-10-06): freeze now pre-resolves the TAC
|
|
373
|
+
// interface `path:` tokens the way readiness does, so the D3
|
|
374
|
+
// `shapes:pathsResolve` check inside freeze agrees with the
|
|
375
|
+
// readiness verdict instead of defaulting to an empty set and
|
|
376
|
+
// false-failing resolvable paths.
|
|
377
|
+
const resolvedPaths = await resolveInterfacePaths(projectRoot, tree);
|
|
371
378
|
/** @type {import('../query/readiness.js').ReadinessResult} */
|
|
372
379
|
const readiness = computeReadiness(tree, {
|
|
373
380
|
freeze: freezeForCompute,
|
|
@@ -375,6 +382,7 @@ async function runFreeze({
|
|
|
375
382
|
profileText,
|
|
376
383
|
testPointers,
|
|
377
384
|
validateErrors,
|
|
385
|
+
resolvedPaths,
|
|
378
386
|
});
|
|
379
387
|
const gated = await runWithAdmissibilityGate({
|
|
380
388
|
tree,
|
package/src/cli/readiness.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// every other query verb has.
|
|
17
17
|
|
|
18
18
|
import { parseArgs } from 'node:util';
|
|
19
|
-
import { readFile
|
|
19
|
+
import { readFile } from 'node:fs/promises';
|
|
20
20
|
import { join } from 'node:path';
|
|
21
21
|
|
|
22
22
|
import { resolveTestPointers, walkTree } from '#core/store';
|
|
@@ -30,9 +30,9 @@ import {
|
|
|
30
30
|
STAGE_ORDER,
|
|
31
31
|
STAGE_SHORT_NAMES,
|
|
32
32
|
ackable,
|
|
33
|
-
extractInterfacePathTokens,
|
|
34
33
|
stagePolicy,
|
|
35
34
|
} from '../query/gates.js';
|
|
35
|
+
import { resolveInterfacePaths } from '../define/interface-paths.js';
|
|
36
36
|
import {
|
|
37
37
|
computeReadiness,
|
|
38
38
|
countLitmusReadersAtHash,
|
|
@@ -129,42 +129,6 @@ const STATE_LABEL = {
|
|
|
129
129
|
notApplicable: 'notApplicable',
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
-
/**
|
|
133
|
-
* ADR-4131 (0.30.0 PR 5): walk the tree's TAC interfaces, extract the
|
|
134
|
-
* `path:` tokens the engineer named, resolve each one against
|
|
135
|
-
* `projectRoot` on disk, and return the set of tokens that resolve.
|
|
136
|
-
* `shapes:pathsResolve` reads the set from the stage context and
|
|
137
|
-
* decides the D3 bite without I/O. The compute is pure; this helper is
|
|
138
|
-
* the one place I/O happens for the check.
|
|
139
|
-
*
|
|
140
|
-
* @param {string} projectRoot
|
|
141
|
-
* @param {import('#core/store/walker.js').TreeModel} tree
|
|
142
|
-
* @returns {Promise<Set<string>>}
|
|
143
|
-
*/
|
|
144
|
-
async function resolveInterfacePaths(projectRoot, tree) {
|
|
145
|
-
const resolved = new Set();
|
|
146
|
-
/** @type {Set<string>} */
|
|
147
|
-
const candidates = new Set();
|
|
148
|
-
for (const tac of tree.tacs ?? []) {
|
|
149
|
-
for (const iface of tac.interfaces ?? []) {
|
|
150
|
-
const desc = typeof iface?.description === 'string' ? iface.description : '';
|
|
151
|
-
for (const token of extractInterfacePathTokens(desc)) {
|
|
152
|
-
candidates.add(token);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
await Promise.all([...candidates].map(async (token) => {
|
|
157
|
-
try {
|
|
158
|
-
await stat(join(projectRoot, token));
|
|
159
|
-
resolved.add(token);
|
|
160
|
-
} catch {
|
|
161
|
-
// Missing path stays out of the set; the gate decides whether
|
|
162
|
-
// `authoredAt: D3` lets it through.
|
|
163
|
-
}
|
|
164
|
-
}));
|
|
165
|
-
return resolved;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
132
|
/**
|
|
169
133
|
* Read `rcf/.identity/profile.md` when it exists; return null on
|
|
170
134
|
* ENOENT. Any other read error surfaces as null so the profile
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Shared define-time helper: resolve the `path:` tokens an engineer
|
|
2
|
+
// named on TAC interfaces against the project root on disk. The pure
|
|
3
|
+
// D3 `shapes:pathsResolve` check reads the resulting set from the
|
|
4
|
+
// stage context; this is the one place I/O happens for the check.
|
|
5
|
+
//
|
|
6
|
+
// Lifted out of `src/cli/readiness.js` in issue 302 (2026-10-06) so
|
|
7
|
+
// the freeze CLI can pre-resolve the same tokens and D3 agrees with
|
|
8
|
+
// readiness instead of false-failing from an empty fallback set.
|
|
9
|
+
// ADR-4131 (0.30.0 PR 5) still names readiness as the origin.
|
|
10
|
+
|
|
11
|
+
import { stat } from 'node:fs/promises';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
|
|
14
|
+
import { extractInterfacePathTokens } from '../query/gates.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Walk the tree's TAC interfaces, extract the `path:` tokens the
|
|
18
|
+
* engineer named, resolve each one against `projectRoot` on disk, and
|
|
19
|
+
* return the set of tokens that resolve.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} projectRoot
|
|
22
|
+
* @param {import('#core/store/walker.js').TreeModel} tree
|
|
23
|
+
* @returns {Promise<Set<string>>}
|
|
24
|
+
*/
|
|
25
|
+
export async function resolveInterfacePaths(projectRoot, tree) {
|
|
26
|
+
const resolved = new Set();
|
|
27
|
+
/** @type {Set<string>} */
|
|
28
|
+
const candidates = new Set();
|
|
29
|
+
for (const tac of tree.tacs ?? []) {
|
|
30
|
+
for (const iface of tac.interfaces ?? []) {
|
|
31
|
+
const desc = typeof iface?.description === 'string' ? iface.description : '';
|
|
32
|
+
for (const token of extractInterfacePathTokens(desc)) {
|
|
33
|
+
candidates.add(token);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
await Promise.all([...candidates].map(async (token) => {
|
|
38
|
+
try {
|
|
39
|
+
await stat(join(projectRoot, token));
|
|
40
|
+
resolved.add(token);
|
|
41
|
+
} catch {
|
|
42
|
+
// Missing path stays out of the set; the gate decides whether
|
|
43
|
+
// `authoredAt: D3` lets it through.
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
return resolved;
|
|
47
|
+
}
|
package/src/query/gates.js
CHANGED
|
@@ -2064,10 +2064,24 @@ function extractThenFieldTokens(thenClause) {
|
|
|
2064
2064
|
/**
|
|
2065
2065
|
* Collect every interface the tree "reaches" from an AC `ownerRef` or
|
|
2066
2066
|
* a REQ `deliveredBy` (REQ-174 companion field). The result is a Set
|
|
2067
|
-
* of `<tacId>:<name>` keys.
|
|
2068
|
-
*
|
|
2069
|
-
*
|
|
2070
|
-
*
|
|
2067
|
+
* of `<tacId>:<name>` keys.
|
|
2068
|
+
*
|
|
2069
|
+
* REQ `deliveredBy` takes two canonical shapes, both accepted here:
|
|
2070
|
+
* 1. The schema-canonical object form `{tacId, adrId?, field?}`
|
|
2071
|
+
* (`@stravica-ai/rcf-schemas` 0.6.3 `$defs.deliveredBy`). When
|
|
2072
|
+
* `tacId` is present:
|
|
2073
|
+
* - `field: interfaces[<name>]` reaches `{tacId}:{name}` (one
|
|
2074
|
+
* interface), via `parseOwnerRefInterfaceField`.
|
|
2075
|
+
* - any other `field`, or no `field`, reaches every interface
|
|
2076
|
+
* on the named TAC (the "delivered by this TAC" meaning).
|
|
2077
|
+
* An `adrId`-only deliveredBy does not reach any TAC interface.
|
|
2078
|
+
* 2. A legacy array of pointer strings: `TAC-nnnn:<name>` reaches
|
|
2079
|
+
* one interface, a bare `TAC-nnnn` reaches every interface on
|
|
2080
|
+
* that TAC.
|
|
2081
|
+
*
|
|
2082
|
+
* Issue 301 (2026-10-06): the array-only guard dropped every
|
|
2083
|
+
* schema-canonical object deliveredBy and false-positived every
|
|
2084
|
+
* blueprint TAC interface.
|
|
2071
2085
|
*
|
|
2072
2086
|
* @param {import('#core/store/walker.js').TreeModel} tree
|
|
2073
2087
|
* @returns {Set<string>}
|
|
@@ -2085,19 +2099,40 @@ function collectReachedInterfaces(tree) {
|
|
|
2085
2099
|
out.add(`${ref.tacId}:${name}`);
|
|
2086
2100
|
}
|
|
2087
2101
|
}
|
|
2088
|
-
// REQ deliveredBy
|
|
2102
|
+
// REQ deliveredBy. Both shapes documented above.
|
|
2089
2103
|
for (const req of tree.requirements ?? []) {
|
|
2090
2104
|
const deliveredBy = /** @type {any} */ (req)?.deliveredBy;
|
|
2091
|
-
if (
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2105
|
+
if (deliveredBy == null) continue;
|
|
2106
|
+
if (Array.isArray(deliveredBy)) {
|
|
2107
|
+
for (const entry of deliveredBy) {
|
|
2108
|
+
if (typeof entry !== 'string' || entry.length === 0) continue;
|
|
2109
|
+
const m = entry.match(/^(TAC-[A-Za-z0-9-]+):(.+)$/);
|
|
2110
|
+
if (m) {
|
|
2111
|
+
out.add(`${m[1]}:${m[2]}`);
|
|
2112
|
+
continue;
|
|
2113
|
+
}
|
|
2114
|
+
// Plain TAC id: every interface on that TAC is reached.
|
|
2115
|
+
const tac = (tree.tacs ?? []).find((t) => t?.tacId === entry);
|
|
2116
|
+
if (!tac) continue;
|
|
2117
|
+
for (const iface of tac.interfaces ?? []) {
|
|
2118
|
+
if (typeof iface?.name === 'string') out.add(`${tac.tacId}:${iface.name}`);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
continue;
|
|
2122
|
+
}
|
|
2123
|
+
if (typeof deliveredBy === 'object') {
|
|
2124
|
+
const tacId = /** @type {any} */ (deliveredBy).tacId;
|
|
2125
|
+
if (typeof tacId !== 'string' || tacId.length === 0) continue;
|
|
2126
|
+
const field = /** @type {any} */ (deliveredBy).field;
|
|
2127
|
+
const name = parseOwnerRefInterfaceField(field);
|
|
2128
|
+
if (name) {
|
|
2129
|
+
out.add(`${tacId}:${name}`);
|
|
2097
2130
|
continue;
|
|
2098
2131
|
}
|
|
2099
|
-
//
|
|
2100
|
-
|
|
2132
|
+
// No field, or a field that does not name an interface: reach
|
|
2133
|
+
// every interface on the named TAC (the "delivered by this TAC"
|
|
2134
|
+
// meaning carried over from the array-shape bare TAC pointer).
|
|
2135
|
+
const tac = (tree.tacs ?? []).find((t) => t?.tacId === tacId);
|
|
2101
2136
|
if (!tac) continue;
|
|
2102
2137
|
for (const iface of tac.interfaces ?? []) {
|
|
2103
2138
|
if (typeof iface?.name === 'string') out.add(`${tac.tacId}:${iface.name}`);
|
package/src/view/html-page.js
CHANGED
|
@@ -843,21 +843,33 @@ function buildSpecFilterText(fbs) {
|
|
|
843
843
|
}
|
|
844
844
|
|
|
845
845
|
/**
|
|
846
|
-
* Viewer UI refresh PR 2 (shell polish, Baz 2026-10-02; decision 17)
|
|
847
|
-
* the
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
846
|
+
* Viewer UI refresh PR 2 (shell polish, Baz 2026-10-02; decision 17)
|
|
847
|
+
* with the issue-295 flip (w-2026-10-06-dave-001): the one-bar header
|
|
848
|
+
* carries the project name only, sourced from `prd.productName` first
|
|
849
|
+
* (the human-readable name an owner edits via `rcf define update
|
|
850
|
+
* PRD-001 --set productName=...`), then `manifest.projectName` (the
|
|
851
|
+
* init-time value kept for ids / paths), then `prd.productTitle` (the
|
|
852
|
+
* PRD title the brief names). Decision 17 remains: project name
|
|
853
|
+
* prominent after the icon mark, tool name out of the bar, no
|
|
854
|
+
* technology chip. When the tree exposes no project name, the whole
|
|
855
|
+
* product block collapses and the brand icon stands on its own
|
|
856
|
+
* (silent empty state; CSS `.product:empty { display:none; }`).
|
|
857
|
+
*
|
|
858
|
+
* The precedence flip is a lite-native cut (US-202, Part 2 cut b):
|
|
859
|
+
* `rcf define update MANIFEST` is not addressable in the current CLI,
|
|
860
|
+
* so before this change manifest.projectName was a one-shot init value
|
|
861
|
+
* and the only way to rename the audit surface was a hand edit of
|
|
862
|
+
* manifest.json. prd.productName is already updatable via `rcf define
|
|
863
|
+
* update PRD-001 --set productName=...`; preferring it in the header
|
|
864
|
+
* means the display name is lite-native without touching the manifest
|
|
865
|
+
* (ids and paths are untouched).
|
|
854
866
|
*
|
|
855
867
|
* @param {import('./tree-model.js').BuiltTreeModel} model
|
|
856
868
|
* @returns {{ projectName: string | null }}
|
|
857
869
|
*/
|
|
858
870
|
export function resolveProductHeader(model) {
|
|
859
|
-
const projectName = model.
|
|
860
|
-
?? model.
|
|
871
|
+
const projectName = model.prd?.productName
|
|
872
|
+
?? model.manifest?.projectName
|
|
861
873
|
?? model.prd?.productTitle
|
|
862
874
|
?? null;
|
|
863
875
|
return { projectName };
|
|
@@ -870,7 +882,13 @@ function renderProductBlock(header) {
|
|
|
870
882
|
// empty state (brief) survives screen-reader output too.
|
|
871
883
|
return '<div class="product"></div>';
|
|
872
884
|
}
|
|
873
|
-
|
|
885
|
+
// w-2026-10-06-dave-001 (issue 295): .product .name clips its text
|
|
886
|
+
// with text-overflow:ellipsis so a long project name yields rather
|
|
887
|
+
// than pushing the tablist off the bar; the title attribute carries
|
|
888
|
+
// the full name so hover reveals it. Both the title and the body go
|
|
889
|
+
// through escapeHtml so the attribute is safe against injection.
|
|
890
|
+
const safe = escapeHtml(header.projectName);
|
|
891
|
+
return `<div class="product"><span class="name" title="${safe}">${safe}</span>
|
|
874
892
|
<span class="sub">review surface</span></div>`;
|
|
875
893
|
}
|
|
876
894
|
|
package/src/view/live-client.js
CHANGED
|
@@ -507,19 +507,27 @@
|
|
|
507
507
|
// the panels back in line with the aria-selected button and re-run
|
|
508
508
|
// Mermaid in the visible one.
|
|
509
509
|
function resyncTabsAfterSwap(doc, win) {
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
|
|
516
|
-
// Scope to
|
|
510
|
+
// Issue 297 (w-2026-10-06-dave-003, AC-18001-8): the authoritative
|
|
511
|
+
// tab list is the server-rendered nav.tabs itself, not a parallel
|
|
512
|
+
// array that can drift. Earlier the list was hand-copied here and
|
|
513
|
+
// omitted 'readiness' (added as tab 1 in US-18001); a Readiness
|
|
514
|
+
// SSE swap fell back to Overview and both panels went visible.
|
|
515
|
+
// Deriving from the DOM means a future tab cannot be forgotten
|
|
516
|
+
// the same way. Scope to nav.tabs to avoid matching pm-group-btn
|
|
517
517
|
// nodes (they also carry role="tab").
|
|
518
|
+
var buttons = doc.querySelectorAll('nav.tabs [role="tab"][data-tab]');
|
|
519
|
+
var names = [];
|
|
520
|
+
for (var b = 0; b < buttons.length; b += 1) {
|
|
521
|
+
var n = buttons[b].getAttribute ? buttons[b].getAttribute('data-tab') : null;
|
|
522
|
+
if (n) names.push(n);
|
|
523
|
+
}
|
|
518
524
|
var selectedBtn = doc.querySelector('nav.tabs [role="tab"][aria-selected="true"]');
|
|
519
525
|
var selected = selectedBtn && selectedBtn.getAttribute ? selectedBtn.getAttribute('data-tab') : null;
|
|
520
|
-
if (!selected ||
|
|
521
|
-
|
|
522
|
-
|
|
526
|
+
if (!selected || names.indexOf(selected) === -1) {
|
|
527
|
+
selected = names.indexOf('overview') !== -1 ? 'overview' : (names[0] || 'overview');
|
|
528
|
+
}
|
|
529
|
+
for (var i = 0; i < names.length; i += 1) {
|
|
530
|
+
var name = names[i];
|
|
523
531
|
var panel = doc.getElementById('tab-' + name);
|
|
524
532
|
if (!panel) continue;
|
|
525
533
|
if (name === selected) panel.removeAttribute('hidden');
|
package/src/view/style.css
CHANGED
|
@@ -236,6 +236,10 @@ header.app-header .bar {
|
|
|
236
236
|
color: var(--sv-ink);
|
|
237
237
|
overflow: hidden;
|
|
238
238
|
text-overflow: ellipsis;
|
|
239
|
+
/* w-2026-10-06-dave-001 (issue 295): .product is a flex container; the
|
|
240
|
+
ellipsis only triggers when its child is itself free to shrink below
|
|
241
|
+
intrinsic. min-width:0 unlocks that. */
|
|
242
|
+
min-width: 0;
|
|
239
243
|
}
|
|
240
244
|
|
|
241
245
|
.product .sub {
|
|
@@ -248,8 +252,12 @@ nav.tabs {
|
|
|
248
252
|
display: flex;
|
|
249
253
|
gap: 0.25rem;
|
|
250
254
|
margin-left: 1rem;
|
|
251
|
-
|
|
252
|
-
|
|
255
|
+
/* w-2026-10-06-dave-001 (issue 295): the tablist grows to fill the
|
|
256
|
+
bar but never shrinks below its intrinsic width (flex-shrink:0),
|
|
257
|
+
so every main tab stays visible and the Find button in .tools
|
|
258
|
+
never sits on top of a clipped tab. .product carries the shrink
|
|
259
|
+
budget via its own min-width:0 and the ellipsis on .product .name. */
|
|
260
|
+
flex: 1 0 auto;
|
|
253
261
|
overflow-x: auto;
|
|
254
262
|
-webkit-overflow-scrolling: touch;
|
|
255
263
|
}
|