Skip to content

Commit 34ae3ab

Browse files
committed
fix(html): resolve responsive report overflows and polish provenance badges
1 parent 3db90c4 commit 34ae3ab

5 files changed

Lines changed: 167 additions & 48 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
# Changelog
22

3-
## [2.0.0b1]
3+
## [2.0.0b2]
44

5-
Fixed:
5+
### Dependencies
66

7-
- Upgrade requests to 2.33.0 for extract_zipped_paths security fix (CVE-2026-25645)
7+
- Upgrade requests (dev dep) to 2.33.0 for extract_zipped_paths security fix (CVE-2026-25645)
88

9+
### HTML
10+
11+
- Fix page-level horizontal scrolling in wide table tabs by constraining overflow to local table wrappers (#14).
12+
- Fix mobile header brand block layout on narrow viewports (#15).
13+
- Keep Overview KPI micro-badges inside cards at extreme browser/mobile widths.
14+
- Restyle Report Provenance summary badges to match the card-style badge language used across the report.
915

1016
## [2.0.0b1] - 20260325
1117

codeclone.baseline.json

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
"schema_version": "2.0",
88
"fingerprint_version": "1",
99
"python_tag": "cp313",
10-
"created_at": "2026-03-24T15:14:34Z",
10+
"created_at": "2026-03-26T16:36:17Z",
1111
"payload_sha256": "691c6cedd10e2a51d6038780f3ae9dffe763356dd2aba742b3980f131b79f217",
12-
"metrics_payload_sha256": "3310d3a0f64d5fa0373546c5c4c82675dc5a441344f876092005665e81234e94"
12+
"metrics_payload_sha256": "f18db9aa4573517b0babb31e4e995208209895ea6b8a1957087c0f3b6f1f5434"
1313
},
1414
"clones": {
1515
"functions": [
@@ -32,16 +32,14 @@
3232
"high_coupling_classes": [],
3333
"max_cohesion": 5,
3434
"low_cohesion_classes": [
35-
"codeclone._cli_reports:_OutputPaths",
36-
"codeclone._cli_reports:_ReportArtifacts",
3735
"codeclone.baseline:Baseline",
3836
"codeclone.metrics_baseline:MetricsBaseline",
3937
"tests.test_golden_v2:_DummyExecutor"
4038
],
4139
"dependency_cycles": [],
42-
"dependency_max_depth": 9,
40+
"dependency_max_depth": 10,
4341
"dead_code_items": [],
44-
"health_score": 78,
42+
"health_score": 81,
4543
"health_grade": "B"
4644
}
4745
}

codeclone/_html_css.py

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,18 @@
139139
box-shadow:var(--shadow-sm)}
140140
.topbar-inner{display:flex;align-items:center;justify-content:space-between;
141141
height:72px;padding:0 var(--sp-6);max-width:var(--container-max);margin:0 auto}
142-
.brand{display:flex;align-items:center;gap:var(--sp-3)}
142+
.brand{display:flex;align-items:center;gap:var(--sp-3);min-width:0;flex:1}
143143
.brand-logo{flex-shrink:0}
144-
.brand-text{display:flex;flex-direction:column;gap:2px}
145-
.brand h1{font-size:1.15rem;font-weight:700;color:var(--text-primary);line-height:1.3}
146-
.brand-meta{font-size:.78rem;color:var(--text-muted)}
147-
.brand-project{font-weight:500;color:var(--text-secondary)}
144+
.brand-text{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
145+
.brand h1{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--sp-1);font-size:1.15rem;
146+
font-weight:700;color:var(--text-primary);line-height:1.3;min-width:0}
147+
.brand-meta{font-size:.78rem;color:var(--text-muted);overflow-wrap:anywhere}
148+
.brand-project{display:inline-flex;flex-wrap:wrap;align-items:baseline;gap:4px;
149+
font-weight:500;color:var(--text-secondary);min-width:0}
148150
.brand-project-name{font-family:var(--font-mono);font-size:.85em;font-weight:500;padding:1px 5px;
149-
border-radius:var(--radius-sm);background:var(--bg-overlay);color:var(--accent-primary)}
150-
.topbar-actions{display:flex;align-items:center;gap:var(--sp-2)}
151+
border-radius:var(--radius-sm);background:var(--bg-overlay);color:var(--accent-primary);
152+
max-width:100%;overflow-wrap:anywhere}
153+
.topbar-actions{display:flex;align-items:center;gap:var(--sp-2);flex-shrink:0;flex-wrap:wrap}
151154
152155
/* Theme toggle */
153156
.theme-toggle{display:inline-flex;align-items:center;gap:var(--sp-1);
@@ -284,9 +287,10 @@
284287
# ---------------------------------------------------------------------------
285288

286289
_TABLES = """\
287-
.table-wrap{overflow-x:auto;border:1px solid var(--border);border-radius:var(--radius-lg);
288-
margin-bottom:var(--sp-4)}
289-
.table{width:100%;border-collapse:collapse;font-size:.82rem;font-family:var(--font-mono)}
290+
.table-wrap{display:block;inline-size:100%;max-inline-size:100%;min-inline-size:0;overflow-x:auto;
291+
overflow-y:hidden;border:1px solid var(--border);border-radius:var(--radius-lg);margin-bottom:var(--sp-4)}
292+
.table{inline-size:max-content;min-inline-size:100%;border-collapse:collapse;font-size:.82rem;
293+
font-family:var(--font-mono)}
290294
.table th{position:sticky;top:0;z-index:2;padding:var(--sp-2) var(--sp-3);text-align:left;font-family:var(--font-sans);
291295
font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.05em;
292296
color:var(--text-muted);background:var(--bg-overlay);border-bottom:1px solid var(--border);
@@ -300,10 +304,11 @@
300304
.table tr:last-child td{border-bottom:none}
301305
.table tr:hover td{background:var(--bg-raised)}
302306
.table .col-name{font-weight:500;color:var(--text-primary)}
303-
.table .col-file{color:var(--text-muted);max-width:240px;overflow:hidden;
307+
.table .col-file,.table .col-path{color:var(--text-muted);max-width:240px;overflow:hidden;
304308
text-overflow:ellipsis;white-space:nowrap}
305-
.table .col-number{font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}
306-
.table .col-risk{white-space:nowrap}
309+
.table .col-number,.table .col-num{font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}
310+
.table .col-risk,.table .col-badge,.table .col-cat{white-space:nowrap}
311+
.table .col-steps{max-width:120px;word-break:break-word}
307312
.table .col-wide{max-width:320px;word-break:break-all}
308313
.table-empty{padding:var(--sp-8);text-align:center;color:var(--text-muted);font-size:.9rem}
309314
"""
@@ -478,6 +483,12 @@
478483
}
479484
@media(max-width:520px){
480485
.overview-kpi-cards{grid-template-columns:1fr}
486+
.overview-kpi-cards .meta-item{grid-template-rows:auto auto auto;align-content:start;
487+
min-height:0}
488+
.overview-kpi-cards .meta-item .meta-label{min-height:0}
489+
.overview-kpi-cards .meta-item .meta-value{padding-top:0}
490+
.overview-kpi-cards .kpi-detail{align-self:start}
491+
.overview-kpi-cards .kpi-micro{max-width:100%;white-space:normal;overflow-wrap:anywhere}
481492
}
482493
483494
/* Health gauge */
@@ -916,12 +927,19 @@
916927
/* Provenance summary badges */
917928
.prov-summary{display:flex;flex-wrap:wrap;align-items:center;gap:6px;
918929
padding:var(--sp-2) var(--sp-4);border-top:1px solid var(--border)}
919-
.prov-badge{font-size:.65rem;font-weight:600;padding:2px 8px;
920-
border-radius:10px;white-space:nowrap;display:inline-flex;align-items:center;gap:3px}
921-
.prov-badge.green{background:var(--success-muted);color:var(--success)}
922-
.prov-badge.red{background:var(--error-muted);color:var(--error)}
923-
.prov-badge.amber{background:var(--warning-muted);color:var(--warning)}
924-
.prov-badge.neutral{background:var(--bg-overlay);color:var(--text-muted)}
930+
.prov-badge{display:inline-flex;align-items:center;gap:4px;font-size:.66rem;
931+
padding:2px 8px;border-radius:var(--radius-sm);background:var(--bg-raised);
932+
white-space:nowrap;line-height:1.3;border:1px solid color-mix(in srgb,var(--border) 55%,transparent)}
933+
.prov-badge-val{font-weight:600;font-variant-numeric:tabular-nums}
934+
.prov-badge-lbl{font-weight:400;color:var(--text-muted);text-transform:lowercase}
935+
.prov-badge--green{background:var(--success-muted);border-color:color-mix(in srgb,var(--success) 20%,transparent)}
936+
.prov-badge--green .prov-badge-val{color:var(--success)}
937+
.prov-badge--red{background:var(--error-muted);border-color:color-mix(in srgb,var(--error) 20%,transparent)}
938+
.prov-badge--red .prov-badge-val{color:var(--error)}
939+
.prov-badge--amber{background:var(--warning-muted);border-color:color-mix(in srgb,var(--warning) 20%,transparent)}
940+
.prov-badge--amber .prov-badge-val{color:var(--warning)}
941+
.prov-badge--neutral{background:var(--bg-overlay);border-color:color-mix(in srgb,var(--border) 75%,transparent)}
942+
.prov-badge--neutral .prov-badge-val{color:var(--text-secondary)}
925943
.prov-explain{font-size:.62rem;color:var(--text-muted);margin-left:auto;font-style:italic}
926944
"""
927945

@@ -1034,11 +1052,19 @@
10341052
.suggestion-head{flex-direction:column;align-items:flex-start}
10351053
.suggestion-facts{grid-template-columns:1fr}
10361054
.container{padding:0 var(--sp-3)}
1055+
.topbar{position:static}
1056+
.topbar-inner{height:auto;padding:var(--sp-3);flex-direction:column;align-items:stretch;gap:var(--sp-2)}
1057+
.brand{align-items:flex-start}
1058+
.brand h1{font-size:1rem}
1059+
.brand-meta{font-size:.72rem;line-height:1.4}
1060+
.topbar-actions{width:100%;justify-content:flex-start}
1061+
.main-tabs-wrap{top:0}
10371062
.main-tabs{padding:0 var(--sp-3)}
10381063
}
10391064
@media(max-width:480px){
10401065
.overview-kpi-grid{grid-template-columns:1fr}
10411066
.search-box input[type="text"]{width:140px}
1067+
.brand-logo{width:28px;height:28px}
10421068
}
10431069
10441070
/* Print */

codeclone/_html_report/_sections/_meta.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -333,36 +333,41 @@ def _section_html(title: str, rows: list[tuple[str, object]]) -> str:
333333
_section_html(st, rows) for st, rows in meta_sections if rows
334334
)
335335

336-
def _prov_badge(label: str, color: str) -> str:
337-
return f'<span class="prov-badge {color}">{_escape_html(label)}</span>'
336+
def _prov_badge(label: str, value: str, color: str) -> str:
337+
return (
338+
f'<span class="prov-badge prov-badge--{color}">'
339+
f'<span class="prov-badge-val">{_escape_html(value)}</span>'
340+
f'<span class="prov-badge-lbl">{_escape_html(label)}</span>'
341+
"</span>"
342+
)
338343

339344
badges: list[str] = []
340345
if _bl_verified is True:
341-
badges.append(_prov_badge("Baseline verified", "green"))
346+
badges.append(_prov_badge("Baseline", "verified", "green"))
342347
elif _bl_loaded is True and _bl_verified is not True:
343-
badges.append(_prov_badge("Baseline untrusted", "red"))
348+
badges.append(_prov_badge("Baseline", "untrusted", "red"))
344349
elif _bl_loaded is False or _bl_loaded is None:
345-
badges.append(_prov_badge("Baseline missing", "amber"))
350+
badges.append(_prov_badge("Baseline", "missing", "amber"))
346351
if ctx.report_schema_version:
347-
badges.append(_prov_badge(f"Schema {ctx.report_schema_version}", "neutral"))
352+
badges.append(_prov_badge("Schema", str(ctx.report_schema_version), "neutral"))
348353
if _bl_fp_ver is not None:
349-
badges.append(_prov_badge(f"Fingerprint {_bl_fp_ver}", "neutral"))
354+
badges.append(_prov_badge("Fingerprint", str(_bl_fp_ver), "neutral"))
350355
gen_name = str(_bl_gen_name or "")
351356
if gen_name and gen_name != "codeclone":
352-
badges.append(_prov_badge(f"Generator mismatch: {gen_name}", "red"))
357+
badges.append(_prov_badge("Generator mismatch", gen_name, "red"))
353358
if _cache_used is True:
354-
badges.append(_prov_badge("Cache hit", "green"))
359+
badges.append(_prov_badge("Cache", "hit", "green"))
355360
elif _cache_used is False:
356-
badges.append(_prov_badge("Cache miss", "amber"))
361+
badges.append(_prov_badge("Cache", "miss", "amber"))
357362
else:
358-
badges.append(_prov_badge("Cache N/A", "neutral"))
363+
badges.append(_prov_badge("Cache", "N/A", "neutral"))
359364
analysis_mode = str(_meta_pick(meta.get("analysis_mode")) or "")
360365
if analysis_mode:
361-
badges.append(_prov_badge(f"Mode: {analysis_mode}", "neutral"))
366+
badges.append(_prov_badge("Mode", analysis_mode, "neutral"))
362367
if _mbl_verified is True:
363-
badges.append(_prov_badge("Metrics baseline verified", "green"))
368+
badges.append(_prov_badge("Metrics baseline", "verified", "green"))
364369
elif _mbl_loaded is True and _mbl_verified is not True:
365-
badges.append(_prov_badge("Metrics baseline untrusted", "red"))
370+
badges.append(_prov_badge("Metrics baseline", "untrusted", "red"))
366371

367372
prov_summary = (
368373
f'<div class="prov-summary">{"".join(badges)}'

tests/test_html_report.py

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,54 @@ def test_html_report_topbar_actions_present() -> None:
746746
assert 'id="help-modal"' not in html
747747

748748

749+
def test_html_report_mobile_topbar_reflows_brand_block() -> None:
750+
html = build_html_report(func_groups={}, block_groups={}, segment_groups={})
751+
_assert_html_contains(
752+
html,
753+
"@media(max-width:768px){",
754+
".topbar{position:static}",
755+
".topbar-inner{height:auto;padding:var(--sp-3);flex-direction:column;align-items:stretch;gap:var(--sp-2)}",
756+
".brand h1{font-size:1rem}",
757+
".topbar-actions{width:100%;justify-content:flex-start}",
758+
".main-tabs-wrap{top:0}",
759+
)
760+
761+
762+
def test_html_report_narrow_kpi_cards_keep_badges_inside_card() -> None:
763+
html = build_html_report(func_groups={}, block_groups={}, segment_groups={})
764+
_assert_html_contains(
765+
html,
766+
"@media(max-width:520px){",
767+
(
768+
".overview-kpi-cards .meta-item{grid-template-rows:auto auto auto;"
769+
"align-content:start;"
770+
),
771+
".overview-kpi-cards .kpi-detail{align-self:start}",
772+
(
773+
".overview-kpi-cards .kpi-micro{max-width:100%;white-space:normal;"
774+
"overflow-wrap:anywhere}"
775+
),
776+
)
777+
778+
779+
def test_html_report_table_css_matches_rendered_column_classes() -> None:
780+
html = build_html_report(func_groups={}, block_groups={}, segment_groups={})
781+
_assert_html_contains(
782+
html,
783+
".table-wrap{display:block;inline-size:100%;max-inline-size:100%;min-inline-size:0;overflow-x:auto;",
784+
".table{inline-size:max-content;min-inline-size:100%;border-collapse:collapse;",
785+
(
786+
".table .col-file,.table .col-path{color:var(--text-muted);"
787+
"max-width:240px;overflow:hidden;"
788+
),
789+
(
790+
".table .col-number,.table .col-num{font-variant-numeric:"
791+
"tabular-nums;text-align:right;white-space:nowrap}"
792+
),
793+
".table .col-risk,.table .col-badge,.table .col-cat{white-space:nowrap}",
794+
)
795+
796+
749797
def test_html_report_footer_links_present() -> None:
750798
html = build_html_report(func_groups={}, block_groups={}, segment_groups={})
751799
assert f'href="{REPOSITORY_URL}"' in html
@@ -809,6 +857,31 @@ def test_html_report_includes_provenance_metadata(
809857
assert "deterministic render" not in html
810858

811859

860+
def test_html_report_provenance_summary_uses_card_like_badges(
861+
report_meta_factory: Callable[..., dict[str, object]],
862+
) -> None:
863+
html = build_html_report(
864+
func_groups={},
865+
block_groups={},
866+
segment_groups={},
867+
report_meta=report_meta_factory(
868+
baseline_schema_version=2,
869+
baseline_fingerprint_version=1,
870+
),
871+
)
872+
_assert_html_contains(
873+
html,
874+
'class="prov-badge prov-badge--green"',
875+
'class="prov-badge prov-badge--neutral"',
876+
'<span class="prov-badge-val">verified</span>',
877+
'<span class="prov-badge-lbl">Baseline</span>',
878+
'<span class="prov-badge-val">2.1</span>',
879+
'<span class="prov-badge-lbl">Schema</span>',
880+
'<span class="prov-badge-val">1</span>',
881+
'<span class="prov-badge-lbl">Fingerprint</span>',
882+
)
883+
884+
812885
def test_html_report_escapes_meta_and_title(
813886
tmp_path: Path,
814887
report_meta_factory: Callable[..., dict[str, object]],
@@ -1712,10 +1785,17 @@ def test_html_report_provenance_badges_cover_mismatch_and_untrusted_metrics() ->
17121785
"baseline_fingerprint_version": "1",
17131786
},
17141787
)
1715-
assert "Baseline missing" in html
1716-
assert "Generator mismatch: other-generator" in html
1717-
assert "Metrics baseline untrusted" in html
1718-
assert "Cache N/A" in html
1788+
_assert_html_contains(
1789+
html,
1790+
'<span class="prov-badge-val">missing</span>',
1791+
'<span class="prov-badge-lbl">Baseline</span>',
1792+
'<span class="prov-badge-val">other-generator</span>',
1793+
'<span class="prov-badge-lbl">Generator mismatch</span>',
1794+
'<span class="prov-badge-val">untrusted</span>',
1795+
'<span class="prov-badge-lbl">Metrics baseline</span>',
1796+
'<span class="prov-badge-val">N/A</span>',
1797+
'<span class="prov-badge-lbl">Cache</span>',
1798+
)
17191799

17201800

17211801
def test_html_report_provenance_handles_non_boolean_baseline_loaded() -> None:
@@ -1729,8 +1809,12 @@ def test_html_report_provenance_handles_non_boolean_baseline_loaded() -> None:
17291809
"report_schema_version": "2.0",
17301810
},
17311811
)
1732-
assert "Schema 2.0" in html
1733-
assert "Baseline missing" not in html
1812+
_assert_html_contains(
1813+
html,
1814+
'<span class="prov-badge-val">2.0</span>',
1815+
'<span class="prov-badge-lbl">Schema</span>',
1816+
)
1817+
assert '<span class="prov-badge-lbl">Baseline</span>' not in html
17341818

17351819

17361820
def test_html_report_dependency_hubs_deterministic_tie_order() -> None:

0 commit comments

Comments
 (0)