diff --git a/AGENTS.md b/AGENTS.md
index c790995c1..32e0b0ab4 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 / 0030) 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. A calendar commitment that is a leftover
+pair also names the leftover criterion next to the due date.
`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..d2146db35 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -122,7 +122,9 @@ flowchart LR
`rankweave_client.py`'s default transport raises
`RankWeaveNotAvailable`. `GET /api/rankings` then returns
`rankweave_not_available` and an empty ranking list. Hidden posts
- are omitted from every channel. See ADR 0024.
+ are omitted from every channel. See ADR 0024. A calendar
+ commitment that is also a leftover pair names that criterion
+ (ADR 0030).
## Standards and citations
diff --git a/CHANGELOG.d/0.89.0-leftover-calendar.md b/CHANGELOG.d/0.89.0-leftover-calendar.md
new file mode 100644
index 000000000..d3a9d4998
--- /dev/null
+++ b/CHANGELOG.d/0.89.0-leftover-calendar.md
@@ -0,0 +1,8 @@
+# 0.89.0 — Leftover badge on a matching calendar commitment
+
+## Added
+
+- Calendar commitments that are leftover pairs name the leftover
+ criterion (ADR 0030). After `make seed`, the leftover commitment
+ reads **Closest leftover · sales-lead** next to the due date;
+ click opens that post. An empty calendar stays fail-closed.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6bfcaa28f..551e6d84a 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.89.0] - 2026-08-18
+
+### Added
+
+- Calendar commitments that are leftover pairs name the leftover
+ criterion (ADR 0030). After `make seed`, the leftover commitment
+ reads **Closest leftover · sales-lead** next to the due date;
+ click opens that post. An empty calendar stays fail-closed.
+ Never invent a fused score or a theta.
+
## [0.75.0] - 2026-08-17
### Added
diff --git a/docs/adr/0030-leftover-pair-on-calendar-commitment.md b/docs/adr/0030-leftover-pair-on-calendar-commitment.md
new file mode 100644
index 000000000..685b181f9
--- /dev/null
+++ b/docs/adr/0030-leftover-pair-on-calendar-commitment.md
@@ -0,0 +1,58 @@
+# ADR 0030 — Name leftover on a matching calendar commitment
+
+**Decision status:** Accepted
+**Date:** 2026-08-18
+
+## Context
+
+ADR 0018 puts leftover pairs above the period-report member list.
+Calendar sits under Rankings and lists due commitments (Allen
+intervals). A buyer who starts from Calendar still sees only the
+commitment title, post title, status, and due date. The leftover
+post is already on that list when it has a due ticket; the row just
+does not name the leftover criterion.
+
+Do not invent a second leftover store. Do not invent a fused score
+or a theta. An empty calendar stays **No upcoming commitments**.
+
+## Decision
+
+When an authorized leftover pair names a calendar commitment, that
+button shows `Closest leftover · {criterion}` or
+`Farthest leftover · {criterion}` and includes the same caption in
+its accessible name.
+
+A commitment that is not a leftover pair stays unmarked. A leftover
+pair for a hidden post never reaches Calendar (ADR 0017 ABAC). A
+report fetch error clears leftover badges and leaves the due list
+intact — never an invented pair.
+
+After `make seed`, the leftover calendar commitment reads **Closest
+leftover · sales-lead** next to the due date; click still opens
+that post.
+
+Leftover evidence is the same authorized `leftover_pairs` already
+on the period-report payload.
+
+## Consequences
+
+Leftover buttons above the member list stay (ADR 0018). Rankings
+leftover badges stay on #252 / ADR 0029. Member badges stay on
+#234 / ADR 0028. This slice only labels the already-visible
+calendar commitment.
+
+## 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
+
+Allen, J. F. (1983). Maintaining knowledge about temporal intervals.
+*Communications of the ACM, 26*(11), 832–843.
+https://doi.org/10.1145/182.358434
diff --git a/frontend/package.json b/frontend/package.json
index 575b7c586..51401bfd3 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
- "version": "0.75.0",
+ "version": "0.89.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index a32a26403..6756cf914 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -1330,6 +1330,76 @@ describe("App, authenticated", () => {
await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument());
});
+ it("names leftover criterion on a matching calendar commitment", async () => {
+ stubBackend({
+ calendarCommitments: [
+ {
+ issue_ticket_id: "ticket-a100",
+ post_id: "post-1",
+ ticket_status_code: "open",
+ ticket_status_label: "Open",
+ ticket_title: "Send Northridge Grid the revised quote",
+ assigned_account_id: null,
+ due_date: "2026-01-12",
+ commitment_summary: null,
+ created_at: "2026-01-01T00:00:00Z",
+ updated_at: "2026-01-01T00:00:00Z",
+ post_title: "Public post",
+ },
+ {
+ issue_ticket_id: "ticket-b200",
+ post_id: "post-2",
+ ticket_status_code: "open",
+ ticket_status_label: "Open",
+ ticket_title: "Send Westfield Power the revised specification",
+ assigned_account_id: null,
+ due_date: "2026-01-14",
+ commitment_summary: null,
+ created_at: "2026-01-02T00:00:00Z",
+ updated_at: "2026-01-02T00:00:00Z",
+ post_title: "Specification revision requested",
+ },
+ {
+ issue_ticket_id: "ticket-c300",
+ post_id: "post-9",
+ ticket_status_code: "open",
+ ticket_status_label: "Open",
+ ticket_title: "Confirm Riverbend delivery window",
+ assigned_account_id: null,
+ due_date: "2026-01-16",
+ commitment_summary: null,
+ created_at: "2026-01-03T00:00:00Z",
+ updated_at: "2026-01-03T00:00:00Z",
+ post_title: "Riverbend calendar commitment",
+ },
+ ],
+ });
+ render(