Skip to content
Closed
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
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +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) 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.
a click opens that post. The opened post names the leftover
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
4 changes: 3 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,9 @@ 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), 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
9 changes: 9 additions & 0 deletions CHANGELOG.d/0.78.0-leftover-open-context.md
Original file line number Diff line number Diff line change
@@ -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.
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.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ 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

- 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
Expand Down
52 changes: 52 additions & 0 deletions docs/adr/0026-leftover-pair-open-context.md
Original file line number Diff line number Diff line change
@@ -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
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.75.0",
"version": "0.82.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1417,6 +1417,15 @@ 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.",
);
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 All @@ -1425,6 +1434,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);
Expand Down
Loading
Loading