CMS-krønike·marts 2026·

CMS Kronik #12: Den AI-native redaktør

748 commits på 11 dage. Vi udviklede et samtalebaseret CMS-grænseflade, opgraderede den rige teksteditor til TipTap v3, byggede en mediabehandlingspipeline med Sharp og EXIF, tilføjede AI-billedanalyse og opdagede en gem-fejl, der havde gemt sig siden dag ét.

748 Commits på 11 Dage

Denne kronik dækker perioden 16.–27. marts 2026 — formentlig det mest produktive forløb i projektets historie. Vi leverede 8 funktioner til 'Done', skabte 4 nye funktionsplaner, fik bugget en kritisk gem-fejl og byggede noget, intet andet CMS har: en fuldskærms samtalegrænseflade, der kan læse, søge, oprette og redigere hele dit site via naturligt sprog.

Her er alt sammen.

F107: Chat med dit Site

Hovedfunktionen. En hovedkontakt i admin-hovedmenuen skifter mellem "Traditionel" (sidebjælken/fanerne/editor-grænsefladen, du kender) og "Chat" — en fuldskærms samtalegrænseflade.

Fase 1 blev leveret med skrivebeskyttede værktøjer: sitesammendrag, liste dokumenter, søg indhold, hent skema, liste udkast, hent site-konfiguration, liste medier. Du kan spørge: "Vis mig alle udkast-indlæg" eller "Hvilke felter har pages-samlingen?" og AI’en svarer ved hjælp af dine faktiske site-data.

Fase 2 fulgte umiddelbart efter: opret, opdater, publicer, afpublicer, slet dokumenter. Bekræftelsesflow for destruktive handlinger. Forhåndsvisningskort, der viser, hvad der er ændret. AI’en kalder direkte på interne CMS API-ruter — ikke MCP — for lavere latenstid og automatisk funktionsparitet, når API’et udvikler sig.

Chat’en gengiver rig markdown, forhåndsvisningskort med iframe-snapshots og værktøjsudførelsessummeringer. Samtaler gemmes pr. bruger. Smart velkomstskærm med kontekstbevidste forslag baseret på site-tilstand (antal udkast, planlagte indlæg, deploy-status).

Intet headless CMS tilbyder dette. Contentful, Sanity, Strapi — de har alle traditionelle admin-paneler. Et CMS, du kan tale med, er virkelig nyt.

F106: TipTap v3-opgradering

Vi opgraderede den rige teksteditor fra TipTap v2.27 til v3.21. Det skulle være ligetil. Det var det ikke.

Pakkemigreringen var i sig selv ren: konsoliderede tabel-imports (4 pakker → 1), flyttede BubbleMenu til Floating UI, opdaterede StarterKit-konfigurationen. TypeScript kompilerede første forsøg.

Så opdagede vi, at værktøjslinjebuttoner ikke længere afspejlede den aktuelle formateringstilstand. I v3 er shouldRerenderOnTransaction deaktiveret som standard — komponenten genindrender ikke ved hver markørbevægelse. Vi flyttede alle markeringstilstande (fed, kursiv, gennemstreget, understreget, lister, overskrifter) til useEditorState-vælgeren, som abonnerer på transaktioner uafhængigt.

Så opdagede vi, at gemning af indhold nogle gange mistede de seneste redigeringer. Playwright E2E-tests bekræftede: indholdet overlevede gem, men forsvandt efter faneskift.

Den egentlige årsag var en stale closure-fejl, der havde eksisteret siden før v3-opgraderingen — v2’s aggressive genindrendering maskerede blot problemet. Cmd+S-tastaturhåndtereren var placeret i en useEffect([saving]), der fangede en forældet save()-funktion. Når brugeren skrev tekst og trykkede Cmd+S, kaldte håndtereren save() med en gammel doc.data, der ikke inkluderede de seneste redigeringer. Filen blev "gemt" med forældet indhold.

Fix: en ref (saveRef.current = save), der altid peger på den seneste funktion. Tre Playwright-tests vogter nu dette: indhold overlever gem, indhold overlever faneskift, indhold overlever fuld sides genindlæsning.

Advarslen flushSync fra React 19 blev løst ved at fjerne immediatelyRender: false (unødvendigt for klientkomponenter) og holde shouldRerenderOnTransaction: false.

F108: Forbedringer til den rige teksteditor

Med TipTap v3 stabil, tilføjede vi de formateringsfunktioner, editoren manglede:

  • Understreget (Cmd+U) — var i StarterKit, men deaktiveret
  • Hævet skrift (Cmd+.) og sænket skrift (Cmd+,)
  • Tekstjustering — venstre, center, højre for afsnit og overskrifter
  • Fremhævning — 6 farvevælger (grøn, blå, gul, rød, lilla, orange) med ryd-knap
  • Editor zoom — [−] 100% [+] interval 50–200%, CSS font-size-skaleret
  • AI-proofreading — registrerer automatisk sprog, kontrollerer stave-/grammatik-/stilfejl
  • Tabelkontroller — tilføj/slet rækker og kolonner, kontekstknapper, der vises, når markøren er i en tabel

