diff --git a/AGENTS.md b/AGENTS.md index 67c1a19cf..805c548ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -538,3 +538,5 @@ closed-world data-validation boundary for DB-to-RDF projections and is published beside the ontology. ADR 0294 keeps grouping-comparison incomplete-item coverage in the LineageWeave read-model boundary: consume only authorization-filtered persisted coverage for a full grouping, omit partial visibility, and never recompute psychometrics in the client. + +ADR 0295 keeps grouping-comparison reconstruction in the LineageWeave read-model/UI boundary: format only persisted `R̂`, expose the value in the pair button accessible name, and never derive psychometric reconstruction from UI-visible proxies. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 52d2ecb61..98c0c7669 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1091,3 +1091,5 @@ through the unchanged, lock-free similarity-matching fast path; only the rare creation branch serializes. ADR 0294 adds persisted incomplete-item coverage to grouping comparison presentation while the API remains the full-group visibility authority; the frontend formats only an admitted persisted count. + +ADR 0295 adds persisted `R̂` to grouping-comparison pair buttons. The explicit button accessible name carries the reconstruction label/value; the visible duplicate badge is presentation-only. Psychometric computation remains owned by fast-mlsirm. diff --git a/CHANGELOG.d/2.52.0-leftover-map-compare-reconstruction.md b/CHANGELOG.d/2.52.0-leftover-map-compare-reconstruction.md new file mode 100644 index 000000000..9e9baf824 --- /dev/null +++ b/CHANGELOG.d/2.52.0-leftover-map-compare-reconstruction.md @@ -0,0 +1,5 @@ +## 2.52.0 — Grouping comparison leftover-map reconstruction + +- Show persisted leftover-map reconstruction `R̂` on grouping-comparison pair buttons (ADR 0295). +- Include the admitted reconstruction label/value in the button accessible name; keep the visible badge `aria-hidden` so it is not double-announced. +- Missing or non-finite values omit only the reconstruction; do not derive `R̂` from distance, coordinates, residuals, rank, coverage, or any other proxy. diff --git a/CHANGELOG.md b/CHANGELOG.md index 969413fc4..f34327ac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to this project are documented here. Format follows ### Added +- Grouping comparison leftover-pair buttons now expose persisted leftover-map reconstruction `R̂` (ADR 0295 / v2.52.0). The visible badge is decorative to accessibility APIs; the button accessible name carries the reconstruction label and value, so the explicit button `aria-label` cannot suppress the metric. Missing/non-finite `R̂` omits only that value and no client-side proxy is invented. + - Grouping comparison rows now show persisted leftover-map incomplete-item count through `leftoverMapIncompleteItemCount` (ADR 0294 / v2.51.0) only for fully caller-visible persisted groupings. Partial visibility remains omitted by the API; valid zero stays visible, and the UI never derives dropped criteria from scored-minus-used. - Grouping comparison rows now show persisted leftover-map incomplete-post count through `leftoverMapIncompletePostCount` (ADR 0293 / v2.50.0) only for a fully caller-visible persisted grouping. Partial visibility omits the aggregate; valid persisted zero remains visible, and the UI never derives dropped posts from scored-minus-used. diff --git a/CLAUDE.md b/CLAUDE.md index 58c6119de..00cfe1087 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,3 +59,5 @@ changing code, tests, or runtime policy rather than restating anything here. ADR 0294 is the Proposed grouping-comparison incomplete-item presentation contract; preserve server-side full-group visibility gating and do not infer dropped criteria in the frontend. + +ADR 0295 is the Proposed grouping-comparison reconstruction contract: persisted `R̂` only, no client inference, accessible-name exposure required despite the button's explicit aria-label. diff --git a/docs/adr/0295-leftover-map-compare-reconstruction.md b/docs/adr/0295-leftover-map-compare-reconstruction.md new file mode 100644 index 000000000..bfdc0f3b1 --- /dev/null +++ b/docs/adr/0295-leftover-map-compare-reconstruction.md @@ -0,0 +1,11 @@ +# ADR 0295 — Grouping comparison leftover-map reconstruction + +**Decision status:** Proposed + +## Problem +Grouping-comparison pair rows carry persisted `leftover_map_reconstruction`, but the serialized current stack does not expose it. The historical implementation placed a reconstruction badge inside a button that already had an explicit `aria-label`; descendant text therefore did not reliably contribute to the button accessible name. + +## Decision +Format only persisted `leftover_map_reconstruction` through `formatLeftoverMapReconstruction`. Render the visible `R̂` badge, mark that duplicate badge `aria-hidden`, and include the localized comparison-reconstruction label plus formatted value in the pair button accessible name. Missing or non-finite `R̂` omits only this suffix. Never reconstruct `R̂` from distance, coordinates, residuals, unexplained leftover, rank, coverage counts, or any visible-subset proxy. + +This is LineageWeave read-model/UI composition only; fast-mlsirm remains the psychometric owner. ADR status remains Proposed while Draft. Acceptance requires current-head hosted tests, keyboard/screen-reader/browser evidence, canonical eight-locale translation-ledger convergence, independent approval, and normal protected-branch merge. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 1942ff510..76c6db089 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1393,3 +1393,7 @@ historical evidence only. The job-architecture artifact ship originally via ### 2026-09-07 — #825 current-parent reconstruction candidate Exact parent `#824@499d653ed6e9206249e3f3a07518ad3fc01f14bd` lacks the grouping-comparison incomplete-item note while carrying authorization-filtered persisted coverage. ADR 0294 / v2.51.0 adds that buyer-visible read-model presentation and regression. Candidate evidence is not protected-main/release evidence; current-head browser/a11y, canonical eight-locale ledger consumption, independent review, and normal protected merge remain outstanding. + +### 2026-09-07 — #826 exact-parent reconstruction candidate + +Exact parent `#825@b73b10e3079e77f3e62235b2b709dc8a3f450292` omits persisted `R̂` from grouping-comparison pair rows. ADR 0295 / v2.52.0 restores that valid read-model delta and repairs the historical accessibility defect by carrying the label/value in the button accessible name. Candidate evidence is not protected-main/release evidence; current-head browser/screen-reader/a11y, canonical eight-locale ledger consumption, independent review, and normal protected merge remain outstanding. diff --git a/frontend/package.json b/frontend/package.json index 2238b2b96..10d0e822c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.51.0", + "version": "2.52.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 61369d1b6..49dedd06e 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -922,6 +922,7 @@ describe("App, authenticated", () => { criterion_code: "sales_lead_specificity", leftover_distance: 0.12, leftover_residual: 0.4, + leftover_map_reconstruction: 0.248, }, ], leftover_map_coverage: { @@ -4337,6 +4338,10 @@ describe("App, authenticated", () => { name: /open leftover closest pair from comparison: public post/i, }), ).toHaveTextContent("Closest leftover: Public post · sales-lead"); + const reconstructionPair = screen.getByRole("button", { + name: /open leftover closest pair from comparison: public post.*leftover map comparison reconstruction R̂ \+0\.25/i, + }); + expect(reconstructionPair).toHaveTextContent("R̂ +0.25"); await waitFor(() => expect(fetchMock).toHaveBeenCalledWith( expect.stringContaining("/api/reports/thread_group/2026-W02"), diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8c8773619..2d4724e72 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -143,6 +143,10 @@ import { LEFTOVER_MAP_AXIS_BADGE_SHARE, LEFTOVER_MAP_AXIS_BADGE_SINGULAR, } from "./leftoverMapAxisBadge"; +import { + formatLeftoverMapReconstruction, + LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL, +} from "./leftoverMapReconstruction"; import "./App.css"; const AdminPanel = lazy(() => import("./components/AdminPanel").then((module) => ({ default: module.AdminPanel }))); @@ -4066,6 +4070,14 @@ function ReportsPanel({ ? "Open this post to read the criterion it sat farthest from after main effects." : "Open this post to read the criterion it sat closest to after main effects."; const criterion = criterionShortLabel(pair.criterion_code); + const reconstruction = formatLeftoverMapReconstruction( + pair.leftover_map_reconstruction, + ); + const pairAccessibleName = `Open leftover ${pair.pair_kind} pair from comparison: ${pair.post_title} · ${criterion}${ + reconstruction + ? ` · ${t(LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL)} ${reconstruction}` + : "" + }`; return (