Text som lade sig ovanpå text — och kontrollen som inte såg det

Etiketten AUTHENTICATION är bredare än 96 px och kan inte brytas. Den
staplade mobilformen gav den `w-[96px]`, så texten rann ut ur sin ruta
medan värdet började vid 96 px. Bokstäverna hamnade ovanpå varandra.
`min-w` låter korta etiketter hålla kolumnen och långa knuffa värdet
till nästa rad i stället.

Det allvarligare var att breddmätningen i genomgången inte kunde ha
hittat det här: överspill gör inte dokumentet bredare. Ett första försök
att jämföra elementens rutor rapporterade noll krockar med felet
återinlagt — rutorna skar aldrig varandra, bara bokstäverna gjorde det.
Mätningen jämför därför nu elementets textbredd med dess egen ruta, och
görs över portalens åtta vyer i telefonbredd.

Bevisad: med `w-[96px]` återinlagt faller genomgången med exitkod 1 och
pekar på kunskapskällor och Authentication. Med `min-w` grön igen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
Claude
2026-08-06 10:35:57 +00:00
parent b457543ede
commit c9fdef27c9
2 changed files with 59 additions and 1 deletions
+52
View File
@@ -31,6 +31,12 @@
// schemat stängdes utifrån vad koden borde skicka, och det
// enda som räknas är vad den skickar.
//
// Layout Text som rinner ur sin ruta. En etikett med fast bredd
// lade sig ovanpå sitt värde i portalen, och breddmätningen
// såg det inte: överspill gör inte dokumentet bredare. Den
// här mätningen jämför elementets textbredd med dess egen
// ruta, vilket är det enda som fångar just det felet.
//
// Budget Interaktioner per ärende. Ett verktyg som tyst växer från
// 65 klick till 90 överges i verkstaden långt innan någon
// skriver en felrapport. Taket är därför ett testvillkor,
@@ -369,6 +375,26 @@ async function kor(sida, fall) {
};
}
/**
* Letar text som rinner ur sin ruta.
*
* Bara element med egen text och synlig overflow: ett spill där räknas
* alltid som ett fel, eftersom bokstäverna hamnar ovanpå grannen.
*/
async function overspill(sida) {
return sida.evaluate(() =>
[...document.querySelectorAll("body *")]
.filter((el) => {
if (el.children.length > 0) return false;
if (!(el.textContent ?? "").trim()) return false;
if (getComputedStyle(el).overflowX !== "visible") return false;
return el.clientWidth > 0 && el.scrollWidth - el.clientWidth > 1;
})
.slice(0, 5)
.map((el) => `"${(el.textContent ?? "").trim().slice(0, 30)}" (+${el.scrollWidth - el.clientWidth} px)`),
);
}
// ---- Körning -------------------------------------------------------------
//
// Bygget görs härifrån, med den miljö genomgången kräver, i stället för
@@ -433,6 +459,32 @@ try {
if (r.interaktioner > BUDGET) fel.push(`${fall.id}: ${r.interaktioner} interaktioner överskrider budgeten ${BUDGET}`);
if (r.handelser === 0) fel.push(`${fall.id}: ingen händelse hamnade i loggen`);
}
// ---- Layoutkontroll i telefonbredd ------------------------------------
//
// Portalens vyer har inga ärenden att köra igenom, men de är det som
// faktiskt gick sönder. Mätningen görs där felet uppstod.
const ctx = await webblasare.newContext({ viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true });
const sida = await ctx.newPage();
for (const vag of [
"/alva",
"/alva/ansokan",
"/alva/logga-in",
"/alva/portal",
"/alva/portal/analys",
"/alva/portal/kunskapskallor",
"/alva/portal/integration",
"/alva/portal/fakturor",
]) {
await sida.goto(`${BAS}/#${vag}`, { waitUntil: "networkidle" });
await sida.waitForTimeout(400);
const bred = await sida.evaluate(() => Math.max(document.documentElement.scrollWidth, document.body.scrollWidth));
if (bred > 391) fel.push(`${vag}: dokumentet är ${bred} px brett på en 390 px-skärm`);
const spill = await overspill(sida);
if (spill.length > 0) fel.push(`${vag}: text rinner ur sin ruta — ${spill.join(" · ")}`);
}
await ctx.close();
} finally {
await webblasare.close();
server.close();
+7 -1
View File
@@ -393,8 +393,14 @@ export function Tabell({ kolumner, rader }: { kolumner: string[]; rader: ReactNo
>
{rad.map((cell, j) => (
<div key={j} className="mt-2 flex flex-wrap items-baseline gap-2 first:mt-0">
{/* `min-w` och inte `w`: en etikett som AUTHENTICATION är
bredare än 96 px och kan inte brytas, så en fast bredd
fick den att spilla ut ur sin ruta och lägga sig ovanpå
värdet. Med en minimibredd håller korta etiketter
kolumnen, och långa knuffar i stället värdet till nästa
rad — behållaren bryter redan. */}
<dt
className="w-[96px] shrink-0 text-[11px] font-semibold uppercase tracking-[0.08em]"
className="min-w-[96px] shrink-0 text-[11px] font-semibold uppercase tracking-[0.08em]"
style={{ color: FARG.steel }}
>
{kolumner[j]}