Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 2 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
5 changes: 5 additions & 0 deletions CHANGELOG.d/2.52.0-leftover-map-compare-reconstruction.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
11 changes: 11 additions & 0 deletions docs/adr/0295-leftover-map-compare-reconstruction.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "2.51.0",
"version": "2.52.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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"),
Expand Down
19 changes: 18 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 })));
Expand Down Expand Up @@ -4066,14 +4070,22 @@ 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,
);
Comment on lines +4073 to +4075

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Comparison payload already carries reconstruction

The comparison payload already returns persisted reconstruction after authorization filtering. Missing and non-finite values remain omitted by the shared formatter.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

const pairAccessibleName = `Open leftover ${pair.pair_kind} pair from comparison: ${pair.post_title} · ${criterion}${
reconstruction
? ` · ${t(LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL)} ${reconstruction}`
: ""
}`;
return (
<li
key={`${row.grouping_kind}:${row.grouping_key}:${pair.pair_kind}:${pair.post_id}:${pair.criterion_code}`}
className="ticket-list-item"
>
<button
className="post-list-item"
aria-label={`Open leftover ${pair.pair_kind} pair from comparison: ${pair.post_title} · ${criterion}`}
aria-label={pairAccessibleName}
onClick={() =>
// Same promise, same landing: the badge tells the
// reader the criterion will be current in Post
Expand All @@ -4092,6 +4104,11 @@ function ReportsPanel({
</span>
<span className="post-badge">{nextAction}</span>
<span className="post-badge">d {pair.leftover_distance.toFixed(2)}</span>
{reconstruction ? (
<span className="post-badge" aria-hidden="true">
{reconstruction}
</span>
) : null}
Comment on lines +4107 to +4111

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Zero reconstruction survives truthiness guard

formatLeftoverMapReconstruction(0) returns a nonempty R̂ 0.00 label. The conditional therefore retains valid zero reconstruction.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

</button>
</li>
);
Expand Down
1 change: 1 addition & 0 deletions frontend/src/i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ describe("i18n", () => {
"Leftover map comparison item coverage",
"Leftover map comparison incomplete posts",
"Leftover map comparison incomplete items",
"Leftover map comparison reconstruction",
"Leftover-map graphic item coverage",
"Leftover map item coverage",
"Leftover map incomplete posts",
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ const TRANSLATIONS: Partial<Record<Locale, Record<string, string>>> = {
"Leftover map comparison item coverage": "잔여 지도 비교 기준 포함 범위",
"Leftover map comparison incomplete posts": "잔여 지도 비교 불완전 글",
"Leftover map comparison incomplete items": "잔여 지도 비교 불완전 기준",
"Leftover map comparison reconstruction": "잔여 지도 비교 재구성",
"Leftover map item coverage": "잔여 지도 기준 포함 범위",
"Leftover map incomplete posts": "잔여 지도 불완전 글",
"Leftover map incomplete items": "잔여 지도 불완전 기준",
Expand Down Expand Up @@ -885,6 +886,7 @@ const TRANSLATIONS: Partial<Record<Locale, Record<string, string>>> = {
"Leftover map comparison item coverage": "残差地图比较准则覆盖范围",
"Leftover map comparison incomplete posts": "残差地图比较不完整帖文",
"Leftover map comparison incomplete items": "残差地图比较不完整准则",
"Leftover map comparison reconstruction": "残差地图比较重建",
"Leftover map item coverage": "残差地图准则覆盖范围",
"Leftover map incomplete posts": "残差地图不完整帖文",
"Leftover map incomplete items": "残差地图不完整准则",
Expand Down Expand Up @@ -1519,6 +1521,7 @@ const TRANSLATIONS: Partial<Record<Locale, Record<string, string>>> = {
"Leftover map comparison item coverage": "残差マップの比較基準対象範囲",
"Leftover map comparison incomplete posts": "残差マップの比較不完全投稿",
"Leftover map comparison incomplete items": "残差マップの比較不完全基準",
"Leftover map comparison reconstruction": "残差マップの比較再構成",
"Leftover map item coverage": "残差マップの基準対象範囲",
"Leftover map incomplete posts": "残差マップの不完全投稿",
"Leftover map incomplete items": "残差マップの不完全基準",
Expand Down Expand Up @@ -2132,6 +2135,7 @@ const TRANSLATIONS: Partial<Record<Locale, Record<string, string>>> = {
"Leftover map comparison item coverage": "Phạm vi so sánh tiêu chí bản đồ phần dư",
"Leftover map comparison incomplete posts": "Bài không đầy đủ so sánh trên bản đồ phần dư",
"Leftover map comparison incomplete items": "Tiêu chí không đầy đủ so sánh trên bản đồ phần dư",
"Leftover map comparison reconstruction": "Tái dựng so sánh bản đồ phần dư",
"Leftover map item coverage": "Phạm vi tiêu chí bản đồ phần dư",
"Leftover map incomplete posts": "Bài không đầy đủ trên bản đồ phần dư",
"Leftover map incomplete items": "Tiêu chí không đầy đủ trên bản đồ phần dư",
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/leftoverMapReconstruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { formatSignedLeftoverValue } from "./leftoverMapUnexplained";
export const LEFTOVER_MAP_RECONSTRUCTION_ACTION =
"Leftover map reconstructs R̂ {value} after IRT main effects. Open this post to read {criterion}.";

export const LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL =
"Leftover map comparison reconstruction";

const RECONSTRUCTION_BADGE = "R\u0302";

export function formatLeftoverMapReconstruction(value: number | null | undefined): string | null {
Expand Down
2 changes: 1 addition & 1 deletion lineageweave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,4 @@
"serialize_lineage_analysis_result",
]

__version__ = "2.51.0"
__version__ = "2.52.0"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
version = "2.51.0"
version = "2.52.0"
description = "Reconstructs git-branch-style lineage DAGs from scattered short records using multi-channel score fusion and LLM adjudication."
readme = "README.md"
license = { text = "MIT" }
Expand Down
15 changes: 15 additions & 0 deletions tests/test_adr_0295_governance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]


def test_reconstruction_comparison_uses_distinct_proposed_adr_0295() -> None:
assert (ROOT / "docs/adr/0294-leftover-map-compare-incomplete-item.md").exists()
path = ROOT / "docs/adr/0295-leftover-map-compare-reconstruction.md"
assert path.exists()
text = path.read_text(encoding="utf-8")
assert text.startswith("# ADR 0295 —")
assert "**Decision status:** Proposed" in text
assert "**Decision status:** Accepted" not in text
assert "accessible name" in text
assert "fast-mlsirm" in text