Skip to content
Merged
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
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.d/0.82.0-focus-leftover-eval-row.md
Original file line number Diff line number Diff line change
@@ -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.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions docs/adr/0027-focus-leftover-evaluation-row.md
Original file line number Diff line number Diff line change
@@ -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
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": "0.78.0",
"version": "0.82.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
15 changes: 14 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -770,12 +770,19 @@ function EvaluationPanel({
const [evaluating, setEvaluating] = useState(false);
const [error, setError] = useState<string | null>(null);
const [orchestratorOff, setOrchestratorOff] = useState(false);
const leftoverRowRef = useRef<HTMLLIElement | null>(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);
Expand Down Expand Up @@ -811,8 +818,14 @@ function EvaluationPanel({
<ul>
{responses.map((row) => {
const leftover = leftoverPairs.find((pair) => pair.criterion_code === row.criterion_code);
const isFocusRow = leftover != null && row.criterion_code === focusCriterion;
return (
<li key={row.criterion_code}>
<li
key={row.criterion_code}
ref={isFocusRow ? leftoverRowRef : undefined}
tabIndex={isFocusRow ? -1 : undefined}
aria-current={leftover ? true : undefined}
>
{row.criterion_label ?? row.criterion_code}: {row.response_category}
{leftover && (
<span className="post-badge"> {leftoverRowLabel(leftover.pair_kind)}</span>
Expand Down
2 changes: 1 addition & 1 deletion lineageweave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@
"sentence_excerpts",
]

__version__ = "0.78.0"
__version__ = "0.82.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 = "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" }
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.