From 4f6c8c606e1de32ccf1a5cb6dbf7e875adda1915 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 17 Aug 2026 10:06:48 +0000
Subject: [PATCH 1/2] feat: name leftover pair on the opened post (v0.78.0)
After seed, opening a leftover pair names the leftover criterion and
marks that evaluation row so the buyer can read it next. Same loaded
report evidence applies to a member click of that post. Never invent a
leftover pair.
---
AGENTS.md | 5 +-
ARCHITECTURE.md | 3 +-
CHANGELOG.d/0.78.0-leftover-open-context.md | 9 +++
CHANGELOG.md | 10 +++
docs/adr/0026-leftover-pair-open-context.md | 52 ++++++++++++++++
frontend/package.json | 2 +-
frontend/src/App.css | 8 +++
frontend/src/App.test.tsx | 8 +++
frontend/src/App.tsx | 68 ++++++++++++++++++---
lineageweave/__init__.py | 2 +-
pyproject.toml | 2 +-
11 files changed, 155 insertions(+), 14 deletions(-)
create mode 100644 CHANGELOG.d/0.78.0-leftover-open-context.md
create mode 100644 docs/adr/0026-leftover-pair-open-context.md
diff --git a/AGENTS.md b/AGENTS.md
index c790995c1..8661a0ffa 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -75,12 +75,13 @@ in the same spirit) -- never against real data, per the hard rule above.
against a live local stack (`make up`) and self-skip without one -- see
[README.md](README.md#local-product-stack-docker-compose).
-Period leftover pairs (ADR 0017 / 0018) are computed in
+Period leftover pairs (ADR 0017 / 0018 / 0026) are computed in
`lineageweave/leftover_pairs.py` from the residual after a real
GRM/GPCM score, never invented. Missing cells stay out of the
Gabriel factorization. Closest and farthest post–criterion pairs
persist to `report_leftover_pair` and sit above the member list so
-a click opens that post.
+a click opens that post. The opened post names the leftover
+criterion and marks that evaluation row as the next action.
`frontend/` has its own toolchain (Node pinned via `frontend/mise.toml`,
pnpm via Corepack -- do not add a second Node package manager or a
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index f8a83ceb1..e2e91adc3 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -524,7 +524,8 @@ bank as the dummy high/low band rows, so comparison-strip click
through opens those DAG posts. Report members include the earliest
open ticket title, status lookup label, and due date when one exists. The home page renders
the actual mean θ, the FIPC delta, the CAT-selected item, leftover
-closest/farthest pairs above the member list, and the
+closest/farthest pairs above the member list, leftover-open copy
+on that post (ADR 0026), and the
PU / corp / thread comparison -- never a placeholder. TEPP is unchanged.
## Phase 6b: Knowledge Graph as a real Ontology + Semantic Layer
diff --git a/CHANGELOG.d/0.78.0-leftover-open-context.md b/CHANGELOG.d/0.78.0-leftover-open-context.md
new file mode 100644
index 000000000..83ec29e5d
--- /dev/null
+++ b/CHANGELOG.d/0.78.0-leftover-open-context.md
@@ -0,0 +1,9 @@
+# 0.78.0 — Name leftover pair on the opened post
+
+## Added
+
+- Opening a leftover pair (or the same member from the loaded report)
+ names why that post sat closest to or farthest from a criterion after
+ IRT main effects, and marks the matching evaluation row. After
+ `make seed`, click the closest leftover pair: the popup says to read
+ the sales-lead evaluation next.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6bfcaa28f..c11301aaf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,16 @@ All notable changes to this project are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.78.0] - 2026-08-17
+
+### Added
+
+- Opening a leftover pair (or the same member from the loaded report)
+ names why that post sat closest to or farthest from a criterion after
+ IRT main effects, and marks the matching evaluation row. After
+ `make seed`, click the closest leftover pair: the popup says to read
+ the sales-lead evaluation next.
+
## [0.75.0] - 2026-08-17
### Added
diff --git a/docs/adr/0026-leftover-pair-open-context.md b/docs/adr/0026-leftover-pair-open-context.md
new file mode 100644
index 000000000..b49cedc72
--- /dev/null
+++ b/docs/adr/0026-leftover-pair-open-context.md
@@ -0,0 +1,52 @@
+# ADR 0026 — Name leftover pair context on the opened post
+
+**Decision status:** Accepted
+**Date:** 2026-08-17
+
+## Context
+
+ADR 0018 puts closest and farthest leftover pairs above the period-report
+member list. Clicking a pair opened the post, but the popup did not name
+why the buyer landed there. The evaluation list showed criterion scores
+without marking the leftover criterion, so “Open this post to read the
+criterion it sat closest to” had no next action on the destination.
+
+## Decision
+
+When the loaded period-report payload includes leftover pairs for the
+open post, the popup names that leftover map result and marks the
+matching evaluation row.
+
+1. `ReportsPanel` lifts authorized `leftover_pairs` into `PostList`.
+ A report fetch error clears the list — never an invented pair.
+2. The popup status is
+ `This post sat closest to {criterion} after main effects. Read that
+ evaluation row next.` (or `farthest from`).
+3. The evaluation row whose `criterion_code` matches a leftover pair
+ shows `Closest leftover` or `Farthest leftover`. Other rows stay
+ unmarked.
+4. Opening the same post from the member list, home list, or leftover
+ button uses the same loaded report evidence. A post that is not a
+ leftover pair shows no leftover copy.
+
+Do not invent leftover numbers. Do not persist a second leftover store.
+Do not mix this into #74 or #92.
+
+## Consequences
+
+After `make seed`, opening the closest leftover pair (or that same
+member) names the leftover criterion and points at the evaluation row.
+Mean θ stays on the report panel. Rankings stay on ADR 0024. TEPP stays
+on #214.
+
+## Related
+
+Depends on [ADR 0017](0017-persist-lsirm-leftover-pairs.md) and
+[ADR 0018](0018-leftover-pair-report-ui.md).
+
+## References
+
+Jeon, M., Jin, I. H., Schweinberger, M., & Baugh, S. (2021). Mapping
+unobserved item–respondent interactions: A latent space item response
+model with interaction map. *Psychometrika, 86*(2), 378–403.
+https://doi.org/10.1007/s11336-021-09762-5
diff --git a/frontend/package.json b/frontend/package.json
index 575b7c586..eef0c8735 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
- "version": "0.75.0",
+ "version": "0.78.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/App.css b/frontend/src/App.css
index 76cf3665c..449c3aeed 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -103,6 +103,14 @@
opacity: 0.7;
}
+.leftover-open-notice {
+ margin: 1rem 0 0;
+ padding: 0.75rem 1rem;
+ border: 1px solid #3333;
+ border-radius: 8px;
+ font-size: 0.95rem;
+}
+
.popup-section {
margin-top: 1.5rem;
padding-top: 1rem;
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index a32a26403..7202a1c80 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -1417,6 +1417,11 @@ describe("App, authenticated", () => {
await screen.findByRole("button", { name: /open leftover closest pair: public post/i }),
);
await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument());
+ expect(screen.getByRole("status")).toHaveTextContent(
+ "This post sat closest to sales-lead after main effects. Read that evaluation row next.",
+ );
+ expect(screen.getByText(/Sales-lead specificity: 3/)).toHaveTextContent("Closest leftover");
+ expect(screen.getByText(/Constructive stance: 2/)).not.toHaveTextContent("leftover");
});
it("opens Event Lineage, Keyman, and evaluation from a report member click", async () => {
@@ -1425,6 +1430,9 @@ describe("App, authenticated", () => {
await userEvent.click(await screen.findByRole("button", { name: /open report post: public post/i }));
await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument());
+ expect(screen.getByRole("status")).toHaveTextContent(
+ "This post sat closest to sales-lead after main effects. Read that evaluation row next.",
+ );
expect(screen.getByText("Constructive stance: 2")).toBeInTheDocument();
expect(screen.getAllByText(/Ada West/).length).toBeGreaterThan(0);
expect(screen.getAllByLabelText("A-100 lineage").length).toBeGreaterThanOrEqual(2);
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 6056e5eb4..e9a47f701 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -42,6 +42,7 @@ import {
type IssueTicket,
type LineageGraph,
type Keyman,
+ type LeftoverPair,
type LinkedPostRef,
type PostAiSummary,
type PostDetail,
@@ -82,6 +83,26 @@ function criterionShortLabel(itemCode: string): string {
return CRITERION_SHORT_LABEL[itemCode] ?? itemCode;
}
+function leftoverKindPhrase(pairKind: string): "closest to" | "farthest from" {
+ return pairKind === "farthest" ? "farthest from" : "closest to";
+}
+
+function leftoverOpenCopy(pair: LeftoverPair): string {
+ const criterion = criterionShortLabel(pair.criterion_code);
+ return (
+ `This post sat ${leftoverKindPhrase(pair.pair_kind)} ${criterion} after main effects. ` +
+ "Read that evaluation row next."
+ );
+}
+
+function leftoverPairsForPost(pairs: LeftoverPair[], postId: string): LeftoverPair[] {
+ return pairs.filter((pair) => pair.post_id === postId);
+}
+
+function leftoverRowLabel(pairKind: string): string {
+ return pairKind === "farthest" ? "Farthest leftover" : "Closest leftover";
+}
+
// This popup's layout follows the textual product brief (Korean summary,
// key events, R&R, Event Lineage, Keyman, in-popup chat with a sliding
// evidence panel) rather than the referenced Figma frame's actual pixel
@@ -735,12 +756,14 @@ function EvaluationPanel({
postId,
accessToken,
responses,
+ leftoverPairs,
canExtract,
onEvaluated,
}: {
postId: string;
accessToken: string;
responses: EvaluationResponse[] | null;
+ leftoverPairs: LeftoverPair[];
canExtract: boolean;
onEvaluated: (rows: EvaluationResponse[]) => void;
}) {
@@ -786,11 +809,17 @@ function EvaluationPanel({
Not yet evaluated.
) : (
- {responses.map((row) => (
- -
- {row.criterion_label ?? row.criterion_code}: {row.response_category}
-
- ))}
+ {responses.map((row) => {
+ const leftover = leftoverPairs.find((pair) => pair.criterion_code === row.criterion_code);
+ return (
+ -
+ {row.criterion_label ?? row.criterion_code}: {row.response_category}
+ {leftover && (
+ {leftoverRowLabel(leftover.pair_kind)}
+ )}
+
+ );
+ })}
)}
@@ -1087,6 +1116,7 @@ function PostDetailPopup({
accessToken,
canExtract,
graph,
+ leftoverPairs,
onClose,
onSelectPost,
}: {
@@ -1094,6 +1124,7 @@ function PostDetailPopup({
accessToken: string;
canExtract: boolean;
graph: LineageGraph | null;
+ leftoverPairs: LeftoverPair[];
onClose: () => void;
onSelectPost?: (postId: string) => void;
}) {
@@ -1169,6 +1200,11 @@ function PostDetailPopup({
{new Date(post.created_at).toLocaleString()}
{post.post_body}
+ {leftoverPairs.length > 0 && (
+
+ {leftoverPairs.map((pair) => leftoverOpenCopy(pair)).join(" ")}
+
+ )}
요약 (Summary)
@@ -1227,6 +1263,7 @@ function PostDetailPopup({
postId={postId}
accessToken={accessToken}
responses={evaluation}
+ leftoverPairs={leftoverPairs}
canExtract={canExtract}
onEvaluated={(rows) => setEvaluation(rows)}
/>
@@ -1425,10 +1462,12 @@ function ReportsPanel({
accessToken,
canRebuild,
onSelectPost,
+ onLeftoverPairsChange,
}: {
accessToken: string;
canRebuild: boolean;
onSelectPost: (postId: string) => void;
+ onLeftoverPairsChange: (pairs: LeftoverPair[]) => void;
}) {
const [grouping, setGrouping] = useState("process_unit");
const [period, setPeriod] = useState("2026-W02");
@@ -1455,9 +1494,13 @@ function ReportsPanel({
setPayload(reports);
setIndex(periods);
setComparison(compared);
+ onLeftoverPairsChange(reports.reports.flatMap((row) => row.leftover_pairs ?? []));
})
- .catch((err) => setError(String(err)));
- }, [accessToken, grouping, period]);
+ .catch((err) => {
+ setError(String(err));
+ onLeftoverPairsChange([]);
+ });
+ }, [accessToken, grouping, period, onLeftoverPairsChange]);
async function handleRebuild() {
setRebuilding(true);
@@ -1472,8 +1515,10 @@ function ReportsPanel({
setPayload(reports);
setIndex(periods);
setComparison(compared);
+ onLeftoverPairsChange(reports.reports.flatMap((row) => row.leftover_pairs ?? []));
} catch (err) {
setError(String(err));
+ onLeftoverPairsChange([]);
} finally {
setRebuilding(false);
}
@@ -1658,6 +1703,7 @@ function PostList({ accessToken }: { accessToken: string }) {
const [graph, setGraph] = useState(null);
const [error, setError] = useState(null);
const [selectedPostId, setSelectedPostId] = useState(null);
+ const [leftoverPairs, setLeftoverPairs] = useState([]);
const [canRebuild, setCanRebuild] = useState(false);
const [rebuilding, setRebuilding] = useState(false);
const [rebuildError, setRebuildError] = useState(null);
@@ -1691,7 +1737,12 @@ function PostList({ accessToken }: { accessToken: string }) {
<>
-
+
Event Lineage
@@ -1726,6 +1777,7 @@ function PostList({ accessToken }: { accessToken: string }) {
accessToken={accessToken}
canExtract={canRebuild}
graph={graph}
+ leftoverPairs={leftoverPairsForPost(leftoverPairs, selectedPostId)}
onClose={() => setSelectedPostId(null)}
onSelectPost={setSelectedPostId}
/>
diff --git a/lineageweave/__init__.py b/lineageweave/__init__.py
index 1710c009e..2f70d5a2a 100644
--- a/lineageweave/__init__.py
+++ b/lineageweave/__init__.py
@@ -35,4 +35,4 @@
"sentence_excerpts",
]
-__version__ = "0.75.0"
+__version__ = "0.78.0"
diff --git a/pyproject.toml b/pyproject.toml
index 764ebad72..aec6c3d75 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
-version = "0.75.0"
+version = "0.78.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" }
From 0153d639d2b2357897bcd7316307d0c9952352f8 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 17 Aug 2026 19:20:58 +0900
Subject: [PATCH 2/2] feat: focus leftover evaluation row on the opened post
(v0.82.0) (#225)
After the leftover-open banner names the criterion, the matching IRT
row is current and receives keyboard focus so "Read that evaluation
row next" is the next action. No invented leftover number.
---
AGENTS.md | 5 +-
ARCHITECTURE.md | 3 +-
CHANGELOG.d/0.82.0-focus-leftover-eval-row.md | 7 +++
CHANGELOG.md | 9 ++++
.../adr/0027-focus-leftover-evaluation-row.md | 46 +++++++++++++++++++
frontend/package.json | 2 +-
frontend/src/App.test.tsx | 6 ++-
frontend/src/App.tsx | 15 +++++-
lineageweave/__init__.py | 2 +-
pyproject.toml | 2 +-
uv.lock | 2 +-
11 files changed, 90 insertions(+), 9 deletions(-)
create mode 100644 CHANGELOG.d/0.82.0-focus-leftover-eval-row.md
create mode 100644 docs/adr/0027-focus-leftover-evaluation-row.md
diff --git a/AGENTS.md b/AGENTS.md
index 8661a0ffa..0fd53fcb2 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -75,13 +75,14 @@ in the same spirit) -- never against real data, per the hard rule above.
against a live local stack (`make up`) and self-skip without one -- see
[README.md](README.md#local-product-stack-docker-compose).
-Period leftover pairs (ADR 0017 / 0018 / 0026) are computed in
+Period leftover pairs (ADR 0017 / 0018 / 0026 / 0027) are computed in
`lineageweave/leftover_pairs.py` from the residual after a real
GRM/GPCM score, never invented. Missing cells stay out of the
Gabriel factorization. Closest and farthest post–criterion pairs
persist to `report_leftover_pair` and sit above the member list so
a click opens that post. The opened post names the leftover
-criterion and marks that evaluation row as the next action.
+criterion, marks that evaluation row as the next action, and
+moves focus onto it (ADR 0027).
`frontend/` has its own toolchain (Node pinned via `frontend/mise.toml`,
pnpm via Corepack -- do not add a second Node package manager or a
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index e2e91adc3..8870ecd5f 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -525,7 +525,8 @@ through opens those DAG posts. Report members include the earliest
open ticket title, status lookup label, and due date when one exists. The home page renders
the actual mean θ, the FIPC delta, the CAT-selected item, leftover
closest/farthest pairs above the member list, leftover-open copy
-on that post (ADR 0026), and the
+on that post (ADR 0026), leftover evaluation-row focus
+(ADR 0027), and the
PU / corp / thread comparison -- never a placeholder. TEPP is unchanged.
## Phase 6b: Knowledge Graph as a real Ontology + Semantic Layer
diff --git a/CHANGELOG.d/0.82.0-focus-leftover-eval-row.md b/CHANGELOG.d/0.82.0-focus-leftover-eval-row.md
new file mode 100644
index 000000000..f2bcc8aab
--- /dev/null
+++ b/CHANGELOG.d/0.82.0-focus-leftover-eval-row.md
@@ -0,0 +1,7 @@
+# 0.82.0 — Focus leftover evaluation row
+
+## Added
+
+- Opening a leftover pair focuses the matching evaluation row
+ (ADR 0027). After `make seed`, click Closest leftover: keyboard
+ focus lands on the sales-lead row. No invented leftover number.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c11301aaf..859aefbdb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,15 @@ All notable changes to this project are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.82.0] - 2026-08-17
+
+### Added
+
+- Opening a leftover pair focuses the matching IRT evaluation row
+ (ADR 0027). After `make seed`, click Closest leftover: the
+ sales-lead row is current and receives keyboard focus so “Read
+ that evaluation row next” is the next action, not a scroll hunt.
+
## [0.78.0] - 2026-08-17
### Added
diff --git a/docs/adr/0027-focus-leftover-evaluation-row.md b/docs/adr/0027-focus-leftover-evaluation-row.md
new file mode 100644
index 000000000..d42c4ebdd
--- /dev/null
+++ b/docs/adr/0027-focus-leftover-evaluation-row.md
@@ -0,0 +1,46 @@
+# ADR 0027 — Focus the leftover evaluation row on the opened post
+
+**Decision status:** Accepted
+**Date:** 2026-08-17
+
+## Context
+
+ADR 0026 names leftover pair context on the opened post and marks the
+matching evaluation row. The status still says “Read that evaluation
+row next,” but the row stayed wherever it sat in the list. A long
+rubric buries the leftover criterion under the fold.
+
+## Decision
+
+When leftover pairs for the open post match an evaluation row, that
+row is the current evaluation item and receives focus.
+
+1. A leftover-matching row sets `aria-current="true"` and the
+ leftover badge from ADR 0026.
+2. The first leftover pair in the loaded report payload is the
+ focus target. When the host provides layout,
+ `scrollIntoView({ block: "nearest" })` runs; keyboard
+ `tabIndex={-1}` focus always runs. Later leftover rows stay
+ marked, not invented.
+3. A post with no leftover pair, or a row whose criterion does not
+ match, is not current and is not focused.
+
+Do not invent leftover numbers. Do not persist a second leftover
+store. Do not mix this into #74 or #92.
+
+## Consequences
+
+After `make seed`, opening the closest leftover pair moves keyboard
+focus to the sales-lead evaluation row. Mean θ stays on the report
+panel. Rankings stay on ADR 0024. TEPP stays on #214.
+
+## Related
+
+Depends on [ADR 0026](0026-leftover-pair-open-context.md).
+
+## References
+
+Jeon, M., Jin, I. H., Schweinberger, M., & Baugh, S. (2021). Mapping
+unobserved item–respondent interactions: A latent space item response
+model with interaction map. *Psychometrika, 86*(2), 378–403.
+https://doi.org/10.1007/s11336-021-09762-5
diff --git a/frontend/package.json b/frontend/package.json
index eef0c8735..5d3f2e2b8 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
- "version": "0.78.0",
+ "version": "0.82.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 7202a1c80..812c3a56f 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -1420,8 +1420,12 @@ describe("App, authenticated", () => {
expect(screen.getByRole("status")).toHaveTextContent(
"This post sat closest to sales-lead after main effects. Read that evaluation row next.",
);
- expect(screen.getByText(/Sales-lead specificity: 3/)).toHaveTextContent("Closest leftover");
+ const leftoverRow = screen.getByText(/Sales-lead specificity: 3/).closest("li");
+ expect(leftoverRow).toHaveTextContent("Closest leftover");
+ expect(leftoverRow).toHaveAttribute("aria-current", "true");
+ expect(leftoverRow).toHaveFocus();
expect(screen.getByText(/Constructive stance: 2/)).not.toHaveTextContent("leftover");
+ expect(screen.getByText(/Constructive stance: 2/).closest("li")).not.toHaveAttribute("aria-current");
});
it("opens Event Lineage, Keyman, and evaluation from a report member click", async () => {
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index e9a47f701..c77816b10 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -770,12 +770,19 @@ function EvaluationPanel({
const [evaluating, setEvaluating] = useState(false);
const [error, setError] = useState(null);
const [orchestratorOff, setOrchestratorOff] = useState(false);
+ const leftoverRowRef = useRef(null);
+ const focusCriterion = leftoverPairs[0]?.criterion_code ?? null;
useEffect(() => {
setOrchestratorOff(false);
setError(null);
}, [postId]);
+ useEffect(() => {
+ leftoverRowRef.current?.scrollIntoView?.({ block: "nearest" });
+ leftoverRowRef.current?.focus?.();
+ }, [postId, focusCriterion, responses]);
+
async function handleEvaluate() {
setEvaluating(true);
setError(null);
@@ -811,8 +818,14 @@ function EvaluationPanel({
{responses.map((row) => {
const leftover = leftoverPairs.find((pair) => pair.criterion_code === row.criterion_code);
+ const isFocusRow = leftover != null && row.criterion_code === focusCriterion;
return (
- -
+
-
{row.criterion_label ?? row.criterion_code}: {row.response_category}
{leftover && (
{leftoverRowLabel(leftover.pair_kind)}
diff --git a/lineageweave/__init__.py b/lineageweave/__init__.py
index 2f70d5a2a..73eb05cc9 100644
--- a/lineageweave/__init__.py
+++ b/lineageweave/__init__.py
@@ -35,4 +35,4 @@
"sentence_excerpts",
]
-__version__ = "0.78.0"
+__version__ = "0.82.0"
diff --git a/pyproject.toml b/pyproject.toml
index aec6c3d75..c86290a70 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
-version = "0.78.0"
+version = "0.82.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" }
diff --git a/uv.lock b/uv.lock
index 08eab7768..b304b80af 100644
--- a/uv.lock
+++ b/uv.lock
@@ -355,7 +355,7 @@ wheels = [
[[package]]
name = "lineageweave"
-version = "0.75.0"
+version = "0.82.0"
source = { virtual = "." }
dependencies = [
{ name = "certifi" },