fix(html-report): read and render the tracker deadline column (#325)

/html-report was the one tracker consumer #319's deadline column left behind:
Step 1 now parses every canonical column and Step 3 renders Deadline after Date.
The drift guard derives CANONICAL_HEADER from apply.md itself, so a future
column added elsewhere but missing here fails with the column named; legacy
13-field rows read as empty deadline, never dropped, never inferred. Includes
rule-6 sweep refinements and deadline-reconciliation rules authored by
jakob1379.

Co-authored-by: Jakob Stender Guldberg <17257805+jakob1379@users.noreply.github.com>
This commit is contained in:
Oscar Madera
2026-08-16 20:02:09 +02:00
committed by GitHub
co-authored by Jakob Stender Guldberg
parent c855e11d22
commit 762d3218ef
8 changed files with 199 additions and 7 deletions
+49
View File
@@ -5,6 +5,7 @@ properties of the real repo, testing the things CI would catch if the
command file or gitignore rule were wrong.
"""
import re
import subprocess
import sys
import unittest
@@ -42,6 +43,54 @@ class HtmlReportCommandFileTests(unittest.TestCase):
self.assertGreater(len(text), 100, "Command file appears suspiciously short")
class HtmlReportTrackerFieldTests(unittest.TestCase):
"""The dashboard is a consumer of every tracker column: the Step 1 field
enumeration and the Step 3 table columns must stay in phase with the
canonical 14-column header (apply.md /outcome.md Step 1.1), so a future
column addition cannot silently vanish from the dashboard the way
`deadline` did."""
# Derived, never copied: a header literal repeated in this file drifts in
# lockstep with the spec it polices - add a 15th column to apply.md and a
# stale hardcoded 14-column list still passes every comparison here (a
# 14-column string is a substring of a 15-column header). Reading the
# canonical line back from apply.md makes the simulated drift fail with a
# clean list diff naming the missing column instead.
CANONICAL_HEADER = re.search(
r"^\s*(date,company,[a-z_,]+)$",
(REPO_ROOT / ".claude" / "commands" / "apply.md").read_text(encoding="utf-8"),
re.M,
).group(1).split(",")
def test_step1_parses_every_canonical_tracker_column(self):
text = COMMAND_FILE.read_text(encoding="utf-8")
match = re.search(
r"Parse every row into a record with fields:\n\s+((?:`[^`]+`,?\s*)+)",
text,
)
self.assertIsNotNone(match, "Step 1 field enumeration not found")
fields = [f.strip() for f in re.findall(r"`([^`]+)`", match.group(1))]
self.assertEqual(fields, self.CANONICAL_HEADER)
def test_step3_table_columns_include_deadline_after_date(self):
"""Date · Deadline order is the whole point of the change: the dashboard
must surface the clock that drives `/rank`'s urgency next to the date.
A membership pair (both `Date` and `Deadline` present somewhere) cannot
tell a swapped order from the correct one, and the order is what the
table shows the reader."""
text = COMMAND_FILE.read_text(encoding="utf-8")
match = re.search(r"### Table: columns to include\n\n(.+)\n", text)
self.assertIsNotNone(match, "Step 3 table column list not found")
line = match.group(1)
self.assertIn(
"`Date` · `Deadline` · `Company`",
line,
"Step 3 must offer the Deadline column directly after Date - the "
"list defines the dashboard's column order, and a swapped order "
"reads as a different table",
)
class HtmlReportGitignoreTests(unittest.TestCase):
"""reports/ must be gitignored — it holds personal generated output."""