File size: 3,882 Bytes
330b65e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66f3a37
 
 
 
 
 
 
330b65e
 
 
 
 
 
 
 
 
 
 
 
 
66f3a37
 
 
 
 
 
 
330b65e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66f3a37
 
 
 
 
 
 
330b65e
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from __future__ import annotations

from backend.models.issue import Category, Issue, Severity


def analyze(parsed_page: dict, thresholds: dict) -> list[Issue]:
    issues: list[Issue] = []
    t = thresholds["tables"]
    tables = parsed_page.get("tables", [])

    for i, table in enumerate(tables):
        label = f"Table #{i + 1}"

        # TBL1 — no header row
        if not table.get("has_header", True):
            issues.append(Issue(
                rule_id="TBL1_missing_header",
                category=Category.COMPONENT_QUALITY,
                severity=Severity.HIGH,
                confidence=0.95,
                message=f"{label} has no header row (<thead>/<th>).",
                recommendation=(
                    "Add a <thead> with <th> elements. Use scope='col' for accessibility. "
                    "Style headers to visually distinguish them from data rows."
                ),
                evidence="No <th> or <thead> detected.",
                estimated_time="10 minutes",
                why=(
                    "Screen readers announce column context from <th> elements — without "
                    "them, table data is read as an unstructured list and is impossible to "
                    "navigate. WCAG 1.3.1 requires programmatic relationships in tabular data. "
                    "Visually, headers also give users an instant map of what each column means."
                ),
                references=["WCAG 2.1 SC 1.3.1", "GOV.UK Design System", "WebAIM"],
            ))

        # TBL2 — no zebra striping
        if not table.get("has_zebra_striping", False):
            issues.append(Issue(
                rule_id="TBL2_no_zebra",
                category=Category.COMPONENT_QUALITY,
                severity=Severity.LOW,
                confidence=0.7,
                message=f"{label} has no alternating row colours — dense tables are hard to scan.",
                recommendation="Apply a subtle background to odd/even rows (e.g. tr:nth-child(even) { background: #f9f9f9; }).",
                evidence="No alternating row background detected.",
                estimated_time="10 minutes",
                why=(
                    "In data-dense tables, alternating row backgrounds reduce horizontal "
                    "scanning errors by helping users track a row across wide tables. "
                    "Leading analytics products consistently use zebra striping for tables "
                    "with more than 5 columns or 10 rows."
                ),
                references=["Stripe", "Notion", "Google Sheets"],
            ))

        # TBL3 — insufficient cell padding
        cell_pad = table.get("cell_padding_px", 0)
        if cell_pad < t["min_cell_padding_px"]:
            issues.append(Issue(
                rule_id="TBL3_cell_padding",
                category=Category.COMPONENT_QUALITY,
                severity=Severity.MEDIUM,
                confidence=0.9,
                message=(
                    f"{label} cell padding ({cell_pad}px) is below the "
                    f"recommended {t['min_cell_padding_px']}px."
                ),
                recommendation=f"Set td, th {{ padding: {t['min_cell_padding_px']}px 12px; }} for comfortable reading.",
                evidence=f"td padding: {cell_pad}px",
                estimated_time="5 minutes",
                why=(
                    "Cramped table cells compress data to the point where users struggle to "
                    "identify individual values without physically pointing at the screen. "
                    "Adequate cell padding (8px+) gives each datum visual breathing room "
                    "and dramatically improves scan speed in data-heavy views."
                ),
                references=["Material Design", "Stripe", "GitHub"],
            ))

    return issues