3eacb6c326
- templates/app_row.html: app_list_row/app_list_row_end (layout_start/_end idiom) — icon+name (+optional secondary) left, caller action block right; compact 44px row; logoURL→PNG→ optional FallbackIcon→hidden onerror chain - applied: Távoli mentés toggle list, Visszaállítás restore-to-verify + .fab lists, dashboard Telepített alkalmazások (state edge + data-href preserved); Alkalmazások collapsed headers ALIGNED (icon+name left, status dot moved right before chevron; expander untouched) - funcmap: dict + appHref; OffboxAppRow/AppBackupRow gain Slug - style.css: .app-row family; stack-card/stack-info/stack-logo/stack-name/stack-desc/ stack-actions rules retired (dashboard rows now shared); .app-backup-row-header matched to the shared row height - scripts/app_row_dedup_gate.py: row markup single-sourced (red-proven: pasted old storage-path-item block → exit 1); render tests per surface (app_row_test.go) - NO behavior change: toggle/download/expander actions byte-identical
83 lines
3.7 KiB
Python
83 lines
3.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""v0.126.0 shared app-row dedup gate — the app-list row markup exists ONCE.
|
|
|
|
The canonical row (icon + name left, action block right) is defined in
|
|
templates/app_row.html (app_list_row / app_list_row_end). Every list surface renders
|
|
THROUGH it; hand-rolled copies are the defect this gate extinguishes (the pre-0.126.0
|
|
state: three visually diverging row structures across four surfaces).
|
|
|
|
Asserts:
|
|
1. The row-opening markup (`class="app-row"...`) appears in app_row.html ONLY.
|
|
2. The row-icon markup (`app-row-icon`) appears only in app_row.html plus the ONE
|
|
allowlisted aligned copy: the backups_apps.html expander header (it owns the
|
|
expand/collapse toggle, so it is aligned to the grammar, not rendered through
|
|
the partial).
|
|
3. The old duplicated structures are gone from the converted surfaces:
|
|
storage-path-item rows on the backups pages, stack-card rows on the dashboard.
|
|
4. Each converted surface actually references the partial.
|
|
|
|
Run from controller/: python scripts/app_row_dedup_gate.py
|
|
Exit 1 on any violation.
|
|
"""
|
|
import io, os, re, sys
|
|
|
|
TPL = os.path.join("internal", "web", "templates")
|
|
|
|
# (file, forbidden-pattern, why)
|
|
FORBIDDEN = [
|
|
("backups_remote.html", r"storage-path-item", "toggle list must render through app_list_row"),
|
|
("backups_remote.html", r"storage-path-header", "old row structure"),
|
|
("backups_restore.html", r"storage-path-item", ".fab + restore-to-verify lists must render through app_list_row"),
|
|
("backups_restore.html", r"storage-path-header", "old row structure"),
|
|
("dashboard.html", r"stack-card", "dashboard rows must render through app_list_row"),
|
|
("dashboard.html", r"stack-info", "old row structure"),
|
|
("dashboard.html", r'stack-logo"', "old row logo (stack-logo-lg on stacks.html is the card, not a list row)"),
|
|
]
|
|
|
|
# files that MUST reference the shared partial
|
|
MUST_USE = ["backups_remote.html", "backups_restore.html", "dashboard.html"]
|
|
|
|
# `class="app-row"` / `class="app-row {{...}}"` opening markup — [^-] excludes the
|
|
# derived class names (app-row-list, app-row-icon, ...).
|
|
ROW_OPEN_RE = re.compile(r'class="app-row[^-]')
|
|
ICON_RE = re.compile(r'app-row-icon')
|
|
ICON_ALLOW = {"app_row.html", "backups_apps.html"} # backups_apps: the aligned expander header
|
|
|
|
failures = []
|
|
|
|
if not os.path.isdir(TPL):
|
|
print("run from controller/ (internal/web/templates not found)")
|
|
sys.exit(2)
|
|
|
|
files = {f: io.open(os.path.join(TPL, f), encoding="utf-8").read()
|
|
for f in sorted(os.listdir(TPL)) if f.endswith(".html")}
|
|
|
|
if "app_row.html" not in files:
|
|
failures.append("templates/app_row.html is missing — the canonical row partial")
|
|
|
|
for fname, src in files.items():
|
|
n_open = len(ROW_OPEN_RE.findall(src))
|
|
if fname == "app_row.html":
|
|
if n_open != 1:
|
|
failures.append("app_row.html: expected the row-opening markup exactly once, found %d" % n_open)
|
|
elif n_open:
|
|
failures.append("%s: hand-rolled app-row markup (%d occurrence(s)) — render through the app_list_row partial" % (fname, n_open))
|
|
if ICON_RE.search(src) and fname not in ICON_ALLOW:
|
|
failures.append("%s: app-row-icon outside the partial/allowlist — do not copy the icon markup" % fname)
|
|
|
|
for fname, pat, why in FORBIDDEN:
|
|
src = files.get(fname, "")
|
|
if re.search(pat, src):
|
|
failures.append("%s: forbidden old structure %r survives (%s)" % (fname, pat, why))
|
|
|
|
for fname in MUST_USE:
|
|
if '{{template "app_list_row"' not in files.get(fname, ""):
|
|
failures.append("%s: does not render through the app_list_row partial" % fname)
|
|
|
|
if failures:
|
|
print("app_row_dedup_gate: FAIL")
|
|
for f in failures:
|
|
print(" - " + f)
|
|
sys.exit(1)
|
|
print("app_row_dedup_gate: OK (row markup single-sourced in app_row.html; %d templates scanned)" % len(files))
|