fix(ui): una cornice sola, e smette di spostarsi sotto le dita
Punto 14. La segnalazione dell'operatore del 28/07 non era stata circoscritta sul codice: «le dimensioni delle viste cambiano a seconda del menu». Sono quattro meccanismi distinti, tutti leggibili senza avere il tablet in mano. Ogni vista si dichiarava la propria larghezza: sette valori diversi su diciassette template, e nessuno coincideva con quello della navbar, che sta a max-w-7xl su tutte. Il contenuto era quindi disallineato dalla barra sopra di sé, e il disallineamento cambiava a ogni pagina. Il percorso dell'operatore faceva 1280 → 1024 → tutto schermo → 1280 in quattro passaggi. Il padding verticale mescolava py-8 e py-6 fra viste consecutive, così la prima card cambiava altezza a ogni schermata. La barra di scorrimento è classica da 8px e occupa spazio: una pagina lunga la mostrava, una corta no, e la schermata di misura la toglie sempre. A ogni navigazione tutto il contenuto centrato — navbar compresa — si spostava di 8px. La schermata di misura era alta 100vh, che su Android e iOS è l'altezza con la barra dell'indirizzo nascosta: su un tablet il piede, dove stanno «Fine ciclo misura» e il tastierino, finiva sotto il bordo. Ora: .tmf-page e .tmf-page-narrow in themes.css, con i valori esatti della navbar; scrollbar-gutter: stable; 100dvh dove serve. Due larghezze in tutto il prodotto al posto di sette, e il criterio è il tipo di pagina — liste, tabelle e tele stanno larghe, i moduli stanno stretti. Login e schermata di misura tengono la loro geometria, per i motivi scritti in docs/architecture/LAYOUT.md e ripetuti in EXEMPT dentro il test: la prima non ha navbar a cui allinearsi, la seconda non deve scorrere mentre si misura. Il test è statico e guarda i sorgenti: la vista scritta domani copierà la cornice dalla vicina, ed è lì che la deriva ricomincia. Resta da chiedere all'operatore su quale schermo l'ha vista: in verticale su un tablet quasi tutte quelle larghezze collassano e il difetto non si nota. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
"""Point 14: the frame must not move when the operator changes view.
|
||||
|
||||
The report was «le dimensioni delle viste cambiano a seconda del menu». On the
|
||||
code it was seven different page widths across seventeen templates, none of them
|
||||
the width of the navbar sitting above them, plus a scrollbar that came and went.
|
||||
|
||||
This test guards the part that is easy to undo: the next page someone writes will
|
||||
copy the shell from a neighbour, and if a neighbour has gone back to declaring its
|
||||
own `container mx-auto ... max-w-5xl` the drift starts again.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
TEMPLATES = Path(__file__).resolve().parents[1] / "templates"
|
||||
|
||||
# Two views own their geometry on purpose, and say why here so that a reader does
|
||||
# not have to guess whether they were forgotten:
|
||||
# login — a full-viewport splash, no navbar to line up with;
|
||||
# task_execute — the measurement screen, a fixed full-height instrument panel
|
||||
# that must not scroll while an operator is measuring.
|
||||
EXEMPT = {"auth/login.html", "measure/task_execute.html"}
|
||||
|
||||
# What a page must not go back to doing: setting its own width and padding.
|
||||
FORBIDDEN = ("container mx-auto", "max-w-2xl mx-auto", "max-w-3xl mx-auto",
|
||||
"max-w-4xl mx-auto", "max-w-5xl mx-auto", "max-w-6xl mx-auto",
|
||||
"max-w-7xl mx-auto")
|
||||
|
||||
|
||||
def _views():
|
||||
for path in sorted(TEMPLATES.rglob("*.html")):
|
||||
rel = path.relative_to(TEMPLATES).as_posix()
|
||||
if rel.startswith("components/") or rel == "base.html":
|
||||
continue
|
||||
yield rel, path
|
||||
|
||||
|
||||
def test_every_view_uses_the_shared_shell():
|
||||
"""One frame, declared in one place, so it cannot drift per page."""
|
||||
missing = [
|
||||
rel for rel, path in _views()
|
||||
if rel not in EXEMPT and "tmf-page" not in path.read_text(encoding="utf-8")
|
||||
]
|
||||
assert not missing, f"viste senza la cornice condivisa: {missing}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("rel,path", list(_views()))
|
||||
def test_no_view_declares_its_own_width(rel, path):
|
||||
if rel in EXEMPT:
|
||||
pytest.skip("geometria propria, documentata in EXEMPT")
|
||||
html = path.read_text(encoding="utf-8")
|
||||
found = [f for f in FORBIDDEN if f in html]
|
||||
assert not found, (
|
||||
f"{rel} torna a dichiarare la propria larghezza ({found}): "
|
||||
"usare .tmf-page / .tmf-page-narrow"
|
||||
)
|
||||
|
||||
|
||||
def test_the_shell_matches_the_navbar():
|
||||
"""The content lines up with the bar above it, or the page looks like it has
|
||||
two widths at once - which is what the operator was seeing."""
|
||||
navbar = (TEMPLATES / "components/navbar.html").read_text(encoding="utf-8")
|
||||
assert "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" in navbar, (
|
||||
"la navbar ha cambiato cornice: allineare .tmf-page in themes.css"
|
||||
)
|
||||
css = (
|
||||
TEMPLATES.parent / "static/css/themes.css"
|
||||
).read_text(encoding="utf-8")
|
||||
assert "max-width: 80rem;" in css # 80rem == max-w-7xl
|
||||
assert "scrollbar-gutter: stable;" in css
|
||||
Reference in New Issue
Block a user