Alle formateringer bruger HTML-i-markdown (<u>, <sup>, <sub>, <mark>), da standardmarkdown ikke understøtter disse. tiptap-markdown-konfigurationen blev ændret til html: true for at bevare disse tags gennem roundtrippet.

F44: Mediabehandlingspipeline

Mediesystemet fik en fuldstændig opgradering:

  • Sharp WebP-generering — ved upload oprettes der automatisk 400px, 800px, 1200px og 1600px varianter
  • EXIF-ekstraktion — kameramodel, GPS-koordinater, brændvidde, ISO, alt gemt
  • Build-tid <picture>-opgradering<img>-tags i statisk HTML får automatisk WebP srcset
  • Batchoptimering — "Optimer alle"-knap til eksisterende mediebiblioteker
  • Konfigurerbar — variantbredder og WebP-kvalitet justerbar i site-indstillinger

Originaler bliver altid bevaret. Varianter gemmes side om side (hero.jpghero-400w.webp, hero-800w.webp). Build-pipelinen opdager automatisk varianter på disken.

F103: AI-billedanalyse

Hvert uploadet billede analyseres nu automatisk af Claude:

  • Billedtekst — beskrivende sætning om billedets indhold
  • Alt-tekst — tilgængelig beskrivelse (max 125 tegn)
  • Tags — 3–8 relevante nøgleord
  • Sprogbevidst — prompts tilpasses siteets sprog

Batchanalyse af eksisterende biblioteker. Resultater synlige i AI-panelet i lysbilledvisningen. Alt-tekst udfyldes automatisk ved indsættelse af billeder i editoren. Analysen kører ikke-blokerende efter upload — filen er straks tilgængelig, mens AI’en arbejder i baggrunden.

F102: Skemadrift-detektion

En dataintegritetsbeskyttelse født af en reel hændelse. Den 19. marts fjernede en Claude-session utilsigtet alle Posts-felter fra cms.config.ts ned til blot title. Skaden gik ubemærket hen i 6 dage.

Nu, når du åbner en samling i admin’en, tager systemet prøver af dokumenter og sammenligner deres datanøgler med skemafelterne. Hvis indholdet har felter, der ikke findes i skemaet — vises der en gul advarselsbjælke:

⚠ Skemadrift detekteret i "Posts": 8 felter fundet i indhold, men mangler i skemaet (excerpt, content, date, author, category, readTime, tags, attribution)

Udelukkende informativ, kan afvises pr. session. 8 enhedstests vogter detektionslogikken.

Mediebiblioteksforbedringer

  • Sorter efter nyeste, ældste, navn, størrelse (standard: nyeste først)
  • Søg matcher AI-metadata — billedtekst, alt-tekst, tags, ikke kun filnavn
  • Brugerdefinerede tags — tilføj tilpassede tags til enhver fil, filtrer efter tag i sidebjælken
  • WebP-varianter skjult — medielisten viser kun originaler
  • Cmd+K finder medier via EXIF-data og AI-beskrivelser
  • Billedvælger i editoren søger også i AI-metadata

Gem-fejlen

Dette fortjener sin egen sektion, fordi det er den slags fejl, der underminerer tilliden.

Symptomet: du skriver tekst, gemmer, skifter til en anden fane, kommer tilbage — din tekst er væk. Filen på disken indeholder det gamle indhold. Editoren viser det gamle indhold. Dit arbejde er tabt.

Vi brugte timer på at jage dette gennem lag af React-statehåndtering, useEffect-beskyttelser, modulniveau-caches, globalThis-persistens og webpack-filovervågningskonfiguration. Intet af det var det egentlige problem.

Det egentlige problem var en JavaScript-closure. Cmd+S-håndtereren fangede en save()-funktion fra en gammel render. Den funktion læste doc.data fra sin closure-scope — som ikke inkluderede de seneste tastetryk. Fixet var én linje: saveRef.current = save.

Denne fejl eksisterede før TipTap v3-opgraderingen. TipTap v2’s shouldRerenderOnTransaction: true (standard) forårsagede så mange genindrenderinger, at effekten genkoblede med en frisk closure ved næsten hvert tastetryk, hvilket maskerede problemet. v3’s ydeevneoptimering (deaktiveret som standard) afslørede det.

Læringen: ydeevneoptimeringer, der reducerer genindrenderinger, kan afsløre stale closure-fejl, der altid har været der, men aldrig blev udløst. Skriv E2E-tests for kritiske stier.

Hvad kommer hernæst

  • F109: Inline proofreading — ProseMirror-dekorationer til inline-korrektioner med accepter/afvis
  • F97: SEO-modul — per-dokument SEO-panel, SERP-forhåndsvisning, scorer
  • F96: Indlejrede kort — ethvert forretningssite har brug for et kort
  • F107 Fase 3 — inline-formularer i chat (hybridtilstand)

Vi bygger det CMS, vi havde brug for for tredive år siden. Hver uge kommer det tættere på.