{
  "system": "Startsidan – Sundsvalls kommun",
  "version": 1,
  "kalla": {
    "tokens": "packages/tokens/src/theme.css",
    "primitiver": "packages/ui/src",
    "skal": "apps/portal/src/shell",
    "specifikation": "apps/portal/src/app/dev/ui/system"
  },
  "sprak": "sv",
  "regler": [
    {
      "id": "R1",
      "regel": "Grönt är status – klart, godkänt, positivt – och betyder aldrig AI. AI-ytor tonas med --color-ai-* i blått.",
      "darfor": "Grönt betyder klart och godkänt överallt annars. Delar AI tonen med 'klart' blir båda signalerna otydliga – och en grön AI-yta läses som godkänd (ADR-026)."
    },
    {
      "id": "R2",
      "regel": "Allt AI-genererat märks synligt med <AiChip> – texten '✦ AI-genererat' eller en mer precis notis.",
      "darfor": "Märkningen är ett löfte till användaren, inte en dekoration. Den får inte kunna glömmas bort."
    },
    {
      "id": "R3",
      "regel": "Blått betyder identitet: den här personen, den här saken. Avvikande färg betyder status.",
      "darfor": "En avatar i rött skulle läsas som att personen är försenad. Identitet och status får därför aldrig dela färgskala."
    },
    {
      "id": "R4",
      "regel": "Färg kommer alltid ur tokens: inga hex-värden i className. Mått tas ur Tailwinds skala, och där den inte räcker skrivs godtyckliga värden i rem – aldrig i px.",
      "darfor": "Ett hårdkodat hex-värde överlever inte nästa temaändring och syns inte i den här specifikationen. Rotens font-size är 62,5 %, så ett px-mått bryter skalningen när användaren ändrar textstorlek – medan rem följer med. Båda halvorna fångas av eslint-regeln startsidan/inga-hardkodade-designvarden."
    },
    {
      "id": "R5",
      "regel": "Statusfärg bärs av en kantlinje, ett chip eller ett enskilt värde – aldrig av en hel yta. Undantag: dialogstödets områdeskort, där hela kortet fylls i statusens ljusa ton (green-bg, amber-bg, red-bg) utan ram, och statusen alltid står i text.",
      "darfor": "Fylls hela kortet i rött tar en enskild avvikelse över hela arbetsytans intryck. Dialogstödets kort är undantaget för att vyn visas i ett möte: där är lägena själva samtalsämnet och ska synas på avstånd. Undantaget gäller inte startsidan eller andra vyer."
    },
    {
      "id": "R6",
      "regel": "Moduler skriver Tailwind-utilities. CSS i shell.css ägs av skalet och rörs inte av moduler.",
      "darfor": "Skalets krom är porterad 1:1 från prototypen. Ändras den från en modul går måtten isär."
    },
    {
      "id": "R7",
      "regel": "Server components som default. \"use client\" bara på det som faktiskt behöver interaktion.",
      "darfor": "Modulerna hämtar sin egen data på servern (ADR-006). Ett onödigt klientlager tvingar upp datat i props."
    },
    {
      "id": "R8",
      "regel": "Varje kort får en tillgänglig rubrik: <Card headingId=\"…\" title=\"…\">.",
      "darfor": "headingId blir sektionens aria-labelledby. Utan den är kortet en namnlös region för skärmläsaren."
    },
    {
      "id": "R9",
      "regel": "Svenska i all UI-text, i props-värden som visas, och i commit-ämnen.",
      "darfor": "Prototypen visas för kommunens medarbetare. Blandat språk läcker igenom i demolägen."
    },
    {
      "id": "R10",
      "regel": "TypeScript strict, inga any. Varianter uttrycks som unionstyper, inte som fri sträng.",
      "darfor": "Unionen är det som gör varianterna uppräkningsbara – både för kompilatorn och för den här specifikationen."
    },
    {
      "id": "R11",
      "regel": "Ett kort i main eller side fungerar i båda spalterna. Anpassa efter kortets egen bredd med containerfrågor (@3xl = 48rem), aldrig efter fönstret (md:, lg:).",
      "darfor": "Användaren flyttar korten mellan spalterna (ADR-018). Ett kort som bara fungerar brett går sönder i högerspalten, och en fönsterbrytpunkt vet inte vilken spalt kortet står i."
    },
    {
      "id": "R12",
      "regel": "Allt UI uppfyller WCAG 2.2 nivå AA: allt nås och används med tangentbordet, fokus syns och döljs inte av något klistrat, klickytor är minst 24 px, inget rullar i sidled vid 320 px utom tabeller och kod, och läget sägs med aria (aria-current, aria-expanded), inte bara med färg.",
      "darfor": "Kommunens webb omfattas av lagen om tillgänglighet till digital offentlig service. En yta som bara fungerar med mus eller på bred skärm stänger ute medarbetare. Huvudmenyn (ADR-033) är förebilden."
    }
  ],
  "tokens": {
    "farg": [
      {
        "id": "token.neutral",
        "namn": "Neutraler",
        "regel": "Bär all yta och all text som inte har en betydelse att signalera.",
        "tokens": [
          {
            "namn": "ink",
            "variabel": "--color-ink",
            "varde": "#1f1f25",
            "betydelse": "Brödtext och rubriker."
          },
          {
            "namn": "ink-soft",
            "variabel": "--color-ink-soft",
            "varde": "#4a4a55",
            "betydelse": "Metatext, andrahandsinformation."
          },
          {
            "namn": "surface",
            "variabel": "--color-surface",
            "varde": "#fff",
            "betydelse": "Kortets botten."
          },
          {
            "namn": "page",
            "variabel": "--color-page",
            "varde": "#edf1f5",
            "betydelse": "Sidans botten, och rader inuti kort."
          },
          {
            "namn": "page-deep",
            "variabel": "--color-page-deep",
            "varde": "#e4eaf0",
            "betydelse": "Ett steg mörkare botten, för helbreddsband som ska läsas som en annan del av sidan än den ovanför."
          },
          {
            "namn": "border",
            "variabel": "--color-border",
            "varde": "#dde3e9",
            "betydelse": "Kortets ram, tunna avdelare."
          },
          {
            "namn": "border-strong",
            "variabel": "--color-border-strong",
            "varde": "#c3ccd4",
            "betydelse": "Ram på interaktiva ytor, t.ex. ghost-knapp."
          },
          {
            "namn": "muted-bg",
            "variabel": "--color-muted-bg",
            "varde": "#eef0f2",
            "betydelse": "Dämpad chipbotten, listavdelare."
          },
          {
            "namn": "muted-tx",
            "variabel": "--color-muted-tx",
            "varde": "#5a6470",
            "betydelse": "Text på dämpad botten."
          },
          {
            "namn": "ink-faint",
            "variabel": "--color-ink-faint",
            "varde": "#9aa3ad",
            "betydelse": "Platshållartext: ett fält som väntar på innehåll. Läses som frånvaro, inte som dämpad information."
          }
        ]
      },
      {
        "id": "token.identitet",
        "namn": "Identitet – blå",
        "regel": "Blått betyder 'den här personen eller saken'. Aldrig status. En avatar är alltid blå, för en person har ingen status.",
        "tokens": [
          {
            "namn": "blue",
            "variabel": "--color-blue",
            "varde": "#005595",
            "betydelse": "Primärknapp, avatar, aktiv markering."
          },
          {
            "namn": "blue-h",
            "variabel": "--color-blue-h",
            "varde": "#004070",
            "betydelse": "Hovertillstånd för primärknapp."
          },
          {
            "namn": "blue-bg",
            "variabel": "--color-blue-bg",
            "varde": "#eaf1f8",
            "betydelse": "Ljus botten: hover på rader, info-chip."
          },
          {
            "namn": "blue-acc",
            "variabel": "--color-blue-acc",
            "varde": "#cfe0ec",
            "betydelse": "Accentram, t.ex. kort vid hover."
          },
          {
            "namn": "blue-tx",
            "variabel": "--color-blue-tx",
            "varde": "#004c85",
            "betydelse": "Länktext och text på blue-bg."
          },
          {
            "namn": "blue-lift",
            "variabel": "--color-blue-lift",
            "varde": "#1472b8",
            "betydelse": "Identitetsblå mot mörk platta, t.ex. avatar i fokuslägets topprad. Samma betydelse som blue, lyft för att synas."
          },
          {
            "namn": "sidebar",
            "variabel": "--color-sidebar",
            "varde": "#003c69",
            "betydelse": "Endast sidopanelens botten."
          }
        ]
      },
      {
        "id": "token.status",
        "namn": "Status",
        "regel": "Grönt = klart, gult = väntar, rött = tidsgräns eller avvikelse. Statusfärg bärs av en kantlinje, ett chip eller ett värde – aldrig av en hel yta.",
        "tokens": [
          {
            "namn": "green",
            "variabel": "--color-green",
            "varde": "#12874e",
            "betydelse": "Klart, godkänt. Grönt är bara status – aldrig AI (R1)."
          },
          {
            "namn": "green-bg",
            "variabel": "--color-green-bg",
            "varde": "#e8f4ec",
            "betydelse": "Botten i chip med tone=\"ok\"."
          },
          {
            "namn": "green-acc",
            "variabel": "--color-green-acc",
            "varde": "#c9e4d7",
            "betydelse": "Accentram för klart-tillstånd."
          },
          {
            "namn": "green-tx",
            "variabel": "--color-green-tx",
            "varde": "#0c6a3c",
            "betydelse": "Text på green-bg."
          },
          {
            "namn": "amber",
            "variabel": "--color-amber",
            "varde": "#b06a1f",
            "betydelse": "Väntar på någon annan."
          },
          {
            "namn": "amber-bg",
            "variabel": "--color-amber-bg",
            "varde": "#fbeede",
            "betydelse": "Botten i chip med tone=\"warn\"."
          },
          {
            "namn": "amber-tx",
            "variabel": "--color-amber-tx",
            "varde": "#8a5117",
            "betydelse": "Text på amber-bg."
          },
          {
            "namn": "red",
            "variabel": "--color-red",
            "varde": "#b3261e",
            "betydelse": "Tidsgräns passerad, avvikelse."
          },
          {
            "namn": "red-bg",
            "variabel": "--color-red-bg",
            "varde": "#fceceb",
            "betydelse": "Botten i chip med tone=\"avv\"."
          }
        ]
      },
      {
        "id": "token.ai",
        "namn": "AI – blå toner",
        "regel": "Tonen för ytor där AI gör något: AI-knapp, AI-fält, AI-kort och AiChip. Blå toner, ljusare än identitetsblått (ADR-026). Färgen är inte signalen – ✦-märkningen är det (R2). Använd aldrig --color-ai-* för status, och aldrig grönt för AI.",
        "tokens": [
          {
            "namn": "ai",
            "variabel": "--color-ai",
            "varde": "#1f64b8",
            "betydelse": "AI-knapp, AI-märke, AI-fältets fokusram."
          },
          {
            "namn": "ai-deep",
            "variabel": "--color-ai-deep",
            "varde": "#164c8f",
            "betydelse": "Hovertillstånd på AI-knapp."
          },
          {
            "namn": "ai-bg",
            "variabel": "--color-ai-bg",
            "varde": "#eef4fc",
            "betydelse": "Botten på AI-tonade kort och chips."
          },
          {
            "namn": "ai-acc",
            "variabel": "--color-ai-acc",
            "varde": "#c9dcf3",
            "betydelse": "Ram på AI-tonade ytor."
          },
          {
            "namn": "ai-tx",
            "variabel": "--color-ai-tx",
            "varde": "#164c8f",
            "betydelse": "Text på ai-bg."
          }
        ]
      },
      {
        "id": "token.graf",
        "namn": "Diagram",
        "regel": "Serierna i ett diagram får färg i den här ordningen och högst fyra – fler serier slås ihop till Övrigt eller delas i flera diagram. Färgen följer serien, aldrig dess rang. Inget grönt: grönt är status. Text i diagram är alltid ink eller ink-soft, aldrig seriens färg, och diagrammet har alltid en tabellvy.",
        "tokens": [
          {
            "namn": "graf-1",
            "variabel": "--color-graf-1",
            "varde": "#2a78d6",
            "betydelse": "Första serien."
          },
          {
            "namn": "graf-2",
            "variabel": "--color-graf-2",
            "varde": "#eb6834",
            "betydelse": "Andra serien."
          },
          {
            "namn": "graf-3",
            "variabel": "--color-graf-3",
            "varde": "#4a3aa7",
            "betydelse": "Tredje serien."
          },
          {
            "namn": "graf-4",
            "variabel": "--color-graf-4",
            "varde": "#e87ba4",
            "betydelse": "Fjärde serien. Under 3:1 mot vitt – kräver synliga etiketter eller tabell."
          }
        ]
      }
    ],
    "typografi": [
      {
        "namn": "text-eyebrow",
        "variabel": "--text-eyebrow",
        "varde": "1.1rem (11px)",
        "anvandning": "Versal etikett över ett nyckeltal."
      },
      {
        "namn": "text-xs",
        "variabel": "--text-xs",
        "varde": "1.2rem (12px)",
        "anvandning": "Metatext, chips, sidfot."
      },
      {
        "namn": "text-sm",
        "variabel": "--text-sm",
        "varde": "1.4rem (14px)",
        "anvandning": "Radrubriker, knappar."
      },
      {
        "namn": "text-base",
        "variabel": "--text-base",
        "varde": "1.6rem (16px)",
        "anvandning": "Brödtext."
      },
      {
        "namn": "text-h2",
        "variabel": "--text-h2",
        "varde": "1.7rem (17px)",
        "anvandning": "Kortrubrik. Sätts av Card, inte av modulen."
      },
      {
        "namn": "text-lg",
        "variabel": "--text-lg",
        "varde": "1.8rem (18px)",
        "anvandning": "AI-fältets inmatning, ingress."
      },
      {
        "namn": "text-kpi",
        "variabel": "--text-kpi",
        "varde": "3rem (30px)",
        "anvandning": "Nyckeltalets värde."
      }
    ],
    "form": [
      {
        "namn": "rounded-xs",
        "variabel": "--radius-xs",
        "varde": "0.6rem (6px)",
        "anvandning": "Chip, rad inuti kort."
      },
      {
        "namn": "rounded-sm",
        "variabel": "--radius-sm",
        "varde": "0.8rem (8px)",
        "anvandning": "Knapp."
      },
      {
        "namn": "rounded-md",
        "variabel": "--radius-md",
        "varde": "1rem (10px)",
        "anvandning": "shadcns `--radius`; sällan direkt."
      },
      {
        "namn": "rounded-lg",
        "variabel": "--radius-lg",
        "varde": "1.6rem (16px)",
        "anvandning": "Kort och nyckeltalsruta."
      },
      {
        "namn": "shadow-card",
        "variabel": "--shadow-card",
        "varde": "två lager, mjuk",
        "anvandning": "Allt som ligger på arbetsytan."
      },
      {
        "namn": "shadow-pop",
        "variabel": "--shadow-pop",
        "varde": "djup",
        "anvandning": "Menyer som svävar över ytan."
      },
      {
        "namn": "max-w-wrap",
        "variabel": "--container-wrap",
        "varde": "128rem (1280px)",
        "anvandning": "Innehållsbredd. Sätts av `.wrap`."
      }
    ],
    "rorelse": [
      {
        "namn": "animate-msg-in",
        "variabel": "--animate-msg-in",
        "varde": "0.25s ease",
        "anvandning": "Nytt meddelande i ett samtalsflöde."
      },
      {
        "namn": "animate-blink",
        "variabel": "--animate-blink",
        "varde": "1.2s infinite",
        "anvandning": "Assistenten skriver."
      },
      {
        "namn": "animate-flash",
        "variabel": "--animate-flash",
        "varde": "1s ease",
        "anvandning": "Fält som AI just fyllt i formulärspegeln."
      }
    ]
  },
  "poster": [
    {
      "id": "ui.card",
      "namn": "Card",
      "kategori": "primitiv",
      "import": "import { Card } from \"@startsidan/ui\";",
      "syfte": "Vitt kort med mjuk skugga – grundformen för allt innehåll på arbetsytan.",
      "narAnvands": "Varje gång en modul lägger något i sloten main eller side.",
      "narInte": "För nyckeltal (använd nyckeltalsrutan) och inuti ett annat kort – kort staplas inte i kort.",
      "props": [
        {
          "namn": "title",
          "typ": "ReactNode",
          "beskrivning": "Rubrik i kortets huvud. Utelämnas för kort utan huvud."
        },
        {
          "namn": "count",
          "typ": "ReactNode",
          "beskrivning": "Antal bakom rubriken, t.ex. 4 eller \"2/4\". Renderas med punkt före."
        },
        {
          "namn": "action",
          "typ": "ReactNode",
          "beskrivning": "Länk längst till höger i huvudet, normalt <MoreLink>."
        },
        {
          "namn": "headingId",
          "typ": "string",
          "beskrivning": "Id på rubriken. Blir sektionens aria-labelledby. Obligatorisk i praktiken."
        },
        {
          "namn": "tone",
          "typ": "\"default\" | \"ai\"",
          "varden": [
            "default",
            "ai"
          ],
          "standard": "default",
          "beskrivning": "ai ger AI-tonad botten och ram (blå toner). Endast för AI-ytor."
        },
        {
          "namn": "bodyClassName",
          "typ": "string",
          "beskrivning": "För innehåll som bär egen luft, t.ex. en tabell som ska gå kant i kant."
        },
        {
          "namn": "className",
          "typ": "string",
          "beskrivning": "Skriver över kortets egna utilities via tailwind-merge."
        }
      ],
      "kod": "<Card\n  headingId=\"arenden-h\"\n  title=\"Mina ärenden\"\n  count={4}\n  action={<MoreLink href=\"/flode/arenden\">Visa alla →</MoreLink>}\n>\n  <ul className=\"grid list-none\">{/* ListRow-rader */}</ul>\n</Card>",
      "regler": [
        "Rubriken sätts av Card. Skriv aldrig en egen <h2> inuti kortet.",
        "tone=\"ai\" kräver att innehållet faktiskt är AI-genererat (R1, R2).",
        "Kortet är en <section>; headingId gör den namngiven (R8).",
        "Huvudet radbryter: blir det trångt hamnar action under rubriken, och antalet följer rubrikens sista ord (ADR-018)."
      ],
      "fel": [
        {
          "fel": "<Card className=\"rounded-xl\">",
          "ratt": "<Card>",
          "varfor": "rounded-lg är V3:s kortform och sätts redan; rounded-xl är shadcns default som medvetet skrivs över."
        }
      ]
    },
    {
      "id": "ui.chip",
      "namn": "Chip",
      "kategori": "primitiv",
      "import": "import { Chip, type ChipTone } from \"@startsidan/ui\";",
      "syfte": "Litet statusmärke med punkt före texten.",
      "narAnvands": "När en rad eller ett kort har ett tillstånd som får plats i ett ord.",
      "narInte": "För att märka AI-innehåll – där gäller AiChip. Och aldrig som knapp.",
      "props": [
        {
          "namn": "tone",
          "typ": "ChipTone",
          "varden": [
            "ok",
            "warn",
            "info",
            "muted",
            "avv"
          ],
          "standard": "muted",
          "beskrivning": "ok = klart, warn = väntar, info = neutral upplysning, muted = utan laddning, avv = avvikelse eller passerad tidsgräns."
        },
        {
          "namn": "children",
          "typ": "ReactNode",
          "beskrivning": "Etiketten. Ett till två ord på svenska."
        }
      ],
      "kod": "<Chip tone=\"ok\">Klart</Chip>\n<Chip tone=\"warn\">Väntar på chef</Chip>\n<Chip tone=\"avv\">Försenad</Chip>",
      "regler": [
        "Tonen ska följa av data, inte väljas för utseendet.",
        "ok använder statusgrönt (green-bg/green-tx). AI-tonen är blå och betyder aldrig status (R1)."
      ]
    },
    {
      "id": "ui.aichip",
      "namn": "AiChip",
      "kategori": "primitiv",
      "import": "import { AiChip } from \"@startsidan/ui\";",
      "syfte": "Märkning av AI-genererat innehåll.",
      "narAnvands": "På allt en modell har skrivit, sammanfattat, föreslagit eller fyllt i – utan undantag.",
      "narInte": "På innehåll som kommer ur ett API eller en regel. Då är märket en osanning.",
      "props": [
        {
          "namn": "children",
          "typ": "ReactNode",
          "standard": "✦ AI-genererat",
          "beskrivning": "Ersätt standardtexten när en mer precis notis säger mer, t.ex. \"✦ Sammanfattad av AI\"."
        }
      ],
      "kod": "<AiChip />\n<AiChip>✦ Föreslagen prioritet</AiChip>",
      "regler": [
        "Får aldrig utelämnas på AI-ytor (R2).",
        "Stjärnan ✦ är märkets tecken och byts inte ut."
      ]
    },
    {
      "id": "ui.btn",
      "namn": "Btn / BtnLink",
      "kategori": "primitiv",
      "import": "import { Btn, BtnLink } from \"@startsidan/ui\";",
      "syfte": "Knappen i V3:s mått. BtnLink är samma form som ankare.",
      "narAnvands": "Btn för något som händer på sidan, BtnLink för något som leder vidare.",
      "narInte": "Använd inte shadcns Button direkt – den saknar V3:s mått och varianten ai.",
      "props": [
        {
          "namn": "variant",
          "typ": "BtnVariant",
          "varden": [
            "primary",
            "ghost",
            "ai"
          ],
          "standard": "primary",
          "beskrivning": "primary = blå, huvudhandlingen. ghost = vit med bård, sidohandling. ai = AI-blå, endast för handlingar som utför något AI-genererat."
        },
        {
          "namn": "size",
          "typ": "BtnSize",
          "varden": [
            "md",
            "sm"
          ],
          "standard": "md",
          "beskrivning": "sm används i rader och korthuvuden."
        }
      ],
      "kod": "<Btn>Godkänn</Btn>\n<Btn variant=\"ghost\" size=\"sm\">Visa detaljer</Btn>\n<Btn variant=\"ai\">✦ Sammanfatta åt mig</Btn>\n<BtnLink href=\"/flode/felanmalan\">Felanmäl</BtnLink>",
      "regler": [
        "Ett kort har högst en primary. Fler och blicken tappar riktning.",
        "variant=\"ai\" kräver att knappen startar något AI gör (R1).",
        "Fokus ritas av basens outline: 2 px i ring-färgen, utanför knappen. Släck den aldrig med outline-none (R12)."
      ]
    },
    {
      "id": "ui.statusrow",
      "namn": "StatusRow",
      "kategori": "primitiv",
      "import": "import { StatusRow, type RowStatus } from \"@startsidan/ui\";",
      "syfte": "Rad med färgad vänsterkant: titel, meta och handlingar till höger.",
      "narAnvands": "I \"Att agera på\" och överallt där raden i sig har ett tillstånd.",
      "narInte": "I en vanlig innehållslista utan tillstånd – där gäller ListRow.",
      "props": [
        {
          "namn": "status",
          "typ": "RowStatus",
          "varden": [
            "krit",
            "warn",
            "info",
            "ok",
            "neutral"
          ],
          "standard": "neutral",
          "beskrivning": "krit = röd kant (tidsgräns), warn = gul, info = blå, ok = grön, neutral = grå."
        },
        {
          "namn": "title",
          "typ": "ReactNode",
          "beskrivning": "Vad raden gäller."
        },
        {
          "namn": "meta",
          "typ": "ReactNode",
          "beskrivning": "När, av vem, varifrån."
        },
        {
          "namn": "end",
          "typ": "ReactNode",
          "beskrivning": "Chips och knappar, i ordningen AiChip → Chip → knapp."
        }
      ],
      "kod": "<ul className=\"grid list-none gap-2\">\n  <StatusRow\n    status=\"krit\"\n    title=\"Attestera tidrapport\"\n    meta=\"Tidsgräns passerad · 2 dagar\"\n    end={<BtnLink href=\"#\" size=\"sm\">Attestera</BtnLink>}\n  />\n</ul>",
      "regler": [
        "Renderas som <li> och måste ligga i en <ul>.",
        "Kanten bär statusen; resten av raden förblir lugn (R5).",
        "Blir raden för trång bryts handlingarna (end) till en egen rad under titeln – så fungerar den i båda spalterna (R11)."
      ]
    },
    {
      "id": "ui.listrow",
      "namn": "ListRow / Progress",
      "kategori": "primitiv",
      "import": "import { ListRow, Progress, Avatar } from \"@startsidan/ui\";",
      "syfte": "Rad i en lista inuti ett kort: tunn avdelare, ingen ram. Progress är förloppet under raden.",
      "narAnvands": "För innehållslistor: utvecklingsplaner, utbildningar, ärenden utan tabell.",
      "narInte": "När raden har ett tillstånd som ska synas i kanten – då StatusRow.",
      "props": [
        {
          "namn": "lead",
          "typ": "ReactNode",
          "beskrivning": "Något framför titeln, normalt <Avatar>."
        },
        {
          "namn": "title",
          "typ": "ReactNode",
          "beskrivning": "Radens rubrik."
        },
        {
          "namn": "meta",
          "typ": "ReactNode",
          "beskrivning": "Understödjande text."
        },
        {
          "namn": "end",
          "typ": "ReactNode",
          "beskrivning": "Chip eller värde längst till höger."
        },
        {
          "namn": "children",
          "typ": "ReactNode",
          "beskrivning": "Extra innehåll under metaraden, normalt <Progress>."
        },
        {
          "namn": "value (Progress)",
          "typ": "number",
          "beskrivning": "0–100. Klipps automatiskt till intervallet."
        },
        {
          "namn": "done (Progress)",
          "typ": "boolean",
          "standard": "false",
          "beskrivning": "Grön stapel när klar, annars blå."
        }
      ],
      "kod": "<ul className=\"grid list-none\">\n  <ListRow\n    lead={<Avatar initials=\"AL\" />}\n    title=\"Utvecklingsplan 2026\"\n    meta=\"Uppdaterad 12 mars\"\n    end={<Chip tone=\"warn\">Väntar</Chip>}\n  >\n    <Progress value={60} />\n  </ListRow>\n</ul>",
      "regler": [
        "Första raden får ingen toppluft och sista ingen avdelare – det sköts av primitiven.",
        "Progress är Radix under ytan; bygg aldrig en egen div med role=\"progressbar\"."
      ]
    },
    {
      "id": "ui.avatar",
      "namn": "Avatar",
      "kategori": "primitiv",
      "import": "import { Avatar } from \"@startsidan/ui\";",
      "syfte": "Identitetsikon med initialer.",
      "narAnvands": "När en rad handlar om en bestämd person.",
      "narInte": "Som statusmarkör. En person har ingen status (R3).",
      "props": [
        {
          "namn": "initials",
          "typ": "string",
          "beskrivning": "Ett till två versaler."
        },
        {
          "namn": "size",
          "typ": "\"sm\" | \"md\"",
          "varden": [
            "sm",
            "md"
          ],
          "standard": "md",
          "beskrivning": "sm i täta listor."
        }
      ],
      "kod": "<Avatar initials=\"KH\" />\n<Avatar initials=\"AL\" size=\"sm\" />",
      "regler": [
        "Alltid blå. Ingen variantprop för färg finns, och ska inte tillkomma."
      ]
    },
    {
      "id": "ui.morelink",
      "namn": "MoreLink",
      "kategori": "primitiv",
      "import": "import { MoreLink } from \"@startsidan/ui\";",
      "syfte": "Länken längst till höger i ett korthuvud: \"Visa alla →\".",
      "narAnvands": "När kortet visar ett urval och det finns en fullständig vy.",
      "props": [
        {
          "namn": "href",
          "typ": "string",
          "beskrivning": "Målet."
        },
        {
          "namn": "children",
          "typ": "ReactNode",
          "beskrivning": "Text med avslutande →."
        }
      ],
      "kod": "<Card title=\"Idag\" action={<MoreLink href=\"#\">Kalender →</MoreLink>}>…</Card>",
      "regler": [
        "Pilen → hör till texten, inte till en ikon."
      ]
    },
    {
      "id": "ui.table",
      "namn": "Table",
      "kategori": "primitiv",
      "import": "import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from \"@startsidan/ui\";",
      "syfte": "shadcns tabell, vidarelänkad för moduler som behöver rader med kolumner.",
      "narAnvands": "När data har fler än två jämförbara fält per rad, som i modulen arenden.",
      "narInte": "För listor med en rubrik och en metarad – där räcker ListRow.",
      "kod": "<Card headingId=\"arenden-h\" title=\"Mina ärenden\" bodyClassName=\"px-0 pb-0\">\n  <Table>\n    <TableHeader>\n      <TableRow><TableHead>Ärende</TableHead><TableHead>Status</TableHead></TableRow>\n    </TableHeader>\n    <TableBody>\n      <TableRow><TableCell>Behörighet Heroma</TableCell><TableCell><Chip tone=\"warn\">Väntar</Chip></TableCell></TableRow>\n    </TableBody>\n  </Table>\n</Card>",
      "regler": [
        "Tabellen bär egen luft: ge kortet bodyClassName så att raderna går kant i kant.",
        "Kolumnspåret måste kunna krympa (min-width: 0), annars trycker tabellen ut sidan."
      ]
    },
    {
      "id": "monster.nyckeltal",
      "namn": "Nyckeltalsruta",
      "kategori": "monster",
      "syfte": "Ett mätvärde med versal etikett, stort tal och en kommentar. Hela rutan är en länk.",
      "narAnvands": "I sloten kpi, som spänner över hela arbetsytans bredd i fyra spår.",
      "narInte": "För något som inte är ett tal. En status hör hemma i ett chip.",
      "kod": "<a href={post.lank} className=\"block rounded-lg border border-border bg-surface px-6 py-5 text-ink no-underline shadow-card transition-[transform,border-color] duration-150 hover:-translate-y-0.5 hover:border-blue-acc\">\n  <span className=\"flex items-center justify-between gap-2 font-heading text-eyebrow font-bold tracking-[0.12em] text-ink-soft uppercase\">\n    <span className=\"hyphens-auto [overflow-wrap:anywhere]\">Budgetutfall</span>\n    <i aria-hidden=\"true\" className=\"shrink-0 text-sm not-italic\">→</i>\n  </span>\n  <span className=\"mt-2.5 mb-1 block font-heading text-kpi leading-[1.1] font-extrabold text-green-tx\">\n    +2,4<small className=\"ml-1 text-sm font-bold text-ink-soft\">%</small>\n  </span>\n  <span className=\"block text-xs text-ink-soft\">Mot budget hittills i år</span>\n</a>",
      "regler": [
        "Värdet bär tonen (text-blue-tx, text-green-tx, text-amber), aldrig hela rutan (R5).",
        "Etiketten avstavas hellre än kapas – långa etiketter förekommer."
      ]
    },
    {
      "id": "monster.aiyta",
      "namn": "AI-yta",
      "kategori": "monster",
      "syfte": "Ett kort där innehållet är maskinskrivet: AI-tonad botten, ram och märke.",
      "narAnvands": "Sammanfattningar, förslag och allt annat en modell producerat och som visas som innehåll.",
      "narInte": "För något som bara handlar om AI. Ett kort som beskriver assistenten är vanligt innehåll.",
      "kod": "<Card headingId=\"sammanfattning-h\" tone=\"ai\" title=\"Din vecka\" action={<AiChip />}>\n  <p className=\"text-sm text-ink-soft\">\n    Tre ärenden väntar på dig, varav ett har passerat sin tidsgräns.\n  </p>\n  <div className=\"mt-4 flex gap-2\">\n    <Btn variant=\"ai\" size=\"sm\">✦ Föreslå ordning</Btn>\n    <Btn variant=\"ghost\" size=\"sm\">Visa källor</Btn>\n  </div>\n</Card>",
      "regler": [
        "tone=\"ai\" och AiChip hör ihop: den ena utan den andra är ett fel (R1, R2).",
        "En AI-yta ska kunna peka på sin källa – lämna plats för en källrad eller en länk."
      ]
    },
    {
      "id": "monster.agera",
      "namn": "Att agera på",
      "kategori": "monster",
      "syfte": "Kortet som aggregerar andra modulers actions till en enda handlingslista.",
      "narAnvands": "Bidra med rader genom slots.actions i modulens manifest. Rendera aldrig ett eget sådant kort.",
      "kod": "// packages/modules/<namn>/src/manifest.ts\nslots: {\n  actions: [\n    {\n      id: \"ledighet-attest\",\n      roles: [\"chef\"],\n      items: async (ctx) => [\n        {\n          id: \"att-1\",\n          status: \"warn\",\n          title: \"Attestera ledighet – Kim H\",\n          meta: \"Inkom 12 mars\",\n          chip: { tone: \"warn\", label: \"Väntar\" },\n          action: { href: \"/flode/ledighet\", label: \"Attestera\" },\n        },\n      ],\n    },\n  ],\n}",
      "regler": [
        "Modulen äger raden, kortet äger ordningen (ADR-007).",
        "status på raden ska följa av data: passerad tidsgräns → krit, väntar på någon → warn."
      ]
    },
    {
      "id": "monster.modulkort",
      "namn": "Modulkort",
      "kategori": "monster",
      "syfte": "Den vanligaste formen: en server component som hämtar sin data och renderar ett Card.",
      "narAnvands": "Som mall för varje nytt kort i sloten main eller side.",
      "kod": "import { Card, Chip, ListRow, MoreLink } from \"@startsidan/ui\";\nimport type { UserContext } from \"@startsidan/module-sdk\";\nimport { hamtaNagot } from \"../data\";\n\nexport async function NagotKort({ ctx }: { ctx: UserContext }) {\n  const { poster } = await hamtaNagot(ctx);\n\n  return (\n    <Card\n      headingId=\"nagot-h\"\n      title={ctx.role === \"chef\" ? \"Enhetens ärenden\" : \"Mina ärenden\"}\n      count={poster.length}\n      action={<MoreLink href=\"#\">Visa alla →</MoreLink>}\n    >\n      <ul className=\"grid list-none\">\n        {poster.map((p) => (\n          <ListRow key={p.id} title={p.rubrik} meta={p.kommentar}\n            end={<Chip tone={p.chip.ton}>{p.chip.etikett}</Chip>} />\n        ))}\n      </ul>\n    </Card>\n  );\n}",
      "regler": [
        "Ingen \"use client\". Datat hämtas på servern (R7, ADR-006).",
        "Rolltexten väljs i komponenten, inte i två olika manifestposter.",
        "key sätts på datats id, aldrig på index.",
        "Kortet fungerar i båda spalterna (R11). Användaren kan flytta det; anpassa med @3xl:, inte md:. Ge widgeten en label i manifestet – den läses upp på flyttknapparna."
      ]
    },
    {
      "id": "monster.fokus",
      "namn": "Fokusyta",
      "kategori": "monster",
      "syfte": "En nyhet i startsidans främsta plats, direkt under AI-fältet, för något alla ska se – tills de stänger den (ADR-021).",
      "narAnvands": "En viktig nyhet eller en förändring som rör allas arbete. Bidra genom slots.fokus; skalet renderar och äger stängningen.",
      "narInte": "För vanliga nyheter – de hör till nyhetskortet. Och aldrig för något som kräver en handling: det är en notis eller en rad i \"Att agera på\".",
      "kod": "// packages/modules/<namn>/src/manifest.ts\nslots: {\n  fokus: async (ctx) => ({\n    id: \"fokus-ny-startsida-2026-10\",   // nytt id = visas igen för alla\n    kicker: \"Fokusnyhet\",\n    title: \"Ny startsida ska göra arbetsdagen enklare för medarbetare och chefer\",\n    lede: \"Från och med i dag möts alla medarbetare av en ny startsida …\",\n    image: { src: \"https://…/bild.png\", alt: \"\" },      // dekorativ: tom alt\n    links: [{ label: \"Läs hela nyheten\", href: \"/verktyg/nyheter/ny-startsida\", primary: true }],\n    from: \"Kommunikationsenheten\",\n    published: \"2026-10-05\",\n  }),\n}",
      "regler": [
        "Bara en fokusyta åt gången – den nyaste som inte är stängd.",
        "Formad som en nyhet: bild, datum, rubrik, ingress, avsändare – och en primär länk till hela nyheten. Rubriken länkar dit också.",
        "Ljusblå botten (blue-bg) och blå ram (blue-acc), så att den sticker ut bland de vita korten. Aldrig grönt (R1) eller statusfärg (R5).",
        "Ligger direkt under AI-fältet. Ovanför hälsningen läses den som en introduktion, inte som en nyhet.",
        "Stäng flyttar fokus till AI-fältet, så att tangentbordsanvändaren inte tappar bort sig."
      ]
    },
    {
      "id": "monster.notis",
      "namn": "Notis",
      "kategori": "monster",
      "syfte": "Något som väntar på användaren, i klockan i sidopanelen. Leder alltid till funktionen där det görs (ADR-020).",
      "narAnvands": "När något har hänt eller väntar som användaren inte annars ser – ett samtal att godkänna, en tidsgräns, en ny nyhet. Bidra genom manifestets notifications.",
      "narInte": "För sådant som redan står på startsidan i \"Att agera på\" och inte har en egen funktion att leda till. En notis utan href är brus.",
      "kod": "// packages/modules/<namn>/src/manifest.ts\nnotifications: async (ctx) => [\n  {\n    id: \"personal:n-godkann-us-108\",     // stabilt – läst sparas på id\n    kind: \"Utvecklingssamtal\",           // funktionen, visas som etikett\n    title: \"Utvecklingssamtal att godkänna\",\n    meta: \"Hanna Sjöberg\",\n    time: \"2026-10-03\",\n    href: \"/verktyg/personal/m-108/utvecklingssamtal\",\n    status: \"warn\",                      // kantlinjen: krit, warn, info, ok\n  },\n]",
      "regler": [
        "href är obligatoriskt och pekar på funktionen, inte på en översikt.",
        "Räkna fram notisen ur datat när det går, så att den försvinner när saken är gjord.",
        "Statusfärgen bärs av kantlinjen, som i \"Att agera på\" (R5)."
      ]
    },
    {
      "id": "monster.sida",
      "namn": "Sida: ingång och artikel",
      "kategori": "monster",
      "syfte": "Redaktionellt innehåll under \"Sidor\" (ADR-003). Skalet äger layouten; modulen lämnar ett PageManifest och innehållet som block.",
      "narAnvands": "När innehållet är text och länkar som alla läser, inte uppgifter för en roll. layout: \"hub\" för en sektionsingång med snabbvägar, \"article\" för en innehållssida under den.",
      "narInte": "För en roll eller en uppgift – det är en widget eller ett fokusflöde. Och aldrig med en egen renderare eller egen sidtyp (AM1).",
      "kod": "// packages/modules/<namn>/src/manifest.ts\npages: [\n  {\n    slug: \"it-support\",\n    section: \"service-it-lokaler\",       // id ur skalets sektionslista\n    layout: \"hub\",\n    title: \"IT-support\",\n    lede: \"Hjälp med dator, telefon, konton och behörigheter.\",\n    quickLinks: [{ label: \"Felanmäl\", text: \"Skapa ett ärende\", href: \"/flode/felanmalan\", primary: true }],\n    ai: { suggestions: () => [{ label: \"Jag har glömt mitt lösenord\" }] },\n  },\n  {\n    slug: \"prioritering\",\n    section: \"service-it-lokaler\",\n    parent: \"it-support\",                  // ger /sidor/service-it-lokaler/it-support/prioritering\n    layout: \"article\",\n    title: \"Prioriteringsordning för IT-ärenden\",\n    kicker: \"IT-support · Styrande information\",\n    article: { owner: { name: \"IT-avdelningen\" }, review: \"Granskas årligen\" },\n    content: async () => ({ blocks: [{ type: \"heading\", level: 2, text: \"Varför prioriterar vi?\" }] }),\n  },\n]",
      "regler": [
        "Brödtexten är högst 72ch bred, i ett vitt kort på artikeln och som prose i kort på ingången.",
        "Innehåll från en extern källa märks alltid med källa, datum och länk till originalet. Märkningen är inte AI-tonad – den är inte AI (R2).",
        "En sparad version märks med ett gult chip: den väntar på källan (R5).",
        "Innehållet är ContentBlock, aldrig HTML. react/no-danger är en lintgrind (AM12).",
        "Länkar till en annan sida i startsidan går via next/link; externa öppnas i ny flik med ↗.",
        "Vanliga frågor och nivåer (P1–P4) är blocket details: native <details>, ett valfritt chip för status, block inuti (ADR-022).",
        "Levande innehåll på en ingång – driftstatus, ärenden – är modulens egna kort i PageManifest.slots, inte text. Redaktionellt är block, i content.cards.",
        "En inramad sida (layout \"embed\") fyller arbetsytan under menyraden, utan egen rubrik eller kant – som om man var på sidan. Sidans h1 finns för skärmläsare. Blockerar källan ramen visas en förklaring med gult chip, aldrig en tom ram.",
        "En länk med undersidor (PageLink.children) fälls ihop; första raden inuti är sidan själv. Använd det för webbkartor i stället för en egen lista."
      ]
    },
    {
      "id": "monster.varumarke",
      "namn": "Kommunens avsändare i toppraden",
      "kategori": "monster",
      "import": "import { Varumarke } from \"@startsidan/gemensamt/Varumarke\"; // apps/projekt/src/varumarke",
      "syfte": "Visa att sidan är Sundsvalls kommuns, med samma grundstruktur som kommunikationsguidens sidmall: logotyp, en lodrät linje och produktens namn. Hela raden är en länk till produktens första sida.",
      "narAnvands": "I projektportalens topprad (projektyta/Topprad) på alla dess sidor – roten, status, funktionerna, tidslinjen, materialet, visionen och rösten – i devportalens topprad, och på varje ny fristående yta som visas utanför skalet.",
      "narInte": "Inte i skalet (/start och modulvyerna): där är sidopanelen avsändaren. Och aldrig som rubrik inne på en sida.",
      "props": [
        {
          "namn": "produkt",
          "typ": "string",
          "beskrivning": "Produktens namn efter linjen, t.ex. \"Min startsida\"."
        },
        {
          "namn": "href",
          "typ": "string",
          "standard": "\"/\"",
          "beskrivning": "Produktens första sida."
        },
        {
          "namn": "etikett",
          "typ": "string",
          "standard": "\"till första sidan\"",
          "beskrivning": "Dold text efter det synliga namnet, så att länkens namn börjar med det som syns (WCAG 2.5.3)."
        }
      ],
      "kod": "<header className=\"border-b border-border bg-surface\">\n  <div className=\"mx-auto flex max-w-[104rem] items-center justify-between gap-4 px-4 py-3 sm:px-8\">\n    <Varumarke produkt=\"Min startsida\" />\n    {/* ytans knappar till höger; menyn i en egen rad under, se monster.huvudmeny */}\n  </div>\n</header>",
      "regler": [
        "Ordningen är alltid logotyp, lodrät linje, produktnamn – som i kommunikationsguidens topbar.",
        "Logotypen är kommunens fil i public/brand och byts aldrig mot text, en annan färg eller en egen bild.",
        "Måtten är mallens: logotypen 4rem hög, linjen 3.2rem i border-strong och namnet 1.9rem extrabold Raleway; under sm 3.2rem, 2.6rem och 1.5rem så att raden får plats på 375 px.",
        "/brand ligger utanför åtkomstspärren, eftersom avsändaren syns på de öppna sidorna."
      ]
    },
    {
      "id": "monster.hero",
      "namn": "Sidans hero",
      "kategori": "monster",
      "import": "import { Hero } from \"../projektyta/Hero\"; // apps/projekt/src/projektyta",
      "syfte": "Ge varje sida i projektets yta samma början: eyebrow, rubrik och ingress på ett ljust gråblått band direkt under toppraden. Den aktiva fliken i huvudmenyn har samma färg och blöder in i bandet, så att det syns var man är (ADR-033).",
      "narAnvands": "Överst på varje sida med <Topprad />, direkt efter toppraden eller brödsmulorna. Sidans sammanfattande delar – siffror, lägen, lagren – får ligga i den.",
      "narInte": "Inte i skalet eller modulvyerna. Inte för en enskild händelse som medvetet bryter profilen (workshopens mörka hero). Och aldrig med vit botten – heron är alltid --color-page-deep.",
      "props": [
        {
          "namn": "className",
          "typ": "string",
          "beskrivning": "Gäller rutnätet inuti: avstånd, spalter eller en annan bredd (devportalen 132rem)."
        }
      ],
      "kod": "<Hero>\n  <Eyebrow>Status</Eyebrow>\n  <h1 className=\"max-w-[22ch] font-heading text-[3.6rem] leading-[1.08] font-extrabold text-ink sm:text-[4.2rem]\">\n    Vad har hänt i projektet?\n  </h1>\n  <p className=\"max-w-[66ch] text-lg text-ink-soft\">…</p>\n</Hero>\n<div className=\"mx-auto grid max-w-[104rem] gap-10 px-4 pt-10 pb-16 sm:px-8\">…</div>",
      "regler": [
        "Bandet är --color-page-deep med border-b border-border; innehållet under ligger på --color-page.",
        "Heron står direkt under toppraden, eller under brödsmulorna som har samma band – annars blöder fliken in i fel yta.",
        "Länkar i ingressen stryks under (WCAG 1.4.1)."
      ]
    },
    {
      "id": "monster.huvudmeny",
      "namn": "Projektets huvudmeny",
      "kategori": "monster",
      "import": "import { Topprad } from \"../projektyta/Topprad\"; // apps/projekt/src/projektyta",
      "syfte": "Visa alla delar av tjänsten i en klassisk meny som uppfyller WCAG 2.2 AA (ADR-033): efter header A: en rad dockad mot toppradens underkant under avsändaren, grå text utan bakgrund, sidan man är på som en flik i heroens färg som blöder in i heron, grupper med en enkel lista – och under md en Meny-knapp med samma lista, en post per rad.",
      "narAnvands": "På varje sida i projektportalen, genom <Topprad />. En ny sorts innehåll läggs som en sida i projektyta/meny.ts, i raden eller i en grupp. Den som är inloggad med ett konto ser sin identitetsikon i övre högra hörnet, med vägen till kontot och för admin till kontona (projektyta/Kontomeny, ADR-039).",
      "narInte": "Inte för enskilda tillfällen – ett möte blir en händelse under Tidslinjen, en rapport en post under Status. Inte i skalet, där sidopanelen är navigeringen. Och aldrig som flikar som rullar i sidled.",
      "kod": "// projektyta/meny.ts – en ny sida i en grupp\n{ href: \"/material/ordlista\", namn: \"Ordlistan\" },\n\n// Sidan själv: toppraden från layouten, innehållet i main\n<Topprad />\n<main id=\"innehall\" tabIndex={-1} className=\"focus:outline-none\">…</main>",
      "regler": [
        "Disclosure-mönstret, inte role=\"menu\": länkar i listor, grupper som knappar med aria-expanded och aria-controls.",
        "Aktuell sida får aria-current=\"page\" och ett streck – aldrig bara en annan färg.",
        "Varje sida med toppraden har <main id=\"innehall\">, så att hopplänken har ett mål.",
        "Det som klistrar under toppraden använder top-(--topprad-hojd), aldrig en fast höjd. Toppraden klistrar bara från lg.",
        "Sidor bakom åtkomstspärren får last: true, så att menyn säger det före klicket.",
        "Ingen fet stil i raden: font-sans text-sm text-ink-soft, ljus botten vid hovring. Fliken man står på har heroens färg och blöder in i <Hero> (monster.hero). Raden bryts hellre än rullar."
      ]
    },
    {
      "id": "yta.arbetsyta",
      "namn": "Arbetsytan",
      "kategori": "yta",
      "syfte": "Rutnätet skalet lägger ut åt modulerna: en helbreddsrad för nyckeltal och två spalter.",
      "narAnvands": "Ägs av skalet. En modul väljer bara slot i sitt manifest.",
      "kod": "/* apps/portal/src/shell/shell.css – skalets, inte modulernas */\n.kpis  { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 2rem; }\n.board { grid-template-columns: minmax(0, 1fr) 36rem; gap: 2rem; }\n.col   { grid-template-columns: minmax(0, 1fr); gap: 2rem; min-width: 0; }\n\n/* I modulens manifest: */\nslots: { kpi: [...], main: [...], side: [...] }",
      "regler": [
        "kpi = fyra spår över hela bredden. main = vänsterspalten. side = högerspalten (36 rem).",
        "Under 1180 px blir båda rutnäten enspaltiga. Bygg kortet så att det tål det.",
        "Spåren har min-width: 0 – bryts det trycker en bred tabell ut hela sidan (R6)."
      ]
    },
    {
      "id": "yta.aifalt",
      "namn": "AI-fältet",
      "kategori": "yta",
      "syfte": "Skalets AI-yta överst på startsidan: hälsning, fråga och rollens förslag.",
      "narAnvands": "Ägs av skalet. En modul bidrar med förslagschips via ai.suggestions(ctx) i sitt manifest.",
      "kod": "// packages/modules/<namn>/src/manifest.ts\nai: {\n  suggestions: (ctx) =>\n    ctx.role === \"chef\"\n      ? [{ id: \"godkann\", label: \"Godkänn veckans ledighetsansökningar\" }]\n      : [{ id: \"felanmal\", label: \"Felanmäl något på kontoret\" }],\n}",
      "regler": [
        "Fältet är runt (border-radius 999px) och AI-kantat, med ✦ – formen och märket betyder AI (R2).",
        "Förslagen ska vara handlingar användaren faktiskt kan utföra just nu."
      ]
    },
    {
      "id": "yta.fokus",
      "namn": "Fokusflöde",
      "kategori": "yta",
      "syfte": "Helskärm utan sidopanel: smal topprad med Avbryt, ett ärende från början till slut.",
      "narAnvands": "För flöden som kräver odelad uppmärksamhet, som felanmälan. Deklareras med routes i manifestet.",
      "narInte": "För något som ryms i ett kort. Fokusläget tar hela skärmen och kostar därefter.",
      "kod": "// packages/modules/<namn>/src/manifest.ts\nroutes: [\n  {\n    path: \"/flode/felanmalan\",\n    title: \"Felanmälan\",\n    mode: \"focus\",\n    Component: FelanmalanFlode,\n  },\n]",
      "regler": [
        "mode: \"focus\" ger FlowBar med Avbryt och inget annat krom (ADR-004).",
        "Samtalet är inmatningen, formuläret är kvittot: visa i realtid vad assistenten uppfattat.",
        "Ärendet skapas aldrig utan uttrycklig bekräftelse – och servern kontrollerar själv att allt obligatoriskt finns."
      ]
    },
    {
      "id": "yta.full",
      "namn": "Modulvy över hela ytan",
      "kategori": "yta",
      "syfte": "En arbetsplats man stannar i: sidopanelen kvar, vyn tar hela arbetsytan i skärmens höjd. Som en chattjänst – lista längs kanten, innehållet i full höjd.",
      "narAnvands": "När vyn själv är verktyget och behöver bredden – Min AI, och nästa steg ett dokument bredvid samtalet. Deklareras med routes och mode: \"full\" (ADR-025).",
      "narInte": "För en vy som läses uppifrån och ned, som en personalakt – den är mode: \"standard\". För ett ärende från början till slut med Avbryt – det är fokusflödet.",
      "kod": "// packages/modules/<namn>/src/manifest.ts\nroutes: [\n  { path: \"/verktyg/min-ai\", title: \"Min AI\", mode: \"full\", Component: MinAiVy },\n],\nslots: {\n  // Ingången: en knapp bredvid hälsningen. ai: true ger AI-tonen och ✦.\n  genvag: [{ label: \"Min AI\", href: \"/verktyg/min-ai\", order: 1, ai: true }],\n}",
      "regler": [
        "Vyn äger höjden och scrollningen: fyll ytan med flex och låt bara listor och innehåll scrolla (min-h-0, overflow-y-auto).",
        "Läsbar text hålls i en spalt (högst cirka 78rem) – inte över hela bredden. Ytan bredvid är för en panel, inte för längre rader.",
        "Kolumnen längs kanten blir en låda på smal skärm, öppnad med en knapp med aria-expanded och stängd med Escape.",
        "Ingen rubrik eller ingress ovanför – vyn är ytan. Det vyn behöver säga hör hemma i dess egna fält."
      ]
    }
  ],
  "vanligaFel": [
    {
      "fel": "className=\"bg-ai-bg\" på ett kort som visar \"Klart\"",
      "ratt": "<Chip tone=\"ok\">Klart</Chip> på ett vanligt kort",
      "varfor": "AI-tonen är för AI, grönt är för status – en AI-tonad yta säger inte klart (R1)."
    },
    {
      "fel": "className=\"text-[#005595]\"",
      "ratt": "className=\"text-blue\"",
      "varfor": "Hårdkodad färg går förbi tokens och syns inte i temat (R4). Fångas av yarn lint."
    },
    {
      "fel": "className=\"text-[14px]\" – eller vilket px-mått som helst i hakparentes",
      "ratt": "className=\"text-sm\", eller className=\"border-l-[0.3rem]\" när skalan inte räcker",
      "varfor": "Rotens font-size är 62,5 %, så px bryter skalningen när användaren ändrar textstorlek. Rem följer med (R4). Fångas av yarn lint."
    },
    {
      "fel": "<Button> importerad från shadcn-mappen",
      "ratt": "<Btn> ur @startsidan/ui",
      "varfor": "Btn bär V3:s mått och varianten ai. shadcns Button är grunden under, inte gränssnittet utåt."
    },
    {
      "fel": "<Card> utan headingId",
      "ratt": "<Card headingId=\"arenden-h\" title=\"Mina ärenden\">",
      "varfor": "Kortet renderas som <section> och behöver aria-labelledby (R8)."
    },
    {
      "fel": "AI-genererad sammanfattning renderad som vanlig text",
      "ratt": "Samma text med <AiChip> intill, eller i ett kort med tone=\"ai\"",
      "varfor": "Omärkt AI-innehåll bryter mot löftet till användaren (R2)."
    },
    {
      "fel": "<Avatar> i grön eller röd variant",
      "ratt": "<Avatar initials=\"AL\" /> – alltid blå, status läggs bredvid",
      "varfor": "Identitet och status delar inte färgskala (R3)."
    },
    {
      "fel": "Ny regel skriven i apps/portal/src/shell/shell.css från en modul",
      "ratt": "Tailwind-utilities i modulens egen komponent",
      "varfor": "shell.css ägs av skalet (R6)."
    },
    {
      "fel": "tone: string i en modulprop",
      "ratt": "tone: ChipTone – unionen ur @startsidan/ui",
      "varfor": "Fri sträng gör varianterna omöjliga att räkna upp och kontrollera (R10)."
    },
    {
      "fel": "Egen div med role=\"progressbar\"",
      "ratt": "<Progress value={60} /> ur @startsidan/ui",
      "varfor": "Progress är Radix under ytan och sätter aria-valuenow/min/max korrekt."
    }
  ],
  "checklista": [
    "Inga hex-värden och inga px i hakparentes – yarn lint fäller båda, men se dem i diffen först.",
    "Grönt bara för status, AI-ytor med --color-ai-*, och all AI-genererad text bär AiChip.",
    "Varje Card har headingId och title.",
    "Varianter är unionstyper, inte strängar. Inga any.",
    "Komponenten är en server component om den inte behöver interaktion.",
    "Inga nya regler i shell.css från en modul.",
    "All UI-text är på svenska.",
    "yarn lint && yarn test är gröna."
  ],
  "innehallsversion": "c1b2ry3mi0me"
}