From b72048e40bc50f61ae02d2b47513fb2df4931f66 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Aug 2026 18:39:56 +0000 Subject: [PATCH] =?UTF-8?q?Ett=20designsystem,=20inte=20tv=C3=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Portalen höll åtta färger. Arbetsvyn höll fyrtiosju. Det var inte ett slarv utan två system: ui.tsx bar en egen designfilosofi i sitt filhuvud — "industriellt verkstadsverktyg i ETKA-tradition", egen palett, eget snitt, egen rytm — vid sidan av ALVA-SPEC-001. Två beskrivningar av hur en produkt ska se ut blir aldrig lika. Värst syntes det i typsnittet. Arbetsvyn låg utanför `.alva-yta` och ärvde därför värdapplikationens antikva — exakt samma fynd som en gång gjordes om portalen, fast här hade det stått kvar. En tekniker som klickade från portalen in i ett ärende bytte både färg och bokstavsform. De fyrtiosju färgerna avbildas nu på ALVA:s åtta. Tonade fyllningar avskaffas helt: allvarlighet bärs i linje och text, aldrig i en fylld yta. En gul varningsruta drar mer uppmärksamhet än sitt innehåll, och när tre ligger på samma skärm bär ingen av dem något. Grönt finns inte i paletten — blått bär "verifierat", vilket STATUSFARG redan sa. Skuggor och övergångar bort. Panelen är samma form som portalens Block, verktygsraden samma vita rad med en linje under, och ordmärket står först i den precis som i portalens sidhuvud. 176 avståndsklasser snäpptes till 8 px-rutnätet. Knapparna är gemener nu, i BÅDA ytorna. Etiketter och rubriker är versala i ALVA — de är uppmärkning. En knapp är en uppmaning, och i arbetsvyn är uppmaningen ofta en hel mening: "Photograph the instrument panel". Versal sats läses långsammare och bryts sämre, och just den knappen trycks med handskar bredvid ett fordon. Att låta portalen behålla versaler hade gett två knappspråk i samma produkt, vilket var det som skulle bort. Designtestet omfattar nu hela ytan — marknadswebb, portal OCH arbetsvy. Att det bara gällde halva produkten var själva orsaken till att de kunde glida isär: ett designsystem som gäller halva ytan är en stilguide. Mutationsprövat: en enda återinförd blå utanför paletten fäller det. Två fel som bara en skärmdump från en telefon kunde hitta, båda samma sak — text som var vit eller ljusgrå mot den gamla mörkblå raden och blev osynlig mot den vita: tiden i ärendehuvudet och användarnamnet i listan. Tjugo svenska strängar till, alla i mall-literaler och därför osynliga för de tidigare sveparna: loggens radtexter, överlämningsrapporten, AI-promptens fältnamn och "1 tim 37 min". 750 tester, 186 integrationskontroller, genomgång, portalspärr, typkontroll och lint gröna. Artefakten mätt på 390 och 1280 px över femton vyer: inget spill, inga externa begäranden. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt --- felsokning/app/src/alva/komponenter.tsx | 14 +- .../app/src/felsokning/Bilagevisning.tsx | 2 +- .../app/src/felsokning/DelatArendeVy.tsx | 58 +-- felsokning/app/src/felsokning/Mikrofon.tsx | 6 +- felsokning/app/src/felsokning/Slutsats.tsx | 4 +- .../app/src/felsokning/__tests__/ai.test.ts | 6 +- .../src/felsokning/__tests__/alva-yta.test.ts | 23 +- .../app/src/felsokning/__tests__/demo.test.ts | 2 +- .../app/src/felsokning/__tests__/ecm.test.ts | 2 +- felsokning/app/src/felsokning/ai.ts | 8 +- felsokning/app/src/felsokning/demo.ts | 10 +- felsokning/app/src/felsokning/domain.ts | 24 +- felsokning/app/src/felsokning/ecm.ts | 4 +- felsokning/app/src/felsokning/projektioner.ts | 8 +- felsokning/app/src/felsokning/ui.tsx | 161 +++++-- .../app/src/pages/felsokning/ArendeSida.tsx | 433 +++++++++--------- .../app/src/pages/felsokning/Arendelista.tsx | 70 +-- .../app/src/pages/felsokning/DelatArende.tsx | 2 +- .../src/pages/felsokning/Installningar.tsx | 34 +- .../app/src/pages/felsokning/NyttArende.tsx | 52 +-- .../app/src/pages/felsokning/Oversikt.tsx | 54 +-- .../src/pages/felsokning/PublikDelning.tsx | 2 +- 22 files changed, 551 insertions(+), 428 deletions(-) diff --git a/felsokning/app/src/alva/komponenter.tsx b/felsokning/app/src/alva/komponenter.tsx index 8c3fe51..f260abe 100644 --- a/felsokning/app/src/alva/komponenter.tsx +++ b/felsokning/app/src/alva/komponenter.tsx @@ -341,7 +341,19 @@ export function Knapp({ // `h-full` och inte bara `w-full`: lika breda knappar där den ena // texten bryts till två rader blir olika HÖGA, vilket är samma // obalans en rad ned. Bägge fyller sin cell. - className={`border px-4 py-2 text-[13px] font-semibold uppercase tracking-[0.08em] disabled:opacity-40${ + // Gemener, inte versaler. + // + // Etiketter och rubriker är versala i ALVA — de är uppmärkning, och + // ett tekniskt dokument märker upp sina fält så. En knapp är inte + // uppmärkning utan en uppmaning, och i arbetsvyn är uppmaningen ofta + // en hel mening: "Photograph the instrument panel". Versal sats är + // långsammare att läsa och bryts sämre, och just den knappen trycks + // med handskar bredvid ett fordon. + // + // Regeln gäller båda ytorna. Att låta portalen behålla versaler hade + // gett två knappspråk i samma produkt, vilket är precis det som + // skulle bort. + className={`border px-4 py-2 text-[13px] font-semibold tracking-[0.02em] disabled:opacity-40${ bred ? " h-full w-full" : "" }`} style={{ diff --git a/felsokning/app/src/felsokning/Bilagevisning.tsx b/felsokning/app/src/felsokning/Bilagevisning.tsx index 885eec1..bccf028 100644 --- a/felsokning/app/src/felsokning/Bilagevisning.tsx +++ b/felsokning/app/src/felsokning/Bilagevisning.tsx @@ -28,7 +28,7 @@ function useKalla(bilaga: Bilaga, delningskod?: string): string | null | "laddar function Platshallare({ text }: { text: string }) { return ( -

+

{text}

); diff --git a/felsokning/app/src/felsokning/DelatArendeVy.tsx b/felsokning/app/src/felsokning/DelatArendeVy.tsx index 56cd270..70ef5c9 100644 --- a/felsokning/app/src/felsokning/DelatArendeVy.tsx +++ b/felsokning/app/src/felsokning/DelatArendeVy.tsx @@ -28,8 +28,8 @@ function BeslutsKnappar({ const [fel, setFel] = useState(""); return ( -
-

+

+

Your decision to the workshop

@@ -43,14 +43,14 @@ function BeslutsKnappar({ {val && ( <> {fel &&

{fel}

} @@ -66,7 +66,7 @@ function BeslutsKnappar({ > {skickar ? "Sending …" : "Send decision"} -

+

The decision is recorded in the case and cannot be changed here — contact the workshop if you change your mind.

@@ -88,14 +88,14 @@ function IdentitetsPanel({ arende, avslutat }: { arende: Arende; avslutat: boole ["Status", avslutat ? "Closed" : "Diagnosis in progress"], ]; return ( -
+
{idn.beskrivning &&

{idn.beskrivning}

}

{falt .filter(([, varde]) => varde) .map(([etikett, varde]) => ( - {etikett}: {varde} + {etikett}: {varde} ))}

@@ -145,8 +145,8 @@ export function DelatArendeVy({ rubrik={b.objekt?.beskrivning ?? "Case"} hoger={ {avslutat ? "Closed" : "Diagnosis in progress"} @@ -185,7 +185,7 @@ export function DelatArendeVy({ {rader.map(([etikett, text]) => (
{etikett}
-
{text}
+
{text}
))} @@ -193,7 +193,7 @@ export function DelatArendeVy({ ); })()} -

+

{notis}

@@ -212,20 +212,20 @@ export function DelatArendeVy({

{h.beskrivning}

{h.uppskattadKostnad && ( -

Uppskattad kostnad: {h.uppskattadKostnad}

+

Uppskattad kostnad: {h.uppskattadKostnad}

)}
); })} {beslut?.handelse.typ === "kundbeslut" ? ( -

+

Ditt besked: {KUNDBESLUT_LABEL[beslut.handelse.beslut]}{" "} - (registrerat via {beslut.handelse.kanal}) + (registrerat via {beslut.handelse.kanal})

) : vidBeslut ? ( ) : ( -

+

Awaiting your decision — contact the workshop to approve or decline.

)} @@ -234,11 +234,11 @@ export function DelatArendeVy({ {b.utfordaKontroller.map((k, i) => ( -

{k.text}

+

{k.text}

))} - {!avslutat && pagaende &&

{pagaende}

} + {!avslutat && pagaende &&

{pagaende}

} {b.ejKontrollerat.slice(avslutat ? 0 : 1).map((e, i) => ( -

{e}

+

{e}

))}
@@ -246,8 +246,8 @@ export function DelatArendeVy({ {klipp.map((v, i) => (
- -
{v.beskrivning}
+ +
{v.beskrivning}
))}
@@ -257,8 +257,8 @@ export function DelatArendeVy({
{bilder.map((bild, i) => (
- -
{bild.beskrivning}
+ +
{bild.beskrivning}
))}
@@ -273,9 +273,9 @@ export function DelatArendeVy({ const h = p.handelse; if (h.typ !== "matvarde") return null; return ( - - {h.beskrivning} - + + {h.beskrivning} + {h.varde} {h.enhet ? ` ${h.enhet}` : ""} @@ -289,8 +289,8 @@ export function DelatArendeVy({ {kundposter.map((post) => ( -

- {tidKlockslag(post.tidpunkt)}{" "} +

+ {tidKlockslag(post.tidpunkt)}{" "} {handelseRubrik(post)}

))} @@ -305,13 +305,13 @@ export function DelatArendeVy({

{b.totalArbetstid}

{tidsfordelningsRader(arende, nu).map((r) => ( -

+

{r.label}: {r.tid}

))}
-

+

Case #{arende.nummer} · started {tidDatum(arende.skapad)} · generated from the case log

diff --git a/felsokning/app/src/felsokning/Mikrofon.tsx b/felsokning/app/src/felsokning/Mikrofon.tsx index 6cdc173..bac9dd7 100644 --- a/felsokning/app/src/felsokning/Mikrofon.tsx +++ b/felsokning/app/src/felsokning/Mikrofon.tsx @@ -48,10 +48,10 @@ export function MikrofonKnapp({ paText }: { paText: (text: string) => void }) { type="button" onClick={() => (lyssnar ? stoppa() : starta())} aria-label={lyssnar ? "Stop recording" : "Start voice input"} - className={`min-h-8 min-w-11 border px-3 text-[14px] font-semibold transition-colors ${ + className={`min-h-8 min-w-11 border px-4 text-[14px] font-semibold ${ lyssnar - ? "animate-pulse border-[#6E1414] bg-[#8B1A1A] text-white" - : "border-[#ADADAD] bg-white text-[#1A1A1A] hover:border-[#00437A]" + ? "border-[#8B1A1A] bg-[#8B1A1A] text-white" + : "border-[#D7DCE2] bg-white text-[#1B1E22] hover:border-[#005CA9]" }`} > {lyssnar ? "■" : } diff --git a/felsokning/app/src/felsokning/Slutsats.tsx b/felsokning/app/src/felsokning/Slutsats.tsx index c807a29..af41879 100644 --- a/felsokning/app/src/felsokning/Slutsats.tsx +++ b/felsokning/app/src/felsokning/Slutsats.tsx @@ -171,7 +171,7 @@ export function Slutsatspanel({ arende, skicka }: { arende: Arende; skicka: (h: > {etikett} -

+

{f.fraga}