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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
| 2026-08-17 | claude/s1c-residuals-r2-r3-4pb1at | 3960c46a7322018f53ab79441234132f07657197 | S1c follow-ups: use-theme transition-timer guard + issues:done ULID display-id fingerprint, tests | PR #2063 open; both loose ends from the S1c babysit fixed; no RAG surface touched | focused vitest 20/20 + repo-hygiene 57/57; verify:pr-local (lint, typecheck, test, build) failed:(none); live-ledger fingerprint spot-check 3/3 |
21 changes: 13 additions & 8 deletions scripts/check-outstanding-issues.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -251,15 +251,20 @@ export function parseIssues(markdown) {
}

export function issueRowFingerprint(markdown, issueId) {
const match = String(issueId)
.trim()
.match(/^#(\d+)$/);
if (!match) return null;
const number = Number(match[1]);
if (!Number.isFinite(number)) return null;

const trimmed = String(issueId).trim();
const numericMatch = trimmed.match(/^#(\d+)$/);
const number = numericMatch ? Number(numericMatch[1]) : null;
if (numericMatch && !Number.isFinite(number)) return null;

// Legacy numeric ids match by number (tolerating zero-padding differences);
// ULID-suffix display ids minted by reconcile match by the exact display id.
// Without the second arm, issues:done could never close a reconciled row.
const row = parseIssues(markdown).rows.find(
(entry) => entry.number === number && entry.table === "open" && entry.valid && entry.raw,
(entry) =>
entry.table === "open" &&
entry.valid &&
entry.raw &&
(numericMatch ? entry.number === number : entry.id === trimmed),
);
if (!row) return null;
const normalized = `| ${cells(row.raw).join(" | ")} |`;
Expand Down
11 changes: 10 additions & 1 deletion src/components/clinical-dashboard/use-theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ function syncThemeColorMetadata(theme: ResolvedTheme) {
}
}

// The transition class comes off on a short timer. Track the pending timer so a
// rapid second toggle replaces it instead of stacking removals, and bail out if
// it fires after the owning environment is gone — a leaked firing after DOM test
// teardown ("document is not defined") intermittently failed Unit coverage.
let themeTransitionTimer: ReturnType<typeof setTimeout> | null = null;

function applyResolvedTheme(theme: ResolvedTheme) {
const isCurrentlyDark = document.documentElement.classList.contains("dark");
const willBeDark = theme === "dark";
Expand All @@ -89,7 +95,10 @@ function applyResolvedTheme(theme: ResolvedTheme) {
document.documentElement.classList.add("theme-transitioning");
document.documentElement.classList.toggle("dark", willBeDark);
syncThemeColorMetadata(theme);
window.setTimeout(() => {
if (themeTransitionTimer !== null) clearTimeout(themeTransitionTimer);
themeTransitionTimer = setTimeout(() => {
themeTransitionTimer = null;
if (typeof document === "undefined") return;
document.documentElement.classList.remove("theme-transitioning");
}, 200);
} else {
Expand Down
56 changes: 56 additions & 0 deletions tests/repo-hygiene.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,62 @@ describe("outstanding-issues inbox", () => {
expect(() => applyRequest(stale, update)).toThrow(/stale|no longer open/);
});

it("fingerprints and closes ULID-display-id rows minted by reconcile", () => {
// Reconcile mints rows whose display id is the ULID's chars 10-16, not a
// legacy number. issues:done must be able to fingerprint and close them.
const ledger = [
"# Outstanding issues",
"",
"<!-- next-issue-id: 2 -->",
"",
"## Recommended execution queue",
"",
"<!-- prettier-ignore -->",
"",
"| Order | ID(s) |",
"| --- | --- |",
"| 1 | `#001` |",
"",
"## Open items",
"",
"<!-- prettier-ignore -->",
"",
"| ID | Pri | Type | Summary | Detail / next action | Source | Added |",
"| --- | --- | --- | --- | --- | --- | --- |",
"| #001 | P2 | issue | original summary | original detail | source | 2026-01-01 |",
"| #6BG9X2 <!-- issue-ulid:01M07SS71R6BG9X2VAGXMM1A1G --> | P2 | task | reconciled summary | reconciled detail | source | 2026-08-17 |",
"",
"## Resolved / archive",
"",
"<!-- prettier-ignore -->",
"",
"| ID | Type | Summary | Outcome | Resolved |",
"| ---- | ---- | ---- | ---- | ---- |",
"| #000 | issue | old | done | 2026-01-01 |",
"",
].join("\n");

const fingerprint = issueRowFingerprint(ledger, "#6BG9X2");
expect(fingerprint).not.toBeNull();
expect(issueRowFingerprint(ledger, "#000")).toBeNull();
expect(issueRowFingerprint(ledger, "#ZZZZZZ")).toBeNull();

const done = {
version: 2,
id: "99999999-9999-4999-8999-999999999999",
createdOn: "2026-08-17",
action: "done",
payload: { id: "#6BG9X2", outcome: "closed by test", baseRowFingerprint: fingerprint },
};
expect(validateRequest(done)).toEqual([]);
const applied = applyRequest(ledger, done);
expect(applied).not.toContain("| #6BG9X2 <!-- issue-ulid:01M07SS71R6BG9X2VAGXMM1A1G --> | P2 |");
expect(applied.slice(applied.indexOf("## Resolved / archive"))).toContain("closed by test");

const stale = ledger.replace("reconciled summary", "mutated summary");
expect(() => applyRequest(stale, done)).toThrow(/stale|no longer open/);
});

it("rejects done requests targeting archived or nonexistent issues", () => {
const ledger = [
"<!-- issues:next-id=2 -->",
Expand Down
Loading