import type { Project } from '@lib/schema';

/* InboxIntel case study. Source: F:\Documents\InboxIntel\InboxIntel (full codebase + docs audit, 2026-07-12). */
export const inboxintel: Project = {
  id: 'inboxintel',
  name: 'InboxIntel',
  order: 2,
  status: 'in-development',
  template: 'case-study',
  stack: [
    { name: '.NET 10', context: { en: 'ASP.NET Core', no: 'ASP.NET Core' } },
    { name: 'React', context: { en: 'Vite · TypeScript', no: 'Vite · TypeScript' } },
    { name: 'PostgreSQL', context: { en: 'EF Core · pgvector', no: 'EF Core · pgvector' } },
    { name: 'Ollama', context: { en: 'embeddings · summaries', no: 'embeddings · sammendrag' } },
    { name: 'Clean Architecture' },
    { name: 'Docker' },
  ],
  links: [],
  diagram: {
    viewBox: '0 0 1120 300',
    title: { en: 'InboxIntel architecture', no: 'InboxIntel-arkitektur' },
    desc: {
      en: 'A React SPA calls an ASP.NET Core API layered as Clean Architecture. A hosted background worker syncs the Gmail API into PostgreSQL with pgvector, and a local Ollama model produces the embeddings and summaries that power hybrid search.',
      no: 'En React-app kaller et ASP.NET Core-API bygget som Clean Architecture. En bakgrunnstjeneste synkroniserer Gmail-API-et inn i PostgreSQL med pgvector, og en lokal Ollama-modell lager embeddings og sammendrag som driver hybrid søk.',
    },
    nodes: [
      {
        id: 'spa',
        x: 40,
        y: 116,
        w: 170,
        h: 72,
        kind: 'internal',
        label: 'React SPA',
        sub: { en: 'split-view · search', no: 'delt visning · søk' },
      },
      {
        id: 'api',
        x: 290,
        y: 116,
        w: 200,
        h: 72,
        kind: 'primary',
        label: 'ASP.NET Core API',
        sub: { en: 'Clean Architecture', no: 'Clean Architecture' },
      },
      {
        id: 'ollama',
        x: 290,
        y: 232,
        w: 200,
        h: 56,
        kind: 'internal',
        label: 'Ollama',
        sub: { en: 'embeddings · summaries', no: 'embeddings · sammendrag' },
      },
      {
        id: 'pg',
        x: 580,
        y: 48,
        w: 180,
        h: 64,
        kind: 'internal',
        label: 'PostgreSQL',
        sub: { en: 'EF Core · pgvector', no: 'EF Core · pgvector' },
      },
      {
        id: 'worker',
        x: 580,
        y: 160,
        w: 180,
        h: 64,
        kind: 'internal',
        label: 'Sync worker',
        sub: { en: 'hosted · Polly', no: 'hostet · Polly' },
      },
      {
        id: 'gmail',
        x: 900,
        y: 152,
        w: 180,
        h: 72,
        kind: 'external',
        label: 'Gmail API',
        sub: { en: 'external · OAuth2', no: 'eksternt · OAuth2' },
      },
    ],
    edges: [
      { d: 'M210 152 H290', kind: 'flow', label: '/api', labelX: 240, labelY: 144 },
      { d: 'M390 188 V232', kind: 'flow', label: 'AI', labelX: 408, labelY: 216 },
      { d: 'M490 134 L580 92', kind: 'flow' },
      { d: 'M490 170 L580 192', kind: 'flow' },
      { d: 'M670 160 V112', kind: 'flow' },
      { d: 'M760 192 H900', kind: 'external', label: 'sync', labelX: 820, labelY: 184 },
    ],
  },
  media: [
    {
      src: '/assets/projects/inboxintel/inbox.webp',
      width: 1600,
      height: 1000,
      alt: {
        en: 'InboxIntel split view: a persistent message list beside a resizable reading pane with an AI summary',
        no: 'InboxIntel delt visning: en fast meldingsliste ved siden av en justerbar leserute med AI-sammendrag',
      },
      caption: {
        en: 'Split view — read and triage without ever opening a new tab',
        no: 'Delt visning — les og sorter uten å åpne en ny fane',
      },
    },
    {
      src: '/assets/projects/inboxintel/dashboard.webp',
      width: 1600,
      height: 1000,
      alt: {
        en: 'InboxIntel analytics dashboard: inbox-health grade, emails by category, 90-day volume and top senders',
        no: 'InboxIntel dashbord: innboks-helse, e-post per kategori, 90-dagers volum og toppavsendere',
      },
      caption: {
        en: 'Analytics — an inbox-health grade, categories, volume trend and top senders',
        no: 'Analyse — innboks-helse, kategorier, volumtrend og toppavsendere',
      },
    },
    {
      src: '/assets/projects/inboxintel/search.webp',
      width: 1600,
      height: 1000,
      alt: {
        en: 'InboxIntel search: hybrid semantic and full-text results with why-matched highlights and relevance scores',
        no: 'InboxIntel søk: hybrid semantisk og fulltekst med treff-forklaring og relevans-score',
      },
      caption: {
        en: 'Search — hybrid semantic + full-text, with why-matched highlights and relevance',
        no: 'Søk — hybrid semantisk + fulltekst, med treff-forklaring og relevans',
      },
    },
  ],
  content: {
    en: {
      valueProp:
        'A self-hosted inbox intelligence and cleanup tool — hybrid search, AI summaries and inbox-health analytics over your mail.',
      cardTeaser:
        'Self-hosted inbox intelligence — hybrid semantic + full-text search, AI summaries and inbox-health analytics, with safe cleanup by design.',
      tldr: {
        what: 'Inbox intelligence: split-view reading, hybrid semantic search, AI summaries, inbox-health analytics and safe cleanup.',
        why: 'To practise Clean Architecture properly, and make search useful and destructive operations safe by design.',
        stack: '.NET 10 · PostgreSQL + pgvector · React · Ollama · Docker · Clean Architecture.',
        role: 'Sole architect and developer, front to back.',
      },
      sections: [
        {
          kind: 'problem',
          heading: 'Problem & context',
          anchorId: 'problem',
          blocks: [
            {
              type: 'p',
              text: "A busy inbox is hard to search and dangerous to clean up — keyword search misses what you meant, and one wrong bulk filter deletes things you can't get back. I built InboxIntel to make an inbox actually searchable (by meaning, not just words), understandable at a glance, and safe to tidy — self-hosted, with a clean, testable backend built to production discipline rather than side-project shortcuts.",
            },
          ],
        },
        {
          kind: 'solution',
          heading: 'Solution',
          anchorId: 'solution',
          blocks: [
            {
              type: 'p',
              text: 'InboxIntel connects to your mailbox via OAuth, syncs it into a local PostgreSQL database, and layers three things on top: a split-view reading pane for fast triage, hybrid search that finds mail by meaning as well as keywords, and an analytics dashboard that shows inbox health at a glance. Every destructive action — bulk trash, unsubscribe — previews before it executes.',
            },
            {
              type: 'ul',
              items: [
                'Reading: a persistent list beside a resizable pane with an AI summary — triage without opening a new tab.',
                'Search: hybrid semantic (pgvector) + full-text, with a fuzzy fallback for typos and a relevance score.',
                'Analytics: an inbox-health grade, emails by category, a 90-day volume trend and top senders.',
                'Cleanup: preview-then-confirm on every destructive action; the AI is advisory only.',
              ],
            },
            {
              type: 'p',
              text: 'Today it connects to Gmail only. A full multi-provider architecture — a provider abstraction, unified email model, and an Outlook/IMAP rollout plan — is designed in detail (a dozen internal design documents covering auth, data model, admin platform and migration path); building it out is the next major milestone rather than a shipped feature.',
            },
          ],
        },
        {
          kind: 'features',
          heading: 'Feature showcase',
          anchorId: 'features',
          features: [
            {
              title: 'Hybrid search that understands meaning, not just words',
              value:
                'Find "that gym receipt" even if you never typed the word "gym" in the email — search stops being a literal keyword match.',
              technical:
                'Reciprocal Rank Fusion combines Postgres full-text search (tsvector/GIN, websearch_to_tsquery) with pgvector cosine-similarity search over local Ollama embeddings, fusing the top 50 results from each. A pg_trgm fuzzy fallback with a hand-tuned 0.3 similarity threshold (the default 0.6 misses real typos like "recieved") catches what neither ranked list does. Any AI-path failure — Ollama down, embeddings not yet backfilled — degrades silently to lexical-only search.',
            },
            {
              title: 'Cleanup you can trust',
              value:
                'Preview exactly what a bulk-delete or unsubscribe action will do before anything happens — no accidental data loss from an over-broad filter.',
              technical:
                'Every destructive action requires a server-side Preview call before Execute, with an explicit Confirmed flag checked twice — once by FluentValidation, once inside the service itself as defence in depth. Unsubscribe targets are validated by an SSRF egress guard against private/metadata IP ranges before any outbound request is made, and mailto: links are surfaced to the user rather than auto-sent.',
            },
            {
              title: 'Inbox health at a glance',
              value:
                'A draggable dashboard shows volume trends, top senders and category breakdowns without digging through folders manually.',
              technical:
                'A react-grid-layout dashboard with per-user persisted widget positions, backed by dedicated analytics endpoints (health grade, category heatmap, 90-day volume, top senders, attachment stats) computed server-side rather than client-aggregated.',
            },
          ],
        },
        {
          kind: 'architecture',
          heading: 'Engineering highlights',
          anchorId: 'architecture',
          blocks: [
            {
              type: 'p',
              text: 'The backend is Clean Architecture with the dependency rule enforced end to end: Api → Infrastructure → Application → Domain, controllers hold no business logic, and the Domain project has zero external dependencies bar the one type needed for full-text search. AI is fully swappable at the DI boundary — Null, Ollama and OpenAI implementations of the same IAiProvider interface — so the rest of the app never branches on whether AI is enabled.',
            },
            {
              type: 'ul',
              items: [
                "Sync engine: checkpoints its page token and message count after every page, so a killed sync resumes rather than restarting; incremental syncs use Gmail's historyId watermark instead of re-scanning; all calls run through Polly retry/backoff.",
                "Pagination: keyset (cursor) pagination on large result sets — O(page size) instead of OFFSET's O(page × page size) — alongside plain offset pagination for the general browse path.",
                'AI is advisory only by contract: every AiService method carries the explicit guarantee that it never archives, deletes, labels or unsubscribes on its own.',
                'Security is self-audited on a paper trail: a dated, severity-coded internal audit (plaintext-storage risk, missing rate limits, unencrypted keys) with each finding resolved via its own tracked PR and new tests, not just prose claims.',
              ],
            },
          ],
        },
        {
          kind: 'decisions',
          heading: 'Challenges',
          anchorId: 'decisions',
          decisions: [
            {
              n: 1,
              choice:
                'Fuse lexical and semantic search with Reciprocal Rank Fusion, degrading silently on failure.',
              alternative: 'Run semantic search as a separate mode the user has to switch to.',
              rationale:
                "Keyword search alone misses paraphrased or vaguely-remembered mail; semantic search alone drops exact-match precision. RRF blends the top 50 of each into one ranked list automatically, and if Ollama is down or an email hasn't been embedded yet, the query still returns lexical results instead of erroring. Outcome: one search box that works whether or not the AI layer is healthy.",
            },
            {
              n: 2,
              choice: 'Encrypt OAuth refresh tokens at rest and never log them.',
              alternative: 'Store them as plain columns — simpler, faster to ship.',
              rationale:
                "Refresh tokens are long-lived keys to someone's mailbox. They're encrypted with the ASP.NET Core Data Protection API (AES), with keys persisted to a mounted volume — flagged in the internal audit as the single most important security decision in the app, and verified clean on re-review.",
            },
            {
              n: 3,
              choice: 'Require a mandatory preview before any destructive action, enforced twice.',
              alternative: 'Trust client-side confirmation and execute on request.',
              rationale:
                'An audit finding showed rate limiting and validation gaps around bulk actions early on. The fix — preview-then-confirm checked at both the validator and the service layer, plus an SSRF guard on every outbound unsubscribe request — closed the finding and shipped with regression tests proving the 400 on invalid input.',
            },
          ],
        },
        {
          kind: 'security',
          heading: 'Security & production notes',
          anchorId: 'security',
          blocks: [
            {
              type: 'ul',
              items: [
                'Google-OAuth gated; Gmail scopes are read/modify only — no send scope is ever requested.',
                "Embeddings and summaries are generated locally (Ollama) by default; an optional cloud fallback exists but mail never leaves the box unless that's explicitly enabled.",
                'Polly retry/backoff against Gmail rate limits; Serilog structured logging that never records tokens; OpenTelemetry instrumentation wired for production observability.',
                'Clean Architecture keeps the layers testable; 39+ unit and integration tests, including live-database tests for the full-text/fuzzy/pgvector paths.',
              ],
            },
          ],
        },
        {
          kind: 'screenshots',
          heading: 'Screenshots',
          anchorId: 'screenshots',
        },
        {
          kind: 'next',
          heading: 'Status & roadmap',
          anchorId: 'next',
          blocks: [
            {
              type: 'p',
              text: 'Implemented today:',
            },
            {
              type: 'ul',
              items: [
                'Gmail OAuth sync into PostgreSQL, with resumable, checkpointed background sync',
                'Hybrid semantic + full-text + fuzzy search with keyset pagination',
                'Draggable analytics dashboard: inbox health, categories, volume, top senders',
                'Safe bulk cleanup and unsubscribe (preview-then-confirm, SSRF-guarded)',
                'Optional local AI (Ollama) or cloud fallback for classification, summaries and natural-language search',
                'A self-audited, remediated security posture with tracked findings and fixes',
              ],
            },
            {
              type: 'p',
              text: 'Designed but not yet built — the next major milestones:',
            },
            {
              type: 'ul',
              items: [
                'Multi-provider support (Outlook, generic IMAP) — architecture and migration plan already written',
                'Rules engine for automated cleanup, sender policy (block/whitelist/screener) and an activity log with undo',
                'A privacy monitor (breach-check against known leak databases)',
                'Tuned AI prompts and richer HTML-body/attachment parsing beyond the common cases',
              ],
            },
          ],
        },
      ],
    },
    no: {
      valueProp:
        'Et egendriftet verktøy for innboks-intelligens og opprydding — hybrid søk, AI-sammendrag og innboks-helse over e-posten din.',
      cardTeaser:
        'Egendriftet innboks-intelligens — hybrid semantisk + fulltekst-søk, AI-sammendrag og innboks-helse, med trygg opprydding fra bunnen.',
      tldr: {
        what: 'Innboks-intelligens: delt visning, hybrid semantisk søk, AI-sammendrag, innboks-helse og trygg opprydding.',
        why: 'For å øve på Clean Architecture skikkelig, gjøre søk nyttig og destruktive operasjoner trygge fra bunnen.',
        stack: '.NET 10 · PostgreSQL + pgvector · React · Ollama · Docker · Clean Architecture.',
        role: 'Eneste arkitekt og utvikler, fra ende til ende.',
      },
      sections: [
        {
          kind: 'problem',
          heading: 'Problem og kontekst',
          anchorId: 'problem',
          blocks: [
            {
              type: 'p',
              text: 'En travel innboks er vanskelig å søke i og farlig å rydde i — nøkkelordsøk bommer på det du mente, og ett feil massefilter sletter ting du ikke får tilbake. Jeg bygde InboxIntel for å gjøre en innboks faktisk søkbar (på mening, ikke bare ord), forståelig med ett blikk, og trygg å rydde i — egendriftet, med en ryddig og testbar backend bygget til produksjonsstandard, ikke fritidsprosjekt-snarveier.',
            },
          ],
        },
        {
          kind: 'solution',
          heading: 'Løsning',
          anchorId: 'losning',
          blocks: [
            {
              type: 'p',
              text: 'InboxIntel kobler seg til postkassen din via OAuth, synkroniserer den inn i en lokal PostgreSQL-database, og legger tre ting på toppen: en delt lesevisning for rask sortering, hybrid søk som finner e-post på mening i tillegg til nøkkelord, og et analysedashbord som viser innboks-helse med ett blikk. Hver destruktive handling — massesletting, avmelding — forhåndsvises før den utføres.',
            },
            {
              type: 'ul',
              items: [
                'Lesing: en fast liste ved siden av en justerbar rute med AI-sammendrag — sorter uten å åpne en ny fane.',
                'Søk: hybrid semantisk (pgvector) + fulltekst, med fuzzy-fallback for skrivefeil og relevans-score.',
                'Analyse: en innboks-helsekarakter, e-post per kategori, 90-dagers volumtrend og toppavsendere.',
                'Opprydding: forhåndsvis-så-bekreft på hver destruktive handling; AI-en er kun rådgivende.',
              ],
            },
            {
              type: 'p',
              text: 'I dag kobles den kun til Gmail. En full arkitektur for flere leverandører — en leverandørabstraksjon, en samlet e-postmodell og en utrullingsplan for Outlook/IMAP — er designet i detalj (et dusin interne designdokumenter om autentisering, datamodell, admin-plattform og migreringsvei); å bygge det ut er neste store milepæl, ikke en levert funksjon.',
            },
          ],
        },
        {
          kind: 'features',
          heading: 'Utvalgte funksjoner',
          anchorId: 'funksjoner',
          features: [
            {
              title: 'Hybrid søk som forstår mening, ikke bare ord',
              value:
                'Finn «den kvittering fra treningssenteret» selv om du aldri skrev ordet «trening» i e-posten — søk slutter å være et bokstavelig nøkkelordtreff.',
              technical:
                'Reciprocal Rank Fusion kombinerer Postgres fulltekstsøk (tsvector/GIN, websearch_to_tsquery) med pgvector kosinuslikhet over lokale Ollama-embeddings, og fusjonerer de 50 beste resultatene fra hver. En pg_trgm fuzzy-fallback med en håndjustert similaritetsterskel på 0,3 (standarden 0,6 bommer på ekte skrivefeil som «mottat»→«mottatt») fanger opp det ingen av de rangerte listene gjør. Enhver feil i AI-stien — Ollama nede, embeddings ikke bakfylt ennå — degraderer stille til kun leksikalsk søk.',
            },
            {
              title: 'Opprydding du kan stole på',
              value:
                'Forhåndsvis nøyaktig hva en massesletting eller avmelding vil gjøre før noe skjer — ingen utilsiktet datatap fra et for bredt filter.',
              technical:
                'Hver destruktive handling krever et server-side Preview-kall før Execute, med et eksplisitt Confirmed-flagg sjekket to ganger — én gang av FluentValidation, én gang inne i selve tjenesten som ekstra sikkerhetslag. Avmeldingsmål valideres av en SSRF-utgangsvakt mot private/metadata-IP-områder før noen utgående forespørsel gjøres, og mailto-lenker vises til brukeren i stedet for å sendes automatisk.',
            },
            {
              title: 'Innboks-helse med ett blikk',
              value:
                'Et flyttbart dashbord viser volumtrender, toppavsendere og kategorifordeling uten å måtte grave gjennom mapper manuelt.',
              technical:
                'Et react-grid-layout-dashbord med per-bruker lagret widget-plassering, støttet av dedikerte analyse-endepunkter (helsekarakter, kategori-varmekart, 90-dagers volum, toppavsendere, vedleggsstatistikk) beregnet server-side i stedet for aggregert på klienten.',
            },
          ],
        },
        {
          kind: 'architecture',
          heading: 'Tekniske høydepunkter',
          anchorId: 'arkitektur',
          blocks: [
            {
              type: 'p',
              text: 'Backend er Clean Architecture med avhengighetsregelen håndhevet fra ende til ende: Api → Infrastructure → Application → Domain, kontrollerne har ingen forretningslogikk, og Domain-prosjektet har ingen eksterne avhengigheter bortsett fra én type som trengs for fulltekstsøk. AI er fullt utskiftbart ved DI-grensen — Null-, Ollama- og OpenAI-implementasjoner av samme IAiProvider-grensesnitt — så resten av appen forgrener seg aldri på om AI er skrudd på.',
            },
            {
              type: 'ul',
              items: [
                'Synk-motor: sjekkpunkter side-token og meldingstall etter hver side, så en avbrutt synk fortsetter i stedet for å starte på nytt; inkrementelle synker bruker Gmails historyId-vannmerke i stedet for å skanne på nytt; alle kall går gjennom Polly retry/backoff.',
                'Paginering: keyset (markør)-paginering på store resultatsett — O(sidestørrelse) i stedet for OFFSETs O(side × sidestørrelse) — ved siden av vanlig offset-paginering for generell nettlesing.',
                'AI er kun rådgivende ved kontrakt: hver AiService-metode bærer den eksplisitte garantien at den aldri arkiverer, sletter, merker eller melder av på egen hånd.',
                'Sikkerhet er egen-revidert med papirspor: en datert, alvorlighetskodet intern revisjon (risiko for klartekst-lagring, manglende ratebegrensning, ukrypterte nøkler) hvor hvert funn er løst via egen sporet PR og nye tester, ikke bare prosapåstander.',
              ],
            },
          ],
        },
        {
          kind: 'decisions',
          heading: 'Utfordringer',
          anchorId: 'utfordringer',
          decisions: [
            {
              n: 1,
              choice:
                'Fusjonere leksikalsk og semantisk søk med Reciprocal Rank Fusion, med stille degradering ved feil.',
              alternative: 'Kjøre semantisk søk som en egen modus brukeren må bytte til.',
              rationale:
                'Nøkkelordsøk alene bommer på omskrevet eller vagt husket e-post; semantisk søk alene mister presisjon på eksakte treff. RRF blander de 50 beste fra hver til én rangert liste automatisk, og hvis Ollama er nede eller en e-post ikke er bakfylt ennå, returnerer spørringen fortsatt leksikalske resultater i stedet for å feile. Resultat: én søkeboks som fungerer uansett om AI-laget er friskt.',
            },
            {
              n: 2,
              choice: 'Kryptere OAuth-refresh-tokens i ro og aldri logge dem.',
              alternative: 'Lagre dem som vanlige kolonner — enklere, raskere å levere.',
              rationale:
                'Refresh-tokens er langlevde nøkler til noens innboks. De krypteres med ASP.NET Core Data Protection API (AES), med nøkler lagret på et montert volum — flagget i den interne revisjonen som det viktigste sikkerhetsvalget i appen, og verifisert rent ved ny gjennomgang.',
            },
            {
              n: 3,
              choice:
                'Kreve obligatorisk forhåndsvisning før enhver destruktiv handling, håndhevet to ganger.',
              alternative: 'Stole på klientsidebekreftelse og utføre ved forespørsel.',
              rationale:
                'Et revisjonsfunn viste ratebegrensnings- og valideringshull rundt massehandlinger tidlig. Løsningen — forhåndsvis-så-bekreft sjekket både på validator- og tjenestenivå, pluss en SSRF-vakt på hver utgående avmeldingsforespørsel — lukket funnet og ble levert med regresjonstester som beviser 400 ved ugyldig input.',
            },
          ],
        },
        {
          kind: 'security',
          heading: 'Sikkerhet og drift',
          anchorId: 'sikkerhet',
          blocks: [
            {
              type: 'ul',
              items: [
                'Google-OAuth-beskyttet; Gmail-scopes er kun lese/endre — send-scope blir aldri etterspurt.',
                'Embeddings og sammendrag lages lokalt (Ollama) som standard; en valgfri sky-fallback finnes, men e-post forlater aldri maskinen med mindre det er eksplisitt skrudd på.',
                'Polly retry/backoff mot Gmails rategrenser; strukturert Serilog-logging som aldri lagrer tokens; OpenTelemetry-instrumentering klar for produksjonsobservabilitet.',
                'Clean Architecture holder lagene testbare; 39+ enhets- og integrasjonstester, inkludert live-database-tester for fulltekst-/fuzzy-/pgvector-stiene.',
              ],
            },
          ],
        },
        {
          kind: 'screenshots',
          heading: 'Skjermbilder',
          anchorId: 'skjermbilder',
        },
        {
          kind: 'next',
          heading: 'Status og veien videre',
          anchorId: 'videre',
          blocks: [
            {
              type: 'p',
              text: 'Implementert i dag:',
            },
            {
              type: 'ul',
              items: [
                'Gmail OAuth-synk inn i PostgreSQL, med gjenopptakbar, sjekkpunktet bakgrunnssynk',
                'Hybrid semantisk + fulltekst + fuzzy søk med keyset-paginering',
                'Flyttbart analysedashbord: innboks-helse, kategorier, volum, toppavsendere',
                'Trygg massesletting og avmelding (forhåndsvis-så-bekreft, SSRF-sikret)',
                'Valgfri lokal AI (Ollama) eller sky-fallback for klassifisering, sammendrag og naturlig-språk-søk',
                'En egen-revidert, utbedret sikkerhetsstatus med sporede funn og fikser',
              ],
            },
            {
              type: 'p',
              text: 'Designet, men ikke bygget ennå — de neste store milepælene:',
            },
            {
              type: 'ul',
              items: [
                'Støtte for flere leverandører (Outlook, generisk IMAP) — arkitektur og migreringsplan allerede skrevet',
                'Regelmotor for automatisert opprydding, avsenderpolicy (blokker/hvitliste/screener) og en aktivitetslogg med angre',
                'En personvernvakt (lekkasjesjekk mot kjente bruddatabaser)',
                'Justerte AI-prompter og rikere HTML-body-/vedleggsparsing utover de vanlige tilfellene',
              ],
            },
          ],
        },
      ],
    },
  },
};
