Paperphyte/Changes

Changes.

This site is our own weekend project, and this is its logbook: everything that changed, day by day, baked in when the site was built. The link in the footer says when that was.

  • Fallstudierna visar bara siffror som finns. Under ”Utfall” på /uppdrag/qrgeno, /uppdrag/urb-it och /uppdrag/atg låg elva kort med en streckad gul ruta i stället för ett värde (”— antal —”, ”— veckor —”, ”— ms p95 —”, ”— per vecka —”), och under dem fotnoten ”Streckade rutor saknar verifierad siffra — inga uppskattningar publiceras.” Korten och fotnoten är borta, sv + en. QRGeno och Urb-it går nu från rubriken direkt till texten om vad som levererades, ATG behåller sitt enda kort med siffra (”~20” team på plattformen) och DeLaval sina fyra, som alla hade värden. Ett kort som i Studio är märkt ”Number missing” följer inte längre med i svaret från Sanity (ASSIGNMENT_BY_SLUG_QUERY filtrerar caseSections), så platshållarna finns inte heller i sidornas källkod; fyller man i värdet och slår av märkningen visas kortet igen. Innehållet i Sanity är orört. CaseStudyArticle.jsx ritar inte längre ett block som blivit tomt, och .cs-metric__missing, .cs-footnote och caseStudy.metricsFootnote är borttagna. Fältbeskrivningarna i caseStudy.ts och assignment.ts är rättade efter det.
  • /uppdrag skickar inte längre med ”fyll i faktiska siffror”. Fältet caseOutcome har inte visats på fallstudiekorten sedan den streckade utfallsrutan togs bort i juli, men hämtades fortfarande och följde med i sidans källkod, med texter som ”Deploys per dag och lead time före/efter — fyll i faktiska siffror.” Nu hämtas det inte (ASSIGNMENTS_INDEX_QUERY).
  • Resultat direkt under startsidans inledning. Mellan inledningen och ”Just nu” ligger en ny remsa, ”Resultat hos DeLaval”, med fallstudiens fyra verifierade siffror: 1 000 isolerade tester parallellt, feedback till utvecklare på minuter (ner från timmar), 30 000 vCPU-timmar och 80 000 byggen per månad, och länken ”Läs fallstudien →” till /uppdrag/delaval (sv + en). Siffrorna är inte inskrivna på startsidan utan hämtas från fallstudien i Sanity (HOME_RESULTS_QUERY, Results.jsx): den första utvalda fallstudien som har verifierade siffror, högst fyra, och ett kort märkt ”Number missing” räknas aldrig. Ändras en siffra i fallstudien ändras den här. Remsan följer med i den förrenderade sidan, håller sin plats med en platshållare av samma höjd medan den laddar vid navigering inom sajten, och visas inte alls om det inte finns något att visa.
  • Plats för ett kundcitat med namn och roll, tom tills ett godkänt citat finns. Nytt fält på uppdrag i Studio, ”Customer quote” (customerQuote: citat sv/en, namn, roll sv/en och kryssrutan ”Approved by the customer”). Startsidan visar citatet under resultatremsan, med namn, roll och kund (länkad till uppdraget), men bara när rutan är ikryssad och namn och roll är ifyllda; frågan till Sanity släpper inte igenom något annat. I dag finns inget godkänt citat, så inget visas, och ingen platshållare heller.
  • ”Vad behöver ni hjälp med?” — en väg in via problemet, ovanför åtagandena. Plattformsteknik, DevOps, Delivery Engineering och Developer Experience går in i varandra, och den som har ett problem vet inte alltid vilket av namnen det hör till. Ny sektion på startsidan och på /konsulttjanster, direkt före korten med åtagandena (sv + en, ServiceFinder.jsx, serviceFinder i i18n): fem problem med en mening om vad vi gör åt det och en länk till åtagandet där det brukar lösas, ett per konsultåtagande. ”Byggena tar för lång tid” leder till Plattformsteknik, ”Releaserna går inte att lita på” till Delivery Engineering, ”Incidenterna i produktion tar över” till DevOps, ”Nya utvecklare kommer inte igång” till Developer Experience och ”Vi ska lansera en produkt” till Produktutveckling. Vilket problem som pekar vart står i PROBLEMS i komponenten. Drönarfoto är inte ett av problemen utan har en egen ruta under dem, ”Letar du efter drönarfoto?”, med länk till /dronarfoto och till de tre undersidorna för mäklare, takinspektion och byggprojekt.
  • DeLaval: 84 797 vCPU-timmar på Fargate för testfall per månad. Fallstudiens tredje siffra var ”30 000 vCPU-timmar per månad”, med jämförelsen ”nästan 3,5 år i realtid”. Nu står 84 797, etiketten säger var timmarna körs och vad de går till, ”vCPU-timmar på Fargate för testfall per månad”, och jämförelsen är 9,7 år i realtid (en: 84,797, ”vCPU hours on Fargate for test cases per month”, 9.7 years). Ändrat i Sanity, och syns både under ”Utfall” på /uppdrag/delaval och i resultatremsan på startsidan, som läser samma fält. Meningen med samma siffra i uppdragets beskrivning är ändrad på samma sätt.
  • Tre avgränsade sätt att börja, med vad som ingår och hur det prissätts. Åtagandena beskriver vad vi kan, men lämnade omfattning, leverans och pris öppna. Ny sektion, ”Tre avgränsade sätt att börja.”, på /konsulttjanster (efter korten med åtagandena) och på de fyra tekniska åtagandesidorna: Plattformsteknik, DevOps, Delivery Engineering och Developer Experience (sv + en, EntryOffers.jsx, entryOffers i i18n). Tre uppdrag: Pipelinegenomgång och Plattformsbedömning, som är korta, och Förstärkning i ert Delivery Engineering-team, när ordinarie personal går på ledighet eller rekryteringen drar ut på tiden. Varje kort säger när uppdraget passar, vad kunden får, vad vi behöver av kunden och hur det prissätts. Under korten står prismodellen: vi tar betalt per timme, timpriset är högre för de korta uppdragen och lägre när vi tar en mer varaktig plats i kundens team. Inga belopp anges; timpriset lämnas på förfrågan. Produktutveckling och drönarsidorna visar inte sektionen. llms.txt har fått samma tre uppdrag under ”Ways to start (entry offers)” (scripts/llms.template.txt).

  • Dagens term i stället för podcast-lappen på /anteckningsbok. Den fastnålade gula lappen överst i sidospalten sa ”Vi planerar en podcast om plattformsteknik. Vill du vara med?” med en länk till kontaktsidan. Nu visar den dagens term ur ordlistan (TermOfDayNote): ”Dagens term” och datumet, termen som länk till /ordlista/<slug>, den korta definitionens första mening och ”Hela ordlistan →”, sv + en. Det är samma term som på startsidan: valet ur datumet, hämtningen och prerender-seedningen ligger nu i en gemensam hook i TermOfDay.jsx som båda korten använder. Lappen reserverar 304 px, höjden för en typisk definition av de 27 (uppmätt 214–352 px i spaltens bredd), och håller ordlistelänken på sista raden, så korten under flyttar sig lite eller inte alls när ögonblicksbildens term byts mot dagens. Går hämtningen inte fram visas ingen lapp alls. Texterna pinned–pinnedCtaLink i blogPage är borttagna.
  • ”Populärast” på /anteckningsbok sorterar på reaktioner. Knappen sorterade på lästid (längst inlägg först), som inte säger något om vad som är populärt. Nu är det antalet reaktioner som avgör: flest först, alla fyra slagen räknade, och inlägg med lika många (eller inga) ligger kvar i datumordning. Summan per inlägg har funnits i Redis sedan reaktionerna kom (reactions:top, som /api/react räknar upp vid varje reaktion) men gick inte att läsa; nu svarar GET /api/react?top med { "<slug>": summa } för allt som har reaktioner, så listan hämtar ett svar i stället för ett per inlägg (fetchTotals i lib/reactions.js). Knappen är avstängd tills summorna har kommit, och förblir det om API:et inte svarar, hellre än att sortera på något annat.
  • RSS-länk på /anteckningsbok och i sidfoten. Flödet på /anteckningsbok/feed annonserades bara i sidhuvudet (<link rel="alternate">), som flödesläsare hittar men ingen besökare ser. Nu finns en synlig länk: RSS-ikonen och ordet ”RSS” till höger om ”Sortera” i listans rubrikrad (sv + en, title ”Prenumerera via RSS” / ”Subscribe via RSS”), och ”RSS” sist i sidfotens kolumn Social, så att flödet går att nå från varje sida. Adressen ligger på ett ställe, FEED_PATH i lib/seo.js. Flödet är en fil och ingen route, så båda är vanliga länkar (<a>) som går till samma adress även från /en; sidfoten gör annars en router-länk av allt som börjar med /.
  • Kopiera-knapp på kodblocken i inläggen. Under varje kodblock finns nu ”Copy” bredvid ”Download” (CodeBlock.jsx). Den lägger hela blocket på urklipp, även när blocket är avkortat till femton rader och resten ligger bakom ”Show all … lines”, och knappen säger ”Copied” i två och en halv sekund. Nekar webbläsaren urklippet står knappen kvar som den är; koden går fortfarande att markera.
  • Länkbara rubriker i inläggen. Varje h2 och h3 i ett inlägg har fått ett id av sin egen text (”Så fungerar den” blir #sa-fungerar-den; samma rubrik två gånger i ett inlägg ger -2 på den andra) och ett ”#” efter rubriken som länkar dit, synligt när man pekar på rubriken eller tabbar till länken och alltid på pekskärm. En länk med ankare öppnar inlägget vid det avsnittet, med rubriken under den fasta menyn (scroll-margin-top), även när inlägget hämtas efter sidladdningen. Ankarna sätts av transformBody(…, { anchors: true }) och ritas av Heading.jsx; bara inläggen ber om dem, så tjänstesidor, uppdrag och ordlistan har sina rubriker som förut.
  • Fyra äldre inlägg har fått mellanrubriker. ”Video på webbplatsen med Mux, Sanity och Vercel” (två rubriker), ”Ett besök på Tokyo Game Show 2026” (fem), ”AWS Batch vs Fargate – lärdomar från en migration” (fem) och ”Ersätta AWS Access Keys med IAM Roles Anywhere” (sex) var skrivna som löpande text utan h2, till skillnad från de sexton andra inläggen. Rubrikerna är inlagda i Sanity framför befintliga stycken; texten, bilderna och kodblocken är orörda. Därmed har alla inlägg avsnitt att länka till.
  • Syntaxfärgning för JavaScript i kodblocken. Kodblock märkta js, javascript, jsx, mjs, ts, typescript eller tsx visades i en enda färg, till skillnad från bash, Terraform, JSON, YAML och Python som redan färgades. Nu färgas nyckelord, strängar (även mallsträngar över flera rader), kommentarer, tal och vanliga inbyggda namn som process och console med samma färger som de andra språken (tokenizeJavascript i lib/portableText.jsx). I dag gäller det ett block, i inlägget ”Reaktioner på bloggen”. Block utan angivet språk, som loggrader, är fortsatt ofärgade.
  • Varje inlägg finns också som Markdown, på inläggets adress plus .md. /anteckningsbok/<slug>.md är till för AI-agenter och crawlers, som läser ren text bättre än en renderad sida; det finns ingen knapp för det på sidan. Filen börjar med rubriken och en lista med författare (länkad till profilen), publiceringsdatum, kategori, etiketter och källa (inläggets adress), sedan ingressen och texten: mellanrubriker, listor, citat, hela kodblocken (sidan visar bara femton rader innan man fäller ut), bilder med alt-text och bildtext, och alla länkar som fullständiga adresser. Den slutar med källan en gång till, ”© Paperphyte AB”, en uppmaning att ange källan med länk vid citat eller sammanfattning, och en länk till kontaktsidan. Ordlistetermer länkas till /ordlista på samma ställen som på sidan. Filerna skrivs vid bygget av scripts/generate-markdown.js (sist i prebuild, egen körning med pnpm markdown), för inlägg som är publicerade, samma urval som RSS-flödet.
  • Markdown-kopian kan inte glida ifrån sidan obemärkt. Texten går genom samma omvandling som sidan använder (kodstaket, bildreferenser, inline-markdown; utbruten ur portableText.jsx till lib/bodyTransform.js så att byggskript kan läsa den), och lib/postMarkdown.js kontrollerar sitt eget resultat: lika många rubriker, kodblock och bilder som i inlägget, koden tecken för tecken, varje länk på plats och varje styckes text i rätt ordning. Ett inlägg som inte klarar kontrollen, eller innehåller något som inte går att skriva som Markdown, får ingen fil och ingen rad i llms.txt, och bygget skriver varför; hellre ingen kopia än en felaktig. Vid införandet stämde alla 20 filer ord för ord mot texten på de publicerade sidorna.
  • Kopiorna konkurrerar inte med sidorna i sökresultat. .md-filerna skickas som text/markdown, och vercel.json lägger på X-Robots-Tag: noindex, så sökmotorer fortsätter peka på sidan, som varje fil anger som källa. Inläggssidan talar om var kopian finns med <link rel="alternate" type="text/markdown"> i sidhuvudet (markdownPath i useSeo). RSS-flödet är oförändrat och innehåller fortfarande bara ingresser.
  • llms.txt listar inläggen och byggs nu från en mall. Ny sektion, ”Notebook posts as Markdown”, med en rad per inlägg: titel, länk till .md-filen, ingress och datum, nyast först. Eftersom listan ändras med varje nytt inlägg skrivs public/llms.txt vid bygget (och är inte längre incheckad); den handskrivna delen har flyttat till scripts/llms.template.txt, och det är den filen man redigerar. Når bygget inte Sanity skrivs llms.txt utan listan.
  • En .md-adress som inte finns svarar som en vanlig 404. /anteckningsbok/<något som inte finns>.md gav rätt statuskod, 404, men sajtens 404-sida i HTML skickades märkt som text/markdown: regeln i vercel.json som satte innehållstypen gällde hela adressmönstret, inte bara filer som finns. Regeln sätter inte längre någon innehållstyp. Vercel väljer den själv efter filändelsen, text/markdown för filerna som finns och text/html för 404-sidan. X-Robots-Tag: noindex ligger kvar.
  • Ny bloggpost publicerad: "Lättare att hitta, använda och citera: tolv små ändringar i bloggen" (/anteckningsbok/tolv-sma-andringar-i-anteckningsboken, kategori Bolaget, 4 min). Dagens ändringar berättade för läsaren i stället för utvecklaren, med vad var och en är värd: en sortering som följer läsarnas reaktioner, mellanrubriker i alla inlägg och en term om dagen gör det lättare att hitta rätt; kopiera-knappen, färgad JavaScript-kod och länkar till enskilda avsnitt kortar vägen från läsning till handling; RSS-länken låter den som vill följa oss göra det utan konto eller e-postadress; och Markdown-kopiorna, med källan överst och sist, gör att en AI-assistent läser det vi faktiskt skrev och kan säga var det kommer ifrån. Länkar till ändringsloggen för den tekniska versionen.

  • Drönarfoto flyttar till /dronarfoto och får plats för undersidor. Drönartjänsten säljs till andra köpare än konsulttjänsterna (mäklare, bostadsrättsföreningar, byggprojekt) och hittas via andra sökningar, så den låg fel under /konsulttjanster/dronarefoto. Nu har den en egen adress på toppnivå, /dronarfoto (en: /en/dronarfoto), och den gamla adressen skickar vidare dit med 301 i vercel.json (sv + en) och med en omdirigering i routern för länkar inne i appen, så inläggen som länkar till den gamla adressen följer med. Brödsmulorna är ”Paperphyte / Drönarfoto” utan mellanledet Konsulttjänster; tjänsten ligger kvar som sjätte kort i tjänsterutnätet, med länk till nya adressen. Nytt fält på konsulttjanst i Studio, ”Parent service”: en sida med förälder hamnar en nivå under den (/dronarfoto/maklare), visas som kort på föräldersidan under ”Per användningsområde” i stället för i tjänsterutnätet, får föräldern i brödsmulorna och i BreadcrumbList, och länkar till föräldern och syskonsidorna under ”Läs också”. Adressen räknas ut på ett ställe, src/lib/servicePaths.js, som routern, tjänstekorten, prerendern och sitemapen delar; en undersida nås bara under sin förälder (/konsulttjanster/maklare ger ”Hittades inte” med noindex). Tre undersidor är skrivna på svenska och engelska och ligger som utkast i Sanity tills den här koden är driftsatt: ”Drönarfoto för mäklare”, ”Takinspektion med drönare” och ”Drönarfoto för byggprojekt” (460–683 ord sv), var och en med hjältebild och två–tre sektionsbilder i sidans fotorealistiska stil. Med dem följer ett utkast av huvudsidan där rubriken är ”Drönarfoto” i stället för ”Drönarefoto”, tre sektioner länkar till undersidorna och ”Kommersiella fastigheter & tomter” har fått en bild. Ortssidor för Stockholm och Trosa väntar tills det finns eget material därifrån. llms.txt och kontaktsidans tjänstelista pekar på nya adressen, och llms.txt listar undersidorna. Studio deployad samma dag. Kontrollerat i headless Chrome mot utvecklingsservern med utkasten pålagda (perspective=previewDrafts): titlar under 60 tecken, beskrivningar högst 160, canonical och hreflang på alla åtta sidor (sv + en), brödsmulor, länkarna mellan sidorna, omdirigeringen från gamla adressen och att tjänsterutnätet fortfarande visar sex kort; sitemapen listar /dronarfoto och inte den gamla adressen. ESLint: samma kända fel före och efter; Studio tsc utan fel.
  • ”Sex fasta sätt att engagera oss på” — ingressen på /konsulttjanster räknade fel. Sidans ingress sa ”Fem fasta sätt” medan rubriken på listan direkt under säger ”Sex sätt vi jobbar tillsammans” (listan kommer från Sanity). Nu säger båda sex, sv + en (servicesPage.lead). Ingressen är också sidans meta description, så den rättas med.
  • ”Konsulter i Stockholm och Trosa” — ny sektion sist på /konsulttjanster. Startsidan nämner Stockholm och Trosa, men ingen sida satte orten ihop med tjänsteorden, så en sökning som ”DevOps konsult Stockholm” hade inget naturligt att landa på. Sektionen (sv + en, whereKicker–whereCta2 i servicesPage) ligger efter åtagandena i radlayouten (tone="row"): rubrik och ingress till vänster — ”Vi arbetar med utvecklingsteam i Stockholm och resten av Sverige, på plats eller remote, inom plattformsteknik, DevOps, Delivery Engineering och produktutveckling.” — och till höger ett stycke om team eller enskild konsult, fyra länkar till tjänstesidorna Plattformsteknik, DevOps, Delivery Engineering och Produktutveckling som pillerformade monolänkar (nya .where-regler i styles.css) samt knapparna Kontakta oss och Se uppdragen. Ingen separat landningssida och inga nya påståenden: säte, uppdragslängder och tjänster är sådant sajten redan säger. Kontrollerat i headless Chrome mot bygget: 390 px (en kolumn, inget som går utanför) och 1 280 px (sv + en, länkarna får /en/-prefix). ESLint utan fel i de ändrade filerna.
  • Startsidans H1 säger vad vi gör: ”Plattformsteknik, DevOps och produktutveckling.” Rubriken var ”Från skiss till produktion och drift.”, som inte innehåller något av de ord någon söker på. Nu bär H1 de tre tjänsteorden (sv + en: ”Platform engineering, DevOps and product development.”), med samma orangea markering (på ”DevOps”) och kursiva serif-avslutning som förut. Den gamla raden lever kvar som inledning på ingressen, som nu lyder ”Från skiss till produktion och drift. Paperphyte hjälper företag att bygga, leverera och drifta digitala produkter med fokus på plattformsteknik, DevOps, Developer Experience och modern produktutveckling.” (hero i src/i18n/sv.js och en.js). Rubriken är längre än förut och ”Plattformsteknik,” är 7,8 em brett, så det gamla golvet på 56 px klippte ordet i högerkanten på en telefon; under 560 px följer storleken nu bredden (clamp(34px, 11vw, 56px)), som möter grundregelns 10vw exakt vid brytpunkten. Kontrollerat i headless Chrome mot bygget: 320, 360, 390, 560 och 561 px utan att texten går utanför, tre rader på svenska (fyra på engelska vid 390 px), 1 280 och 1 920 px som förut. Titel och meta description är oförändrade. ESLint utan fel i de ändrade filerna.
  • Startsidans titel och meta description säger samma sak som H1. <title> var ”Paperphyte — Konsultkollektiv för digitala produkter” och är nu ”Plattformsteknik, DevOps och produktutveckling — Paperphyte” (en: ”Platform engineering, DevOps and product development — Paperphyte”), i samma form som undersidornas titlar. Meta description: ”Konsultkollektiv i Stockholm och Trosa inom plattformsteknik, DevOps, Developer Experience och produktutveckling. Från skiss till produktion och drift.” (151 tecken; engelska 156, under clampDescription-gränsen på 160), så orten står kvar för lokala sökningar och tjänsteorden kommer först. Samma text går till og:title och og:description via useSeo i HomePage.jsx. Organisationens description i JSON-LD och manifestet är oförändrade.

  • Person-schema på /konsulter och LinkedIn som sameAs. Konsultlistan (sv + en) har nu ett Person-block per konsult (namn, yrkestitel, profil-URL, porträtt om det finns, worksFor Paperphyte), inte bara brödsmulor. Profilsidans Person och listans delar @id (https://paperphyte.com/konsulter/<slug>#person), så sökmotorer ser dem som samma person. Nytt fält på consultant i Studio, ”Profiles elsewhere” (sameAs, lista med https-URL:er), som båda konsultfrågorna projicerar och personSchema skriver ut som sameAs (bara https://-adresser släpps igenom). Jonas profil har fått https://www.linkedin.com/in/jonas-odencrants/ — samma adress som QR-koden på kontaktsidan leder till. Inläggens BlogPosting pekar också på den: author får samma @id och profilens URL (via samma /konsulter/<slug> som författarlänken under rubriken), så inlägg, lista och profil hänger ihop som en person. Samtidigt rättat stavfel i Jonas svenska tagline (”målglruppen” → ”målgruppen”), som också syntes i profilsidans meta description. Studio deployad samma dag. Kontrollerat i prerendern: /konsulter, /en/konsulter och /konsulter/jonas-odencrants bär samma @id, och alla 19 inlägg har författaren med det @id:t. ESLint: samma kända fel före och efter; Studio tsc utan fel.
  • Deploy-stämpeln i sidfoten visar bara datum. ”Senast deployad 28 sep. 2026” i stället för med klockslag (Footer.jsx). På /var-leverans visar raden under rutorna också bara datum (”Räknat 28 september 2026, när sajten byggdes.”), och tabellerna över senaste och misslyckade driftsättningar visar dag och månad utan klockslag.
  • Matomo räknar varje sidvisning en gång, och från klicket på ”Acceptera”. Två fel i samtyckesbryggan: applyConsent i src/lib/consent.js krävde att window._paq var en array, men när matomo.js väl laddats är den Matomos eget objekt — så ett klick på ”Acceptera” (eller ”Avböj”) gjorde ingenting förrän nästa sidladdning. Och vid samtycke pushades en extra trackPageView ovanpå den sidvisning som analytics.js redan köat under requireConsent och som setConsentGiven skickar, så varje sidladdning räknades två gånger. Nu kontrolleras bara att _paq.push finns, och trackPageView är borttagen ur applyConsent. Dessutom tar prerendern bort <script src=…matomo.js> som snutten lagt till under renderingen, så att besökare inte längre laddar matomo.js två gånger. Kontrollerat i headless Chrome mot bygget med CSP:n påslagen: klick på ”Acceptera” ger en sidvisning för sidan man står på, navigering inom sajten en per sida, omladdning en (plus Matomos ping=1 med laddtider, som inte är en visning); i produktion före ändringen gav klicket ingen och omladdningen två. ”Avböj” ger inga anrop och bara mtm_consent_removed, som cookiepolicyn redan beskriver (sattes förut först vid nästa sidladdning).
  • Content-Security-Policy och Permissions-Policy i produktion. Vercel skickade HSTS, nosniff, X-Frame-Options och referrer-policy men ingen CSP; nu sätter vercel.json en skarp policy på alla sidor, och nginx.conf har samma (dess gamla CSP saknade Matomo och api.github.com och tillät Google Fonts, som inte längre används). Policyn: allt från sajten själv; skript också från cdn.matomo.cloud; stilar med 'unsafe-inline' (prerendrade style-attribut); bilder från cdn.sanity.io, image.mux.com, paperphyte.matomo.cloud, data: och blob:; anslutningar till Sanitys API-CDN, Matomo, api.github.com (Repo-röntgen) och *.mux.com; media från *.mux.com och blob:; object-src 'none', base-uri 'self', form-action 'self', frame-ancestors 'none', upgrade-insecure-requests. Permissions-Policy stänger av kamera, mikrofon, position, betalning, USB, seriell, Bluetooth, HID, MIDI och Topics. Enda körbara inline-skriptet var Matomo-snutten; den ligger nu i public/analytics.js så att script-src klarar sig utan 'unsafe-inline' eller hashar (JSON-LD och __sanity-seeden är datablock och påverkas inte). Kontrollerat i headless Chrome med bygget serverat som https://paperphyte.com via request-interception och policyn påslagen: 29 sidtyper (sv + en, inlägg med galleri, tjänstesida, konsult, uppdrag, bransch, ordlista, alla verktyg, sök, Vår leverans, ändringar, policy, 404) utan överträdelser eller sidfel; Drönarfoto-videon spelar (HLS från stream.mux.com och *.edgemv.mux.com); sök och Repo-röntgen fungerar; Matomo skickar samma sidvisningar som produktionen i dag. En avsiktlig fetch mot en otillåten värd blockerades och rapporterades, så testet hade sett en överträdelse. Varje ny extern tjänst måste nu läggas till i policyn i både vercel.json och nginx.conf.
  • Ikoner, manifest, RSS-länk och security.txt. index.html har fått <link rel="alternate" type="application/rss+xml"> till /anteckningsbok/feed, så att flödesläsare och webbläsare hittar flödet utan att någon behöver veta adressen (flödet fanns, men ingen sida pekade på det). Nya ikoner, alla ritade ur logotypens märke i sajtens tre färger (gul, orange, rosa) från Logo.jsx: apple-touch-icon.png (180 × 180) och icon-192.png/icon-512.png på pappersfärgad bakgrund (#FAF7F2), med märket på 60 % av höjden så att det håller sig inom den säkra zonen när ikonen beskärs rund; favicon.svg, som var Vites standardlogga och inte länkades någonstans, är nu märket och länkas som SVG-favicon bredvid favicon.ico. site.webmanifest (namn, språk, färger och ikonerna, 512 också som maskable) och <meta name="theme-color">; vercel.json skickar application/manifest+json för manifestet. /.well-known/security.txt enligt RFC 9116 med hello@paperphyte.se som kontakt, Expires 2027-09-28 — förnya före dess. Kontrollerat med vite build: filerna hamnar i dist/ och taggarna i huvudet.

  • Video från Mux på tjänstesidorna. Studion har Mux-pluginet (sanity-plugin-mux-input 5.0.12, i sanity.config.ts som muxInput()), som ger verktyget Videos för uppladdning och API-nycklar och fälttypen mux.video. Konsulttjänst har fått heroVideo (”Hero video”) bredvid hjältebilden och konsulttjanstSection fältet video bredvid sektionsbilden, båda med en beskrivning i Studio av hur bild och video samspelar. KONSULTTJANST_BY_SLUG_QUERY i frontend/src/lib/sanity.js projicerar heroVideo och sections[].video till Mux-tillgångens playbackId. Finns en video visas den i bildens ram i stället för bilden (samma kantlinje och förskjutna skugga, 16:9, ny klass .post-article__video som klipper spelaren till de rundade hörnen); bilden, om den också finns, blir affischbild före uppspelning och ligger kvar som delningsbild (og:image); utan bild används Mux egen miniatyr; utan video är sidan som förut. Spelaren är @mux/mux-player-react 3.13.4 via /lazy-ingången i en ny MuxVideo.jsx, med sajtens orange som accentfärg och sidrubriken (eller sektionsrubriken) som titel — den laddas som en egen chunk först när en video finns och ramen närmar sig skärmen, så huvudpaketet växer bara med 1 kB gzippat. Mux Data-spårning och cookies är avstängda, så videon går inte runt Matomo-samtycket. CSP:n i nginx.conf släpper in image.mux.com (bilder), stream.mux.com och blob: (media), blob: (workers) och *.mux.com (anslutningar); Vercel sätter ingen CSP. Uppspelningen är publik — signerade URL:er skulle kräva en tokenserver som inte finns. Kontrollerat i headless Chrome mot bygget med en Mux-exempelvideo injicerad i DevOps-sidan: båda ramarna blir spelare på 760 × 428 px, hjältevideon laddas med Sanity-bilden som affisch. Studio: tsc och sanity build utan fel. ESLint: samma kända set-state-in-effect-fel före och efter. Studio och schema deployade samma dag; Mux-nyckeln (Video Read/Write, Data Read) läggs in under Videos → Configure plugin.
  • Videospelaren byts till webbläsarens egen, utan Google Cast. En kontroll av Drönarfoto-sidan i headless Chrome visade att Mux Player, redan när sidan laddades och utan samtycke, hämtade Googles Chromecast-bibliotek från www.gstatic.com (tre skript) och skrev media-chrome-pref-muted i localStorage — och att den började strömma videon innan någon tryckt på play. Spelarens egna attribut för att stänga av det (disableremoteplayback, nomutedpref) når inte fram i version 3.13.4. MuxVideo.jsx använder nu @mux/mux-video 0.31.4 i stället för @mux/mux-player-react: ett vanligt <video>-element med webbläsarens egna kontroller och Mux HLS-uppspelning, med disableremoteplayback (inget Cast-bibliotek), preload="none" (inget strömmas förrän vid play), Mux Data-spårning och cookies avstängda. Elementet laddas som en egen chunk bara på sidor med video; tills det är definierat visas affischbilden. Affischen är Sanity-bilden när en sådan finns, annars image.mux.com/<id>/thumbnail.webp. Accentfärgen och Mux Players egna kontroller försvinner. @mux/mux-player-react är borttaget ur package.json. I styles.css gäller .post-article__video-regeln nu mux-video och affischbilden (fyller ramen med object-fit: cover). CSP:n i nginx.conf behöver ingen ändring — image.mux.com, stream.mux.com, *.mux.com och blob: används fortfarande. Kontrollerat i headless Chrome mot bygget med Drönarfoto-sidans riktiga video: före play hämtas bara miniatyren från Mux, efter play stream.mux.com och Mux leveransnät, inga anrop till Google, inga cookies, inget i localStorage, videon spelar (4 s efter play) och kontrollerna visas. ESLint utan fel i den ändrade filen.
  • Cookie- och integritetspolicyn beskriver Mux. Cookiepolicyns ”Tredjepartsleverans av innehåll” och integritetspolicyns ”När du besöker den här webbplatsen”, ”Rättslig grund” och ”Vem vi delar uppgifter med” (sv + en) säger nu vad som hämtas från Mux och när (stillbild vid sidladdning om ingen Sanity-bild finns, videon först vid play), att Mux ser IP-adressen i sina driftloggar, att Mux Data är avstängt och att spelaren varken sätter cookies eller lagrar något i webbläsaren. Mux, Inc. (USA) har lagts till bland leverantörerna, anlitad via Vercel Marketplace; både Mux och Vercel anges som certifierade enligt EU-U.S. Data Privacy Framework, kontrollerat mot respektive integritetspolicy. Leverans av video ingår i det berättigade intresset för driften. Policyerna publicerades samma dag, efter att spelarbytet ovan kontrollerats i produktion (bara miniatyren från Mux vid sidladdning, inga anrop till Google, inget i localStorage).
  • Första videon: Drönarfoto (/konsulttjanster/dronarefoto). Tjänstesidan har en hjältevideo uppladdad till Mux via Studio (publik uppspelning, status ready), i stället för den tidigare hjältebilden, som är borttagen — affischen är Mux egen miniatyr. Mux-kontot kopplades via Vercel Marketplace, så plan och fakturering sköts i Vercel.
  • Ny bloggpost publicerad: "Video på webbplatsen med Mux, Sanity och Vercel" (/anteckningsbok/video-med-mux, kategori Bolaget, 2 min). Om varför video på sajten gick via Mux i stället för S3 och CloudFront, Sanitys CDN eller inbäddning från YouTube och Vimeo (som kan kräva samtycke): Mux läggs till via Vercel Marketplace utan separat konto, och en plugin med ett videofält i Studio låter redaktören ladda upp direkt. Länkar till Mux, Vercel och Sanity samt, sedan kvällen samma dag, till tjänstesidan Drönarfoto från inledningens ”sidan för drönarfoto” (fetstilen slutar nu före länken, eftersom en länk inuti **…** inte expanderas). Genererad hero-bild i sajtens isometriska stil (drönare, filmremsa, arkivskåp och en spelare på en webbsida) med svensk alt-text.
  • Fyra nya ordlistetermer: DevOps, Delivery engineering, Återställningstid (MTTR) och Stor språkmodell (LLM) (/ordlista/devops, /ordlista/delivery-engineering, /ordlista/mttr, /ordlista/llm, sv + en), samma form som de 23 befintliga. Alla fyra enligt regeln ”bara sådant sajten redan säger utan att förklara”: DevOps nämns ett tjugotal gånger på fyra sidor och Delivery engineering ett tolvtal, båda med egna tjänstesidor men utan kort definition; MTTR står oförklarat på tjänstesidorna Plattformsteknik och DevOps; LLM tolv gånger på Produktutveckling. DevOps och Delivery engineering bygger på inlägget *Delivery Engineering vs. DevOps* (”DevOps berättar hur ni delar ansvar, delivery engineering hur ni delar verktygen”); en extern källa per term — Werner Vogels intervju i ACM Queue, continuousdelivery.com, DORA:s guide till mätvärdena och Wikipedia. Relaterade tjänster DevOps, Delivery Engineering, DevOps + Plattformsteknik respektive Produktutveckling. Spegelreferenser tillagda i SRE, Platform engineering, DORA-mätvärden, CI/CD, Feature flags, Progressive delivery, Observability, Felbudget, Blameless post-mortem, Kodagent, MCP och AGENTS.md. Auto-länkaren träffar ”DevOps”, ”Delivery Engineering”, ”MTTR” och ”LLM:er” i befintliga inlägg och tjänstesidor. Ordlistan har 27 termer.

  • Hjältebilden i inläggskorten på Anteckningsboken. Färgfältet överst i varje kort på /anteckningsbok (och /en/anteckningsbok) är nu inläggets hjältebild, beskuren till samma höjd; kategorietiketten ligger kvar ovanpå bilden och de dekorativa formerna försvinner när det finns en bild. Det utvalda inlägget visar på samma sätt hjältebilden i hela vänsterhalvan, med ”NYTT”-tejpen kvar och den stora roterade kategorin borttagen. Inlägg utan hjältebild behåller färgfältet. Bilderna hämtas i flera bredder med srcset och nya sizes (IMG_SIZES.postCard, IMG_SIZES.featuredCard), korten under vecket laddas lat, och bilderna har tom alt-text eftersom kortets rubrik redan är länktexten. Listfrågan hämtade redan heroImage, så ingen ändring i Sanity. Kontrollerat: prerendern ger bild i alla 17 kort och det utvalda, skärmbilder i headless Chrome på 1 280 och 390 px utan horisontell överskjutning. ESLint: samma kända set-state-in-effect-fel i BlogPage.jsx före och efter.

  • Bildgalleri på blogginlägg, placerat i brödtexten med ![Alt](handle). Ett inlägg har nu fältet images[] i Studio (bild med handle, alt-text och bildtext; handle är gemener, siffror och bindestreck). Ett stycke i brödtexten som bara innehåller ![Alt](handle) renderas som en figur med samma ram som hjältebilden — kantlinje, förskjuten skugga och bildtext under — via transformBody(body, { images }) i frontend/src/lib/portableText.jsx och den nya Figure.jsx; alt-texten i stycket vinner över galleriets när den anges, srcset och sizes följer hjältebilden, och bredd/höjd sätts från Sanitys bildmetadata så layouten inte hoppar när bilden laddas. Ett okänt handle visar en streckad ”Missing image”-ruta i dev-bygget och ingenting i produktion; en referens mitt i ett stycke lämnas som text. Meta-beskrivning, RSS-flöde och sökindex rensar bort referenserna. Sanity-frågan för enskilt inlägg projicerar galleriet med URL och mått. Studio och schema deployade samma dag; befintliga inlägg påverkas inte.
  • Ny bloggpost publicerad: "Ett besök på Tokyo Game Show 2026" (/anteckningsbok/tokyo-game-show-2026, kategori Bolaget, 4 min). Reseberättelse från konferensresan med Geeks AB till mässans 30-årsjubileum på Makuhari Messe — branschdagen den 17 september, skalan (elva hallar, 1 138 utställare från 53 länder), indieområdet, merchandise och tyfonen som ställde in sista mässdagen — med länkar till mässans officiella pressmeddelanden och en utgående länk till geeks.se. Första inlägget som använder galleriet: tretton foton från mässan placerade intill de stycken de illustrerar, samtliga med svensk alt-text och bildtext; hjältebilden är en genererad isometrisk illustration av Makuhari Messes entré i sajtens stil. Fotona skalades till max 2 400 px och rensades på EXIF och GPS-position innan uppladdning, som gick via sanity exec --with-user-token eftersom läs-token i .env inte får skapa assets.

  • Vår leverans: sajtens egna DORA-mätvärden (/var-leverans + /en/var-leverans, länkad i sidfoten efter deploy-stämpeln — ”Senast deployad … · Vår leverans →” — och i llms.txt). Fyra rutor — deployfrekvens per vecka, ledtid för ändringar (median, med 90-percentil i noten), andel misslyckade ändringar och återställningstid — följt av ett stapeldiagram över driftsättningar per svensk kalendervecka (kodändringar i bläck, innehållsombyggen i grått, pågående vecka i orange; hover/fokus per vecka ger siffrorna, tabellerna nedanför bär samma tal), de tio senaste driftsättningarna med ledtid, ev. misslyckade ändringar, ”Så räknar vi” och en ärlig begränsning: sajten saknar övervakning, så ett fel ingen återställde räknas inte. Källan är GitHubs deployment- och commit-API för repot, lästa i prebuild av scripts/generate-delivery.js till public/leverans.json (gitignorerad): Vercels integration registrerar ett GitHub Deployment per produktionsbygge och ett success-status varje gång en version går live — första gången för pushen, sedan för varje ombygge från Sanity-webhooken — så varje success är en driftsättning med tidsstämpel; ledtiden räknas från commitens författardatum (för squash-mergade PR:ar från branchens första commit) till första driftsättningen som innehöll den; misslyckade ändringar är failure/error-status eller commits som senare revertats på main. Fönster: tretton kalenderveckor. Vid bygget 2026-09-10: 132 driftsättningar (82 kod, 50 ombyggen) på 88 dagar, 10,5 per vecka, ledtid median 1 min och 90 % inom 4 min över 89 commits, 0 misslyckade. Utan token (GITHUB_TOKEN, annars gh auth token lokalt) eller vid API-fel skrivs available: false och sidan säger att talen inte kunde räknas — bygget stannar aldrig på sajtens egna mätvärden. Kräver en read-only GITHUB_TOKEN som miljövariabel i Vercel-projektet paperphyte innan sidan visar siffror live. Sidan prerendras på båda språken med JSON:en i seeden (861 ord), ligger i sitemapen, ny useDelivery-krok i lib/buildInfo.js bredvid changeloggen. Kontrollerat i headless Chrome: hover på vecka 31 aug ger ”48 kodändringar, 38 ombyggen” och talet 86 över stapeln, tabb landar på sidan utan fel, ingen horisontell överskjutning på 390 px. ESLint 24 fel före och efter.
  • Mini-Väntetidskalkylator på startsidan. Överst i verktygssektionen, före de fyra korten: de tre reglagen som bär resonemanget (utvecklare, byggen per dag, minuter per bygge) och de två siffrorna de ger — förlorade utvecklartimmar och kronor per år — som räknas om medan man drar. Andelen förlorad väntan och timkostnaden ligger kvar på verktygets standard (60 %, 850 kr) och nämns i en fotnot; ”Räkna vidare →” öppnar hela kalkylatorn med samma siffror i adressen (?dev=&b=&min=), så inget behöver dras om. Konstanterna och matematiken är utbrutna ur sidan till src/lib/vantetid.js (LIMITS, DEFAULTS, computeWait, inputsFromSearch, searchFromInputs) och används av både verktyget och widgeten, så de kan aldrig visa olika tal; kalkylatorsidan beter sig som förut. Två kolumner från 761 px (reglage till vänster, resultat till höger bakom en linje), staplat på mobil. Kontrollerat i headless Chrome mot bygget: standardvärdena ger 1 056 timmar och 897 600 kr (samma som lanseringsinlägget), 25/6/8 ger 2 640 timmar och 2 244 000 kr i widgeten och exakt samma i verktyget efter klick på länken, engelska startsidan formaterar ”1,056” / ”SEK 897,600” och länkar under /en. Inga sidfel; ESLint 24 fel före och efter. Ur TODO:ns ”Startsidan: visa sådant som händer på andra ställen”.
  • Sök på sajten (/sok + /en/sok, förstoringsglas i sidhuvudet bredvid språkväxlaren, med etikett i mobilmenyn). Ett fält, resultat i tre grupper — Verktyg, Ordlista, Inlägg — med rubrik, metarad (kategori och datum för inlägg, ”9 frågor · 5 minuter” för verktyg) och ett utdrag runt första träffen i brödtexten, träfforden markerade. Index: scripts/generate-search-index.js körs i prebuild bredvid sitemap och RSS och skriver public/search-index.json — inläggens rubrik, ingress, taggar och hela brödtexten som ren text (kodblock bortplockade), ordlistans term och kortdefinition på båda språken, verktygens namn, ingress och punkter ur i18n på båda språken; 70 poster, 93 kB (32 kB gzippat), gitignorerad som feeden. Sökningen (src/lib/search.js) sker i webbläsaren: indexet hämtas först vid första tangenttrycket (eller direkt vid ?q=), aldrig annars; varje ord i frågan måste träffa någonstans; träff i rubrik väger tyngst, sedan taggar/term, ingress och brödtext, med ordgränsbonus; diakritiska tecken viks så att ”vantetid” hittar Väntetidskalkylatorn; högst tolv per grupp. Frågan ligger i ?q= (ersätter historikposten per tangenttryck, så bakåtknappen inte stegar genom bokstäverna) och kan delas. På /en visas termer och verktyg på engelska och inläggen — som är svenska — med markeringen ”in Swedish” via PostLink. Sidan är noindex, follow och ligger utanför sitemapen men prerendras (tom) på båda språken så att adressen svarar 200 hos Vercel. Kontrollerat i headless Chrome mot bygget: inga indexanrop före första tangenttrycket, ett efter; ”golden path” ger termen och tre inlägg, ”vantetid” verktyget och tre inlägg, ”error budget” på engelska två termer under /en/ordlista/…, ”xyzzy” tomläget; historiklängden växer inte med skrivandet. ESLint: 24 fel före och efter. Notering: inlägg med publishedAt i framtiden (Backstage-inlägget är just nu daterat 2026-09-11) kommer med i indexet först vid nästa bygge efter den tidpunkten, som för RSS och sitemap.
  • Ordlistelänkar på tjänstesidor och uppdrag. Samma automatiska länkning som inläggen har haft sedan ordlistan kom: första omnämnandet av en term i brödtexten blir en prickad länk till /ordlista/<slug> med kortdefinitionen som tooltip. Tjänstesidornas intro och alla sektioner samt uppdragens beskrivning går nu genom linkGlossaryTerms (efter transformBody, som förut). Ny seen-mängd i länkaren så att en term länkas en gång per *sida*, inte en gång per sektion — Plattformsteknik-sidan länkar tio termer på svenska och elva på engelska, DevOps sex, Delivery Engineering fyra, DX fem (sex på engelska, där ”developer portals” träffar Utvecklarportal; den svenska texten säger också ”developer portals” och träffar därför inte den svenska termen). useGlossaryTerms läser nu ordlistan ur prerenderns seed och hämtar via fetchSanity, så listan (23 termer, 14 kB JSON) följer med i varje prerendrad sida som länkar och länkarna finns vid första målningen i stället för att försvinna vid hydreringen och komma tillbaka efter hämtningen — det gällde även inläggen tidigare. Fallstudiernas layout (ATG, QRGeno, DeLaval, Urb-it) renderar egna strängfält och får varken länkar eller seed; kroken tar ett enabled-argument och uppdragssidan slår på den först när dokumentet är laddat och inte är en fallstudie. Inget av de sjutton vanliga uppdragen nämner i dag någon term, så där syns ingen skillnad ännu. Kontrollerat: länkaren körd över alla tjänstesidor och uppdrag på båda språken utan dubbletter; prerender av Plattformsteknik, DevOps, Delivery Engineering, Euroflorist och DeLaval; hydrering i headless Chrome med ordlistehämtningen blockerad behåller alla länkar (10/10 sv, 11/11 en, /en/ordlista/… med engelsk tooltip). ESLint: 24 fel före och efter (de kända set-state-in-effect).

  • Ny bloggpost publicerad: "Backstage är inte plattformen — det är dörren till den" (/anteckningsbok/backstage-ar-inte-plattformen, kategori DX, 5 min). Tesen: en utvecklarportal är dörren till den interna utvecklarplattformen, inte plattformen — katalogen svarar på vad som finns, dokumentationen på hur det fungerar och mallarna på hur man gör, och bara mallen utför något. Fyra sätt portaler dör (katalogen som ruttnar, wiki med bättre tema, mallen ingen underhåller, fel mått), ett avsnitt om varför repot ska äga det portalen visar så att människor och kodagenter får samma svar, och fem steg som börjar utan Backstage. Länkar till golden paths-, plattform som produkt- och agent-inläggen samt IDP-termen. Genererad hero-bild i sajtens isometriska stil med svensk alt-text.
  • Ny ordlisteterm: Utvecklarportal (Backstage) (/ordlista/utvecklarportal, sv + en), samma form som de 22 befintliga. Termen låg på gränsen enligt regeln ”bara sådant sajten redan säger” — tolv omnämnanden på DX-tjänstesidan men inget inlägg — och publicerades tillsammans med inlägget ovan, som använder den; auto-länkaren träffar termen på första ”Backstage”. Relaterade tjänster Developer Experience och Plattformsteknik; relaterade termer IDP, Golden path, DX och Platform engineering, med spegelreferenser tillbaka i alla fyra. Kontrollerat: brödtexterna renderar genom Portable Text-pipelinen utan kvarvarande markdown, definitionerna är 250/252 tecken, alla referenser löser upp åt båda hållen. Ordlistan har 23 termer. Notering: bildgeneratorn publicerade inlägget av sig själv när bilden laddades upp; det avpublicerades inom några minuter och publicerades sedan avsiktligt med termen.
  • Ny bloggpost publicerad: "Bygg en gång: det ni testade ska vara det ni driftsätter" (/anteckningsbok/bygg-en-gang, kategori Delivery Engineering, 5 min). Tesen: en pipeline som bygger om inför produktion driftsätter något som aldrig testats, eftersom ett bygge är en funktion av koden och allt annat som råkar vara sant i ögonblicket — basimage på ”latest”, verktygskedjan på runnern, det som hämtas från nätet under bygget och miljövariabler vid byggtid. Principen bygg en gång, befordra artefakten: en oföränderlig artefakt med digest eller checksumma går genom test, staging och produktion, och miljöerna skiljer sig i konfiguration vid körning. Fyra vanliga fällor (frontend-bygget som bakar in API-adressen, taggar som skrivs över, den separata produktionspipelinen, tron att lockfilen räcker) och fem steg för att börja. Länkar till ordlistans CI/CD, Feature flags och Progressive delivery samt tjänstesidan Delivery Engineering. Första inlägget med kategorietiketten Delivery Engineering. Genererad hero-bild i sajtens isometriska stil med svensk alt-text; brödtexten kontrollerad genom transformBody utan kvarvarande markdown.

  • Sex nya ordlistetermer, valda efter vad sajten redan säger. Alla 70 innehållssidor (inlägg, tjänstesidor, branschintron, uppdrag, verktygstexterna, llms.txt och ordlistan själv) genomsökta efter ett åttiotal kandidattermer inom leveransteknik, DX, plattform, SRE och AI-assisterad utveckling. Regeln: en term får en sida bara om sajten redan använder den utan att förklara den, så att ordlistelänkningen i inläggen slår till och sidan får riktiga inkommande länkar. Sex klarade det och är publicerade på sv + en, i samma form som de sexton befintliga (term, kortdefinition på 1–2 meningar, två stycken, relaterade tjänster och termer): Kodagent (fem inlägg, båda AI-verktygen), AGENTS.md (fyra inlägg och ett helt verktyg), Plattform som produkt (fyra inlägg, ett med samma namn), Plattformsteam med Team Topologies-modellen (två inlägg, två tjänstesidor), Felbudget med SLO och SLI (SRE-definitionen använde ”SLO:er” och ”felbudgetar” oförklarat) och Ledtid (tre tjänstesidor, DORA-definitionen och Väntetidskalkylatorn). Brödtexterna länkar till inlägget eller verktyget där sajten redan talar om saken, med relativa adresser så att engelska läsare stannar på /en; varje term har en fetad kärnmening och en extern källa (agents.md, Team Topologies, Googles SRE-bok). Tio befintliga termer har fått spegelreferenser tillbaka (MCP → Kodagent och AGENTS.md, Golden path → AGENTS.md, Platform engineering → Plattform som produkt och Plattformsteam, IDP → Plattform som produkt, Inner source → Plattformsteam, SRE och Observability → Felbudget, DORA → Ledtid och Felbudget, CI/CD och Trunk-based → Ledtid), så inga referenser är enkelriktade. Kontrollerat: alla referenser löser upp åt båda hållen, brödtexterna renderar genom sajtens Portable Text-pipeline utan kvarvarande markdown, och ordlistelänkaren träffar fyra av de sex i befintliga inlägg (Felbudget och Ledtid nås via SRE, DORA och tjänstesidorna). Valda bort: självbetjäning och incident (vardagsord), AI-assisterad utveckling (sajtens egen kategorietikett), mikrotjänster (bara i uppdrag, alla andra har sidan) och allt med noll omnämnanden på sajten — cognitive load, inner loop, toil, blue-green, SBOM, shift left, DDD, chaos engineering, context window, RAG, evals. Utvecklarportal/Backstage ligger på gränsen (tolv omnämnanden på DX-sidan, inget inlägg) och väntar på ett inlägg. Ordlistan har nu 22 termer; Dagens term roterar över alla.
  • Dagens term på startsidan. Ett kort mellan ”Senaste inläggen” och kontaktbandet (TermOfDay): dagens datum, en term ur ordlistan som länk till /ordlista/<slug> och den korta definitionens första mening, plus ”Hela ordlistan →”. Vilken term det blir följer av datumet ensamt (lib/termOfDay.js): dygnet räknas i svensk tid via Intl.DateTimeFormat med Europe/Stockholm, så bytet sker vid midnatt i Sverige oavsett var besökaren eller Vercels byggmaskin står; termerna sorteras på slug (samma term på svenska och engelska, oberoende av svarsordningen från Sanity) och gås igenom i en blandad ordning som blandas om vid varje fullt varv, seedad med varvnumret (mulberry32 + Fisher–Yates), så varje term visas exakt en gång per varv och aldrig två dagar i rad. Första meningen klipps vid första punkt, utropstecken eller frågetecken som följs av versal, med undantag för förkortningar (”t.ex.”, ”e.g.”, ”etc.”) så att bara DORA-mätvärdena (två meningar) klipps i dag. Hela ordlistan (16 termer, 9,6 kB) seedas från prerendern med samma GLOSSARY_LIST_QUERY som /ordlista, så valet görs på besökarens dag även när ögonblicksbilden byggdes en annan dag; kortet har fast minsta höjd och textrutan tre reserverade rader (fem på mobil), så en kortare eller längre definition flyttar inget under. Laddningstillstånd och fel hanteras som ”Just nu”: platshållare i samma höjd, ingenting alls om hämtningen misslyckas utan seed. Två kolumner från 961 px (datum och term till vänster, meningen till höger), staplat därunder. Kontrollerat i Node: samma val oavsett indataordning, 64 dagar utan upprepning med alla 16 per 16-dagarsperiod, 80 000 dagar utan två lika i rad, och gränsen 2026-09-07 22:30 UTC räknas som den 8:e. Ur TODO:ns ”Startsidan: visa sådant som händer på andra ställen”.

  • Drönarefoto med användningsfall, riktat till fastighetsmäklare. Tjänstesidan hade fyra korta generiska sektioner (259 ord). Nu inleder två nya: ”Bostadsförsäljning: visa läget, inte bara huset” — varför flygbilden är det som får en spekulant att klicka vidare (tomten, avståndet till vattnet, kvällssolen på altanen), vilket paket mäklaren får (stillbilder från olika höjder, översikt med markerad tomt, kort film för sociala medier och visning) och att vi utgår från Trosa och Stockholm — och ”Kommersiella fastigheter & tomter” (infarter, lastzoner, granntomten; gränser markerade som illustration, inte lantmäteriunderlag). Efter ”Tak & fasad” en sektion för bostadsrättsföreningar och fastighetsägare (underlag inför takrenovering och upphandling, daterad dokumentation), och sist ”Så går det till” (adress och syfte in, flygning planerad efter väder, ljus och luftrumsregler, leverans i format för webb, sociala medier, presentation och tryck, svar inom 48 timmar). Introt har fått en mening om mäklare, sökningstiteln är ”Drönarefoto för mäklare, bygg & inspektion” och meta description nämner mäklare, byggprojekt och fastighetsägare. Ny fotorealistisk sektionsbild i sidans stil: röd villa på stor tomt vid en sjö i kvällsljus, med alt-text. Svenska och engelska; 259 → 525 ord (en 608). Inga påståenden om priser, leveranstider eller certifieringar.

  • Sexton uppdragssidor omskrivna och översatta. Uppdragen som låg under 100 ord (JNT, Eniro, Cellip, Signal Bredbånd, Nordiskt bevakningsföretag, Type2dialog, Musikindustrin, Norsk aktör – CRM-utbildning, Euroflorist, Fresh Fitness, Viamare, Bioindustri Norge, Global koncern, SKL, Kammarkollegiet och Vi-skogen) har fått nya svenska beskrivningar i Studio — bakgrund om kunden, roll, angreppssätt och ett utfallsstycke — och den engelska versionen är översatt stycke för stycke så att båda språken har samma antal stycken (4–11 per sida). Från 24–91 ord till 124–437 (sv). Alla 21 uppdrag publicerade, inga utkast kvar. Underlaget är kundernas egna presentationer och uppdragsbeskrivningarna; inga uppmätta utfall har lagts till. Där svenskan återger kundens presentation i ”vi”-form är engelskan skriven i tredje person, så att ”we” aldrig pekar på Paperphyte. Nordiskt bevakningsföretag anonymiserat på båda språken (kundnamn, tidigare koncern, år och huvudkontor strukna). SKL-sidan säger nu SKR (tidigare SKL). Type2dialog stavas som bolaget självt gör i brödtexten; titeln ”Type2Dialog ApS” oförändrad.
  • Ny bransch: Tjänsteföretag. Viamare (marin service), Eniro (katalogföretag) och intresseorganisationen i musikindustrin låg under Telekom utan att vara telekombolag. Sjunde bransch ”Tjänsteföretag” / ”Service Businesses” (/branscher/tjansteforetag, ledord ”CRM, affärssystem, integrationer”) i industries.js, Studions branschlista och llms.txt — startsidans rutnät, /branscher, prerender och sitemap följer med automatiskt, och siffran under heron räknar nu sju branscher. Ett ensamt sista kort i det trekolumniga rutnätet spänner över hela raden så sju kort inte lämnar tomma celler. Nytt industry-dokument i Sanity med intro på sv + en (167/193 ord) i samma form som de sex andra: vad branschen kräver, de tre uppdragen med länkar, tjänsterna Produktutveckling och DevOps. De tre uppdragen flyttade; Telekom har kvar Cellip, JNT och Signal Bredbånd, vilket är precis de tre Telekom-introt nämner. Kod deployad före innehållet så att uppdragen aldrig saknade bransch på sajten. Studion deployad med det nya värdet. Meta description för den nya sidan: 141 tecken (sv), 147 (en).
  • E-postadressen är hello@paperphyte.se. Texten sa hej@paperphyte.com, men paperphyte.com har inga MX-poster alls — det har paperphyte.se (Proton). Bytt på kontaktsidan på båda språken.
  • Kontaktsidan har fått text. Sidan prerendrade 72 ord: rubrik, ledtext och två kort. Under korten ligger nu tre block på svenska och engelska — ”Så går ett första samtal till” (WhatsApp eller hej@paperphyte.com, svar inom 48 timmar på vardagar, kort och utan åtagande, ett ärligt ja eller nej), ”Vad vi kan hjälpa till med” (de sex tjänsterna med en rad var, länkade) och ”Bra att veta” (litet kollektiv i Stockholm och Trosa sedan 2017, uppdrag från tvådagarsutbildning till fleråriga åtaganden, kunder i Sverige, Norge, Finland och Danmark, de fyra verktygen, och integritetspolicyn för det man skriver). Allt som påstås finns redan någon annanstans på sajten. 72 → 382 ord (en 78 → 398). Länkrenderaren för inline-markdown är utbruten till inlineLinks.jsx och delas med verktygssidornas ToolAbout.
  • Inga dubbla titlar mellan svenska och engelska sidor. Sju sidpar delade titel: för uppdrag i SaaS & Startups är branschsegmentet samma sträng på båda språken, och för långa kundnamn (Kammarkollegiet, SKL) släppte pageTitle branschen före varumärkessuffixet så att bara kundnamnet blev kvar. Uppdragssidorna heter nu ”Kund — Uppdrag · Bransch — Paperphyte” respektive ”Client — Assignment · Industry — Paperphyte”, och hjälparen behåller hellre ett språkspecifikt segment (först det långa, sedan bara ”Uppdrag”/”Assignment”) än suffixet när allt inte ryms på 60 tecken — så ”Kammarkollegiet, Kapitalförvaltningen — Assignment” tappar Paperphyte men skiljer sig från svenskan. Samma grepp lyfte de kortaste titlarna: ”JNT — Telekom — Paperphyte” (26) är nu ”JNT — Uppdrag · Telekom — Paperphyte”. Cookies-policyns engelska rubrik är ”Cookie policy” i Sanity, i linje med ”Privacy policy”. Kontrollerat mot alla 149 sidor efter deploy: inga dubbla titlar, ingen över 60 tecken, sju under 30 (ordlistans ”Vad är GitOps?” och liknande, som är specifika nog). Sista punkten ur SEO-genomgången 2026-09-06.
  • Förklarande text under AI-redo-kollen och Repo-röntgen. De två verktygen prerendrade 63 respektive 155 ord statisk text — rubrik, ledtext och en knapp — så det fanns inget för en sökmotor att ranka på och inget för en läsare att bedöma verktyget efter. Ny sektion ToolAbout under verktyget, matad av ett about-block i i18n: vad det mäter, de fyra delarna eller grupperna med vad var och en kollar, hur poängen läses (de riktiga trösklarna: 26/13 av 36 respektive 14/9 av 18), att inget lämnar webbläsaren, och vart man går sedan (Developer Experience och AGENTS.md-generatorn respektive generatorn och Delivery Engineering). Inline-markdown-länkar blir router-länkar. Svenska och engelska, 250–270 ord per verktyg. Live: AI-redo-kollen 63 → 334 ord (en 70 → 377), Repo-röntgen 155 → 407 (en 164 → 444). Väntetidskalkylatorn och AGENTS.md-generatorn låg redan på 400–500 ord och är orörda. Ur SEO-genomgången 2026-09-06.
  • Snabbare sidor på mobil: lättare bundle, responsiva bilder, inga laddningsblinkar. Tre delar ur SEO-genomgången. (1) @sanity/client ersatt i webbläsaren av en 20 raders fetch-klient med samma sanityClient.fetch(query, params)-form (parametrar som JSON i $namn-querystring mot API-CDN:et); den officiella klienten drog med sig rxjs, get-it och stega-kod för skrivskyddad GROQ. Huvudbundeln 630 → 543 kB minifierad, 192 → 164 kB gzippad; byggskripten använder den officiella klienten i Node som förut. (2) Alla Sanity-bilder har srcset (heroes 640–1600, kort 400/800) och sizes som matchar sin container, så en mobil inte längre laddar 1600-pixlars heroes; preconnect till bild-CDN:et och API-CDN:et i index.html. (3) Detaljsidorna — uppdrag, tjänst, inlägg, ordlisteterm, policy, konsult, bransch och konsultindex — startade från ett laddningstillstånd vid första klientrenderingen och ersatte det prerendrade innehållet med ”…” tills svaret kom: uppmätt layoutskift 0,71 på mobil, synligt först när hämtningen blev snabbare. De startar nu från prerenderns seed (prerendered()/fetchSanity(), samma mönster som listsidorna) och faller bara tillbaka på laddningstillståndet vid klientnavigering till en annan slug. Konsultsidans CV-uppdrag hämtas utan seed (de matar bara PDF:en och hade lagt 150 kB i HTML:en) och branschfrågan bär inte längre beskrivningar som korten aldrig visar. Routebaserad kodsplittring övervägdes och valdes bort: appen monteras med createRoot().render(), så en lat route hade blankat den prerendrade sidan tills chunken kom, och sidorna är bara ~110 kB av bundeln. Lighthouse mobil på /uppdrag/viamare: 84 → 93, LCP 4,3 → 3,0 s, CLS 0, TBT 60 → 10 ms; tjänstesida 86, inlägg 93, alla med CLS 0. Verifierat live på sju detaljsidor: inget laddningstillstånd, inget skift. Startsidan ligger kvar på 79/LCP 4,0 s i simulerad slow-4G — dess LCP är hero-rubriken, bunden av anslutning, stylesheet och typsnitt; nästa steg där vore inlinad kritisk CSS.
  • Filterlisten på /uppdrag fastnar inte längre på mobil. Bransch- och rollfiltret var position: sticky och blev ~420 px högt på en 390 px bred skärm — halva skärmen — så uppdragen rullade in under det; sjunde branschchipet gav ännu en rad. Under 760 px rullar listen med sidan; på surfplatta och desktop är den fortfarande fäst.
  • Riktiga 404:or för okända adresser. vercel.json skrev om alla okända adresser under /uppdrag, /konsulttjanster, /branscher, /konsulter, /anteckningsbok, /policy, /kontakt och hela /en till SPA-skalet — status 200, skalets standardtitel, och X-Robots-Tag: noindex som var knuten till /_shell.html nådde aldrig de omskrivna adresserna; noindex kom först när JavaScript kört. Eftersom varje giltig route prerendras till en statisk fil gjorde omskrivningarna ingen nytta för riktiga sidor. De är borttagna: Vercel serverar statiska filer först och faller annars tillbaka på dist/404.html med status 404, sidan med ”404 — Paperphyte”, noindex och länk hem. Verifierat live: 13 giltiga adresser (sv, en, verktyg med query) svarar 200 och sex påhittade svarar 404; 308-omdirigeringarna för /en/anteckningsbok/<slug> och avslutande snedstreck är opåverkade. Enda priset: en slug som publiceras svarar 404 för sökrobotar under minuterna fram till webhook-bygget, medan en människa som följer länken ändå får sidan renderad i webbläsaren. Ur SEO-genomgången 2026-09-06.
  • Språkväxlaren är en riktig länk. SV/EN i sidhuvudet var två knappar som bytte sida med window.location.assign, så inte en enda av de 67 engelska sidorna gick att nå via länkar från den svenska sidan (och tvärtom) — sitemap och hreflang pekade ut dem, men bara länkar för vidare auktoritet. Nu två <a> med hreflang, lang och aria-current, till samma sida på det andra språket, fortfarande som full sidladdning eftersom routerns basename är fast per dokument. Blogginlägg är enspråkiga, så där pekar EN på det engelska bloggindexet i stället för en adress som bara omdirigerar tillbaka. Utseendet oförändrat; mittenklick och ”kopiera länk” fungerar nu. Ur SEO-genomgången 2026-09-06.
  • CV-blurb på uppdragen. Nytt fält cvBlurb (sv + en, tre rader) på assignment — en kort text för konsult-CV:t som inte visas på uppdragssidan. PDF-CV:t (CVDocument) visar nu blurben för det språket när den finns och faller annars tillbaka på beskrivningens första stycke som förut; ingen fallback över språkgränsen, så ett engelskt CV visar aldrig en svensk blurb. Fallbacken rensar inline-markdown, som annars hade lagt kundlänkarnas syntax i PDF:en. 21 svenska blurbar skrivna: en mening om kunden och tre om rollen, vad som byggdes och vad det gav (67–102 ord), hämtade ur beskrivningarna utan nya siffror. Engelska blurbar översatta från de svenska, mening för mening (81–113 ord), så att båda PDF-språken nu visar blurben för alla 21 uppdrag — verifierat genom att rendera CVDocument med CV-frågan och jämföra texten fält för fält. Studion deployad med fältet.
  • Hero-illustrationer på alla 21 uppdrag. Ingen uppdragssida hade någon bild; mallen renderade redan heroImage överst i artikeln och som delningsbild, så /uppdrag-länkar delades med sajtens standardbild. Nu en illustration per uppdrag i samma handritade linjestil som tjänstesidorna (krämfärgat papper, tunna bläcklinjer, dämpat mintgrönt och senapsgult, ingen text), 1376×768, genererade med Sanitys bildgenerator från en prompt per uppdrag som beskriver just det arbetet: kustkarta med båtar och kartnålar (Viamare), transportband genom tre stationer (Eniro), musiknot till faktura (musikindustrin), kalkylblad och arkivlåda in i en databas (bevakningsföretaget), radiomast över en fjord (Signal), whiteboard och runt bord utan människor (CRM-utbildningen) och så vidare. Alla har beskrivande alt-text på svenska och engelska (lokaliserat fält). Bilderna syns också i uppdragskorten på bransch- och tjänstesidorna och under ”Relaterade uppdrag”, och fallstudierna får delningsbild även om deras egen layout inte visar heron. Verifierat live på sv och en.
  • Länkar till kundernas webbplatser. Tio uppdragssidor som namnger kunden länkar nu till kundens egen sajt från första stycket, på båda språken: Cellip, Eniro-koncernen, Euroflorist, JNT, Kammarkollegiet, Signal, SKR, Star Stable, Type2dialog (plus Komponent i stycket om sammanslagningen) och Vi-skogen. Vanliga redaktionella länkar med rel="noreferrer noopener", ingen nofollow, samma flik. Varje mål kontrollerat före och efter: alla elva svarar 200. Viamare fick ingen länk — viamare.se är parkerad hos Loopia och viamare.com är en brittisk färjebokare. De fem anonymiserade uppdragen och de fyra fallstudierna (annan brödtext) är orörda. Inlagt via sanity exec med krav på exakt en träff per fras, 22 spann.
  • Egna meta descriptions på uppdragssidorna. Nytt fält seoDescription (localeString, varning över 160 tecken) på assignment; sidan läser det först, därefter fallrubrikens ingress och sist brödtexten som förut. Brödtextfallbacken rensar nu inline-markdown så länksyntax aldrig hamnar i en meta-tagg (gäller alla sidor som använder plainTextFromPortableText). 21 × 2 texter skrivna om arbetet — roll, vad som byggdes, teknik, kund där den är namngiven — på 135–160 tecken, inlagda via sanity exec med längdkontroll och verifierade live på alla 42 sidor. Före: de första 155 tecknen ur beskrivningen, som på elva sidor var kundens bakgrund klippt mitt i meningen (”Eniro-koncernens verksamhet bedrivs inom två affärsområden. Affärsområdet Marketing Partner, som stod för 64 procent…”). Efter: ”Delivery Engineer hos Eniro under ett pressat SugarCRM-införande: automatiserad releasesättning och ett snabbare test- och utvecklingsflöde med färre handgrepp.” Studion deployad med fältet.
  • Interna länkar på uppdragssidorna. De 17 vanliga uppdragssidorna hade noll länkar i brödtexten och fyra i hela huvudinnehållet (brödsmulor + kontakt); sex av dem (Euroflorist, Fresh Fitness, Type2dialog, bioindustribolaget, bevakningsföretaget, norska CRM-utbildningen) hade inga inkommande innehållslänkar alls, elva bara en från branschintrot, och tjänstesidorna länkade inte till ett enda uppdrag. Tre lager, i mall och innehåll: (1) En roll→tjänst-mappning (lib/roleServices.js: DevOps → DevOps, Delivery Engineer → Delivery Engineering, Fullstack/Back-end → Produktutveckling, Cloud Ops/SRE → Plattformsteknik) används åt båda hållen — rollchipsen på uppdragssidan länkar till tjänsten, och varje tjänstesida får ”Uppdrag där vi gjort det här” med de sex senaste uppdragen bemannade med dess roller plus länk till registret (drönarefoto och Developer Experience saknar roll och visar inget). ”Relaterade uppdrag” under varje vanligt uppdrag: tre syskon, samma bransch först, sedan flest delade tekniker, i samma GROQ-anrop som uppdraget; fallstudierna behåller sin redaktionella lista. Uppdragskortet utbrutet till AssignmentCard, som branschsidan nu också använder. (2) Branschintrona för SaaS & Startups och Offentlig sektor har fått en mening var som länkar de sex olänkade uppdragen. (3) Uppdragets brödtext renderas nu med samma Portable Text-renderare som blogg, bransch och tjänstesidor (transformBody + delade komponenter), så inline-markdown-länkar, fetstil och kodblock fungerar — och 46 länkar (23 per språk, 1–2 per sida) ligger nu i texterna på 15 uppdrag: tjänstesidan där texten redan nämner arbetet (”interaktiv kartfunktion”, ”releasesättningsprocessen”, ”Continuous Delivery”) och tre korslänkar mellan uppdrag med samma teknik (Kammarkollegiet → musikindustrin om Agresso, SKL → Vi-skogen om mikrotjänster, bioindustri → SKL om fältarbete). Länkarna lades in med ett skript via sanity exec som krävde att varje fras förekom exakt en gång; norska CRM-utbildningen fick ingen textlänk eftersom ingen fras passade. Uppmätt före/efter på /uppdrag/viamare: 4 → 9 länkar i huvudinnehållet, varav 5 kontextuella. Ordlistelänkning slogs inte på för uppdrag: termerna träffar bara ”CI/CD” på tre fallstudier. Ännu inte gjort: externa länkar till namngivna kunder, hero-bilder, och ett eget seoDescription-fält (meta description är i dag de första 155 tecknen, som på elva sidor nu är kundens bakgrund).
  • Småfel i de nya uppdragstexterna rättade (sv, publicerade): osynliga mjuka bindestreck (U+00AD) från skr.se borta ur SKL-sidans ”attrahera” och ”kompetensförsörjningen”; ”en annan säkerhetskoncernen” → ”en annan säkerhetskoncern” på Nordiskt bevakningsföretag; Kammarkollegiets ”finns hos oss” (myndighetens egen röst) → ”finns hos myndigheten”, och Allmänna arvsfonden med stort A.

  • Tre senaste inläggen på startsidan. Sektion före kontaktbandet (LatestPosts): kategori, titel, utdrag, datum och lästid i samma lässlistekort som /verktyg och relaterade inlägg, via PostLink (oprefixade adresser på /en). Ny LATEST_POSTS_QUERY, seedad från prerendern.
  • Siffror under heron. ”21 uppdrag · 6 branscher · sedan 2008” som en dämpad rad under knapparna, räknat ur uppdragsregistret (SITE_STATS_QUERY: antal, unika branscher, tidigaste startdatum) — inga inskrivna siffror. Uppdrag och branscher länkar till sina sidor. Seedad från prerendern; den befintliga .hero__meta-stilen används.
  • Verktygen på startsidan. Ny sektion mellan Fallstudier och Branscher (ToolsRow), så att ljusa och mörka band växlar: de fyra verktygen i en rad med namn, meta och ledtext ur samma i18n som /verktyg, plus länk dit. Statisk — ingen hämtning, inget layoutskift.
  • Fallstudier på startsidan. Två kort mellan Åtaganden och Branscher (FeaturedCases): de två första featured-uppdragen i redaktörens ordning, i samma mörka kort som /uppdrag med problem och angreppssätt, plus länk till hela registret. Ny FEATURED_CASES_QUERY, seedad från prerendern så korten finns från första renderingen.
  • Öppna flikar överlever deployer. Varje bygge bytte namn på alla chunkar (bygginfon låg inbakad i index-chunken via define, och html2pdf-chunken importerar därifrån), så en flik från förra bygget fick Failed to fetch dynamically imported module vid ”Ladda ner CV”. Två åtgärder: reloadOnStaleChunk.js lyssnar på Vites vite:preloadError och laddar om sidan en gång (spärr i sessionStorage mot omladdningsloop), och bygginfon (deploytid, commit, changelog) levereras nu som /build-info.json och /changelog.json i stället, så JS-hasharna är stabila mellan byggen med oförändrad kod, t.ex. de som Sanity-webhooken triggar. Sidfotens stämpel och /andringar seedas från prerendern via databryggan. 46 kB changelog försvann samtidigt ur huvudbundeln.
  • Lokala dev-stacken. nginx faller tillbaka på SPA-skalet för kataloger utan index.html (/anteckningsbok/ gav 403 eftersom RSS-flödet skapar katalogen), ny .dockerignore hindrar värdens node_modules från att skriva över containerns vid bygget, och efter beroendeändringar krävs podman-compose build frontend-watch frontend && podman-compose up -d --force-recreate.
  • Databryggan på fler listsidor. /anteckningsbok, /uppdrag, /ordlista och /verktyg (lässlistan) startar nu från prerenderns Sanity-svar via prerendered()/fetchSanity() i stället för tomt tillstånd, samma mönster som startsidan. Misslyckas den efterföljande hämtningen behålls det seedade innehållet. Mätt lokalt på prerendrad bundle med slow-4G/4× CPU: CLS 0 på alla sex rutter (sv + en), listorna på plats från första renderingen. Seeden väger 3–16 kB per sida. Bloggens synkrona setState i effekten togs bort på köpet (lint 26 → 24 sedan i morse).
  • Branschsidorna har fått innehåll. Ny dokumenttyp industry i Sanity (slug som matchar src/lib/industries.js, lokaliserat intro, referenser till tjänstesidor). /branscher/<slug> hämtar dokumentet vid sidan av uppdragen och visar introt plus ”Tjänster som hör hit” ovanför uppdragslistan; saknas dokument ser sidan ut som förut. Sex intron skrivna och publicerade (sv + en, 125–178 ord vardera), tre stycken per bransch: vad branschen kräver, vad vi gjort där med länkar till uppdragen, och vilka tjänster som hör dit — allt hämtat ur uppdragsbeskrivningarna, inga nya siffror. Sidorna går från 42–88 ord till runt 200. Portable Text-länkar som börjar med / renderas nu som router-länkar (håller sig på /en på engelska sidor, klientnavigering) — gäller även inläggen. Webhooken omskapad med industry i filtret. Studion deployad med typen.
  • Beskrivningar på 135–160 tecken där de var korta. Kontaktsidan har fått en egen seoDescription i i18n (sv 158, en 151) i stället för den korta ledtexten; branschsidornas mall säger nu vad sidan innehåller (”Paperphytes uppdrag inom branschen: plattformsteknik, DevOps och produktutveckling i drift”) i stället för ”Se Paperphytes uppdrag…”, 135–160 tecken för alla sex branscher på båda språken; policysidornas underrubriker i Sanity (som också är sidornas meta description) utökade till 142–160 tecken (sv + en, publicerade). Uppföljning av titelfixen mot produktion: 147 sidor i sitemap, 0 titlar över 60 tecken.
  • Titlar inom 60 tecken. Nytt fält seoTitle på blogPost (schema + sidfråga) och en regel i useSeo-hjälparen pageTitle(): ” — Paperphyte” läggs bara till när hela titeln ryms i 60 tecken, annars visas titeln utan suffix; uppdragssidor tappar först branschsegmentet, sedan suffixet. Sju inlägg vars titel ensam är över 60 tecken har fått korta seoTitle i Sanity (helgprojekt, reaktioner, agent-inlägget, Claude Design-loopen, IAM Roles Anywhere, QRGeno, plattform som produkt). Verktygens och uppdragsindexets seoTitle i i18n kortade så suffixet ryms; Plattformsteknik (en) och Delivery Engineering (sv + en) trimmade med några tecken i Sanity. Studion deployad med det nya fältet. Fallstudier visar ”Kund: fallrubrik” bara när det ryms, annars uppdragstitel + bransch. Verifierat lokalt: 16 tidigare för långa sidor ligger nu på 36–60 tecken.
  • Engelska inläggs-URL:er borta. /en/anteckningsbok/<slug> (aldrig prerendrad, men länkad 15 gånger från engelska bloggindexet, från startsidans remsa och från /en/verktyg) svarar nu 308 till /anteckningsbok/<slug> via redirects i vercel.json. Ny PostLink-komponent: på engelska sidor renderas inläggslänkar som vanliga <a href> till den oprefixade adressen (routerns /en-basename skulle annars prefixa varje <Link>), med hreflang/lang satt till inläggets språk; på svenska sidor är det fortfarande klientnavigering. Används i bloggindexet (utvalt + lista), ”Just nu”-remsan, verktygssidans lässlista och relaterade inlägg. useSeo: sidor utan språkpar (alternates: false) kanoniseras alltid till den oprefixade URL:en, så ett engelskspråkigt inlägg aldrig pekar sin canonical mot en adress som omdirigerar.
  • SEO-fixar efter genomgång av sajten. (1) Startsidans layoutskift: uppmätt CLS 0,27–0,54 på mobil (Sanity-sektionerna monterades tomma vid hydrering och ”Just nu”-remsan sköt ner allt under sig). Ny prerender-databrygga: klienten hämtar via fetchSanity, prerendern serialiserar svaren till <script id="__sanity" type="application/json"> i ögonblicksbilden, och prerendered(query) ger komponenterna samma data som starttillstånd — Services och JustNu hydreras nu med innehåll. Remsans celler har fast minsta höjd (150px) och en platshållare med samma mått när seed saknas. Uppmätt efteråt: CLS 0,000 på / och /en. (2) Mjuka 404:or: alla sju detaljsidor sätter robots: noindex, follow när slugen inte finns, och useSeo tar bort en tidigare robots-meta när nästa route inte begär någon (så en 404 inte läcker in vid klientnavigering); /_shell.html får X-Robots-Tag: noindex. (3) trailingSlash: false i vercel.json — /en/ och /konsulttjanster/ blir 308 i stället för dubbletter. (4) Säkerhetsrubriker på Vercel: X-Content-Type-Options, X-Frame-Options, Referrer-Policy (samma som i nginx.conf; CSP:n där är inaktuell — nämner fonts.googleapis men inte Matomo eller api.github.com — och kopierades inte). Bonus: PRERENDER_ONLY=/,/en renderar ett urval lokalt. Verifierat i headless Chrome mot lokalt prerendrad bundle med slow-4G/4× CPU.
  • ”Var det här användbart?” på verktyg och ordlistetermer (Useful-komponenten): en ja/nej-fråga längst ner på AI-redo-kollen (i resultatvyn), Väntetidskalkylatorn, AGENTS.md-generatorn, Repo-röntgen och varje /ordlista/<term>. Återanvänder reaktionsbackenden (api/react.js, Upstash) oförändrad: ja/nej lagras som like/dislike under namnrymda slugs (verktyg-<route>, ordlista-<slug>) så de aldrig krockar med ett inlägg — GET /api/react?slug=verktyg-repo-rontgen svarar på vilka verktyg som är värda att bygga vidare på. Ett svar per webbläsare (minns i paperphyte_reactions), byte och ångra fungerar, räknare visas, tack-rad efter svar, samma spärr på 20 anrop per IP och tio minuter. Gemensam plumbing flyttad till src/lib/reactions.js (bloggens Reactions använder samma). Cookies- och integritetspolicyn uppdaterade (sv + en, publicerade): lokal lagring och Upstash-räknarna gäller nu även verktyg och ordlistetermer; verktygslistan i cookies-policyn nämner alla fyra verktyg. Verifierat i headless Chrome mot bundlen med createHandlers och en Redis-fejk i minnet: ja → byt till nej → ångra → ja + omladdning behåller valet; bloggens reaktioner opåverkade.
  • Relaterade inlägg under varje inlägg. Tre kort längst ner (”Läs vidare / Relaterade inlägg.”) med de inlägg som delar flest taggar med det lästa, vid lika många taggar det nyaste först; inlägget självt utesluts och sektionen döljs när inga taggar delas. Ny RELATED_POSTS_QUERY (count(tags[@ in $tags]) i GROQ), korten återanvänder .tools-reading-kortet från /verktyg plus en ny metarad med datum och lästid. Svaret nyckelas på slug så gamla kort inte ligger kvar vid klientnavigering mellan inlägg.
  • Ändringssida /andringar och deploy-stämpel i sidfoten. Hela den här changeloggen visas nu på en egen sida, dag för dag med underpunkter och kodspann intakta, och sidfoten har fått en rad ”Senast deployad 5 sep. 2026 21:02 · 1934279” som länkar dit. Allt bakas in vid bygget via Vite define (scripts/build-info.js → __BUILD_INFO__, läses i src/lib/buildInfo.js): inga anrop till GitHub från besökarens webbläsare, ingen rate limit, fungerar även om repot är privat. Parsern tar ## YYYY-MM-DD, punkter och underpunkter; inline-markdown (kod, fet, länkar) blir riktiga element, inga HTML-strängar. Sidchromet är tvåspråkigt, loggen svensk. Med i prerender, sitemap och llms.txt. Saknas CHANGELOG.md i byggmiljön (t.ex. en Docker-kontext som bara innehåller frontend/) visar sidan en tom-text och stämpeln länkar ändå. Commit-hash från VERCEL_GIT_COMMIT_SHA eller git rev-parse. Ett första utkast la de tre senaste posterna som block på startsidan; det ströks — startsidan är en säljsida, och tre teasers utan länk gjorde inte loggen mer läst.
  • Alt-text på tjänstesidornas hero-bilder (Sanity, publicerat): plattformsteknik, devops, developer-experience, produktutveckling och delivery-engineering saknade alla alt-text; nu beskrivande svensk alt-text per bild (laptop med konfetti, moduler i rörledningar, idé→flöde→app, leveransloop med hjärtkurva, pipeline med tre grindar och kanariefågel). Även de sju sektionsbilderna på plattformsteknik (kuber, kluster-rutnät, vågformer med avvikelse, nätverksdiagram) och produktutveckling (passare på pappersark, MCP-nav med databas/dokument/verktyg/kod, pratbubbla med gnistor) har fått alt-text. Samtliga bilder på tjänstesidorna har nu alt-text.
  • ”Just nu”-remsa på startsidan (JustNu-komponenten, mellan hero och Åtaganden): senaste inlägget med kategori, datum, lästid och reaktionsräknare (ReactionsSummary), nyaste verktyget och senaste uppdraget — tre celler i samma 1px-rutnät som tjänstekorten, staplade under 960px. Data från en ny JUST_NU_QUERY (sex senaste inläggen + senaste uppdraget); klienten väljer första inlägget på besökarens språk och faller tillbaka på det nyaste. Verktygslistan flyttad till src/lib/tools.js med added-datum så både /verktyg och remsan läser samma lista. Renderar ingenting tills Sanity svarat, och ingenting alls om anropet misslyckas.
  • Ordlistelänkar i inlägg. Första omnämnandet av varje ordlisteterm i ett inläggs brödtext blir en prickad länk till /ordlista/<slug> med kortdefinitionen som tooltip vid hover/fokus (src/lib/glossaryLinks.js + GlossaryMark). Bara vanliga stycken och listpunkter — inte rubriker, citat, kod eller inne i befintliga länkar — och varje term högst en gång per inlägg. Termer på formen ”Namn (FÖRK)” matchar långformen eller förkortningen (förkortningen skiftlägeskänsligt); termer på fyra tecken eller mer får ett kort böjningssuffix (”golden paths”, ”plattformen”). Ordlistan hämtas en gång per sidladdning. På pekskärm finns ingen tooltip; länken går till definitionen.
  • Nytt verktyg: Repo-röntgen (/repo-rontgen + /en/repo-rontgen). Klistra in ett publikt GitHub-repo (URL, org/repo eller git-remote) och få arton kontroller i fyra grupper — onboarding (README, AGENTS.md/CLAUDE.md, CONTRIBUTING, .editorconfig, devcontainer), reproducerbart bygge (låsfil, Dockerfile/compose, Makefile/justfile/package.json, fastnålad verktygsversion), kontroller & automation (CI, pre-commit, Dependabot/Renovate, CODEOWNERS, PR-mall) och puls (licens, push senaste 90 dagarna, ingen öppen PR äldre än 30 dagar, inte arkiverat) — med poäng av 18, tre nivåer (Frisk / Under observation / Remiss), vad som letades efter per kontroll, pulsfakta och ett ”Nästa steg” som pekar på AGENTS.md-generatorn när AGENTS.md saknas och Delivery Engineering när CI saknas. Fyra anrop direkt från webbläsaren till api.github.com utan inloggning (repo, rotlistning, .github/-listning, öppna PR:ar); bara rotkatalogen och .github/ på standardgrenen läses. Delbar länk via ?repo=org/repo som kör vid sidladdning. Logiken ligger i src/lib/repoXray.js, sidan i RepoRontgenPage.jsx på .vk-*/.ark-*-chassit plus nya .rx-*-klasser. Felläge för ogiltig adress, 404, GitHubs anonyma gräns (60 anrop/timme) och nätverksfel.
    • Inkopplad på alla nio ställen: route, tools.js, i18n (rontgen, toolsPage.tools.rontgen, toolCta.rontgen; /verktyg-ledtexten säger nu ”Fyra sätt”), prerender, sitemap, llms.txt, och ToolCTA på tjänstesidan DevOps.
    • Verifierat i headless Chrome mot den byggda bundlen: startsidans remsa (sv/en, mobil), ordlistelänkar på tre inlägg (tooltip, inga länkar i rubriker eller nästlade), Repo-röntgen med inspelade GitHub-svar för vitejs/vite (12/18) samt ogiltig adress och 404. Lint: 25 fel (26 sedan tidigare, inga nya).
    • Integritetspolicyn uppdaterad i Sanity (sv + en, publicerad): nytt stycke under ”När du besöker den här webbplatsen” om att Repo-röntgen anropar api.github.com direkt från webbläsaren (fyra anrop per repo, GitHub ser IP-adress och repo, Paperphyte ser inget, inget sparas, inga anrop förrän besökaren klistrar in ett repo); GitHub, Inc. (USA) tillagd under ”Vem vi delar uppgifter med”; rättslig grund (berättigat intresse, 6.1 f) förtydligad för GitHub-anropen.

  • Hela tjänstekortet klickbart. Åtaganden-korten (startsidan och /konsulttjanster, samma Services-komponent) länkar nu i sin helhet till tjänstesidan, inte bara titeln och "Läs mer"-knappen. Stretched link-mönstret i CSS: titellänken får ett ::after-lager som täcker kortet, knappen ligger kvar ovanpå med egen hover och fokusring, och titellänken har fått en :focus-visible-ram. Ingen markup ändrad — skärmläsare och tangentbord ser fortfarande två vanliga länkar. Baksidan: texten i korten går inte längre att markera med musen.
  • Hero-ledtexten avslutas nu med "…drifta digitala produkter." — "— utan teaterföreställning." / "— without the theatre." struket ur sv/en i18n. Frasen fanns aldrig i Sanity.

  • Ny bloggpost publicerad: "Er nästa plattformsanvändare är en agent — och den läser inte wikin" (/anteckningsbok/plattformsanvandaren-ar-en-agent, kategori DX, 5 min). Tesen: en kodagent beter sig som en nyanställd på dag ett, varje dag, utan kollega att fråga — den läser bara det som finns i repot, gissar i stället för att fråga, kör kommandon i stället för att klicka och lär sig av felmeddelanden. Det gör den till den ärligaste DX-mätaren en organisation har, och inlägget beskriver vad en golden path behöver för att fungera utan tyst kunskap (kontraktsfil i repot, CI-kontroller körbara lokalt, felmeddelanden som specifikation, sandlådor med kortlivade rättigheter). Länkar vidare till golden paths-, plattform som produkt- och IAM Roles Anywhere-inläggen, AI-redo-kollen och tjänstesidan Developer Experience. Genererad hero-bild i sajtens isometriska stil med svensk alt-text.
    • Featured flyttad: den nya posten ligger nu i hero-slotten på /anteckningsbok; "Sluta skriva wiki-sidor, börja bygga golden paths" är avflaggad. Exakt ett inlägg är featured.
    • Inlägget använder inline-markdown (**fet**, [text](url)) i span-texterna, på samma sätt som Väntetidskalkylator-posten; transformBody i frontend/src/lib/portableText.jsx expanderar dem.
  • Integritetspolicy och cookies-policy uppdaterade i Sanity (sv + en, publicerade) efter genomgång mot hur sajten faktiskt beter sig:
    • Google Fonts/Google LLC struket överallt — typsnitten har varit self-hostade sedan 2026-07-25. Båda policyerna säger nu att typsnitten ligger på egen domän.
    • paperphyte_lang struket ur cookies-policyn: språket följer av URL:en (/en/), inget lagras. Endast paperphyte_consent finns i localStorage.
    • Ny cookie deklarerad: mtm_consent_removed — sätts av Matomo vid nej i cookie-rutan (forgetConsentGiven), livslängd 30 år enligt matomo.js.
    • Nytt avsnitt i integritetspolicyn: "När du skriver till oss på WhatsApp" (Meta Platforms Ireland som mottagare, metadata, alternativ). Meta tillagd under "Vem vi delar uppgifter med", liksom QRGeno (kontaktsidans QR-kod går via api.qrgeno.com som loggar skanningar innan vidarebefordran till LinkedIn).
    • Tredjepartsleverans förtydligad: matomo.js hämtas från cdn.matomo.cloud vid varje sidladdning oavsett samtycke (IP i InnoCrafts CDN-loggar, ingen spårning); bilder från cdn.sanity.io nämns bredvid apicdn.sanity.io; verktygssidorna sparar ingenting.
  • Nytt verktyg: AGENTS.md-generatorn (/agents-md-generatorn + /en/agents-md-generatorn) — nio frågor om ett repo (vad det är, kommandon, om CI:s kontroller går att köra lokalt, vägen till produktion, grenar/commits, läs-först-filer, regler, fråga-först och vad agenten får göra utan att fråga) blir en färdig AGENTS.md eller CLAUDE.md, på engelska eller svenska, att kopiera eller ladda ner. Tomma fält utelämnas ur filen; en radräknare säger till över 70 rader ("mer än en sida"). „Visa ett exempel" fyller i ett realistiskt repo på filens språk. Allt genereras lokalt, inget sparas.
    • Byggd på .vk-*-chassit från Väntetidskalkylatorn plus nya .amg-*-klasser (textfält, pill-val, förhandsvisning). Filens egna strängar ligger i koden (FILE.sv/en i AgentsMdGeneratornPage.jsx) eftersom filspråket väljs oberoende av sajtspråket; UI-copyn ligger i i18n under agentsmd.
    • Listad på /verktyg (ledtexten säger nu "Tre sätt"), i sitemap, prerender (bilingual) och public/llms.txt — som fått en ny sektion "Free tools"; verktygen saknades där helt.
    • ToolCTA på tjänstesidan Developer Experience byter från Väntetidskalkylatorn till generatorn; Plattformsteknik behåller kalkylatorn.
    • Smoke-testad i headless Chrome mot den byggda bundlen (sv/en UI, båda filspråk, båda filnamn, tre verktyg listade på /verktyg). Lint: inga nya fel (26 sedan tidigare, oförändrat).
  • Död newsletter-copy borttagen ur footer i sv/en i18n — inget formulär har någonsin renderat den.
  • Lanseringsbloggpost publicerad i Sanity ("AGENTS.md-generatorn: sidan kodagenten läser först", /anteckningsbok/agents-md-generatorn, kategori DX, flaggad showOnToolsPage) med genererad hero-bild. Agent-posten länkar nu till generatorn i steg 2 av "Så börjar du".
  • Dela-knapp på alla blogginlägg. Ny komponent SharePost längst ner i varje inlägg (rad med etiketten "Värt att skicka vidare?", knappen "Dela inlägget" och tillbaka-länken). Använder navigator.share() med inläggets titel, ingress och kanoniska URL där webbläsaren har Web Share API (mobil, Safari, Edge); annars kopieras länken till urklipp och knappen visar "Länken är kopierad" i 2,5 s. Avbrutet delningsark ignoreras tyst; blockerat urklipp faller tillbaka på execCommand('copy'). sv + en. Verifierad i headless Chrome (fallback-vägen, urklippet får https://paperphyte.com/anteckningsbok/<slug> även från /en/-URL:en, eftersom inlägg är enspråkiga).
  • Reaktioner på blogginlägg (gren feat/reactions): fyra räknare — 👍 gilla, 👎 ogilla, ❤️ älska, 👏 applådera — längst ner i varje inlägg, ovanför dela-raden. Klick räknar upp, klick igen tar tillbaka; gilla och ogilla utesluter varandra. Optimistisk uppdatering med rollback vid fel; vid 429 visas en vänlig notis.
    • Ny backend: Vercel Function frontend/api/react.js (GET ?slug= → räknare, POST {slug, kind, delta} → uppdaterade räknare) mot Upstash Redis: en hash per inlägg (reactions:<slug>, HINCRBY = atomärt), plus sorted set reactions:top med totalen per inlägg för en framtida riktig "Populärast"-sortering. Sliding-window-rate-limit 20 reaktioner per IP och tio minuter via @upstash/ratelimit; inget om besökaren lagras med räknaren. Klient och limiter är injicerbara (createHandlers) och testade mot en in-memory-fake (8 fall: validering, inkrement, take-back ej under noll, 429 per IP, 503 utan konfiguration).
    • Frontend: Reactions.jsx (visas först när räknarna laddats; döljs helt om API:et svarar fel/503), i18n blogPage.reactions*, CSS .post-reactions*. Vilka reaktioner webbläsaren gett sparas i localStorage under paperphyte_reactions — artighet, inte säkerhet.
    • E2E i headless Chrome mot byggd bundle med den riktiga handlern på fake-Redis: toggle, take-back, gilla↔ogilla, persistens efter reload, ny besökare, engelska etiketter, 429-rollback, 503 → dolt.
    • Innan merge/deploy: (1) Upstash Redis är kopplat via Vercel Marketplace; integrationen injicerar KV_REST_API_URL/KV_REST_API_TOKEN (plus KV_REST_API_READ_ONLY_TOKEN, KV_URL, REDIS_URL som inte används). Funktionen läser både de namnen och Upstashs egna UPSTASH_REDIS_REST_URL/_TOKEN; utan något av paren svarar endpointen 503 och sidan döljer knapparna, så deploy är ofarlig. Kontrollera att variablerna är aktiverade för både Production och Preview i Vercel. (2) Policyerna är uppdaterade och publicerade i Sanity: paperphyte_reactions i cookies-policyns localStorage-lista (plus ett stycke om räknarna under "Tredjepartsleverans"), och i integritetspolicyn ett stycke under "När du besöker", Upstash, Inc. (USA; databas i EU) under "Vem vi delar uppgifter med" samt missbruksskyddet i "Rättslig grund" — allt sv + en. (3) ESLint har fått ett block för api/** (Node- + webbglobaler).

  • Ny sida: Verktyg (/verktyg + /en/verktyg) — samlingssida för sajtens självbetjäningsverktyg (AI-redo-kollen och Väntetidskalkylatorn) med format, tidsåtgång och vad man får ut av vart och ett. Verktygen är kodade, inte innehåll: URL:erna ligger i TOOLS i sidan, copyn i i18n. ItemList-JSON-LD, hreflang och prerender som övriga sektionssidor.
    • Nytt fält på blogPost: showOnToolsPage (bool). Inlägg som flaggas dyker upp i sektionen "Bakgrunden till verktygen" längst ner på sidan, sorterade på publiceringsdatum. Sektionen döljs helt när inget inlägg är flaggat. Fältnamnet är camelCase eftersom det är en objektnyckel i dokumentet och i GROQ — bindestreck skulle falla på schemavalidering. Studion behöver deployas om för att växeln ska synas i redigeringsgränssnittet (cd content-studio && pnpm deploy).
    • "Verktyg" tillagd i footerns Företag-kolumn (sv + en). Huvudnavigationen är oförändrad — den ligger kvar på fem poster.
  • Nytt verktyg: Väntetidskalkylatorn (/vantetidskalkylatorn + /en/vantetidskalkylatorn) — räknar om väntan på bygget till förlorade utvecklartimmar och kronor per år. Tre reglage (utvecklare, byggen per dag, minuter per bygge) plus två redigerbara antaganden (andel av väntan som går förlorad, standard 60 %, och kostnad per utvecklartimme, standard 850 kr). Utlåtande i tre nivåer (Ni har anställt väntan / Det syns i marginalen / Det här är inte er flaskhals) med nästa steg som länkar till DeLaval-fallstudien, plattformsteknik, Developer Experience, ordlistan och AI-redo-kollen. Allt räknas lokalt, inget sparas.
    • Matematiken visas öppet på sidan ("Så räknar vi") med varje mellansteg — samma hållning som de streckade "siffra saknas"-rutorna på fallstudierna. 220 arbetsdagar och 1 600 timmar per heltidstjänst, båda avrundade nedåt.
    • Knappen "Kopiera länk med era siffror" lägger inmatningen i query-strängen (?dev=&b=&min=&spill=&kr=) så resultatet kan skickas vidare till den som äger budgeten. Sidan läser tillbaka värdena vid inläsning; ogiltiga eller orimliga värden faller tillbaka på standardvärdet.
    • ToolCTA-banner med nyckeln vantetid på DeLaval-fallstudien samt på tjänstesidorna Plattformsteknik och Developer Experience. KonsulttjanstPage har fått samma TOOL_CTA_BY_SLUG-mekanism som AssignmentPage; bannern renderas mellan tjänstebeskrivningen och processavsnittet, och bara när dokumentet är inläst.
    • CSS-sektionen /* AI-REDO-KOLLEN */ heter nu /* TOOL PAGES */; den gemensamma verktygschromen delas mellan .ark-* och .vk-* i stället för att dupliceras.
    • Sidan ingår i sitemap och prerender (bilingual; 133 rutter totalt).
    • Lanseringsbloggpost publicerad i Sanity ("Väntetidskalkylatorn: vad kostar er väntan på bygget?", /anteckningsbok/vantetidskalkylatorn, kategori DX) med genererad hero-bild. OBS: posten publicerades innan verktyget deployats — dess länkar till /vantetidskalkylatorn ger 404 tills koden är ute.
    • OBS: localhost ligger inte i projektets CORS-lista, så Sanity-drivna sidor renderar inte i utvecklingsläge utan att API-anropet proxas. Verifieringen av ToolCTA-placeringen gjordes med en sådan proxy — lägg till en dev-origin i CORS-listan om det ska gå att köra lokalt utan omvägar.

  • Nytt blogginlägg med kortkommandon för skärmdumpar

  • Nytt verktyg: AI-redo-kollen (/ai-redo-kollen + /en/ai-redo-kollen) — tolv frågor i fyra dimensioner (skyddsräcken i koden, testgrinden, reproducerbarhet, arbetssätt & ansvar) som visar om en kodbas är redo för AI-assisterad utveckling. Utlåtande (Grinden håller / glappar / saknas), dimensionsanalys och rekommendationer som länkar till QRGeno-fallstudien, tjänstesidor och ordlistan. Allt räknas lokalt; svarsordningen slumpas.
    • Ny återanvändbar komponent ToolCTA — drop-in-banner för verktyg på valfri sida (i18n-driven). Placerad på QRGeno-fallstudien.
    • Lanseringsbloggpost skriven som utkast i Sanity ("AI-redo-kollen: är er kodbas redo att koda i par med AI?", /anteckningsbok/ai-redo-kollen) med genererad hero-bild — publicera den när verktyget är deployat, annars länkar den till en 404.
    • bloggpost published

  • Fyra fullständiga fallstudier på /uppdrag/atg, /uppdrag/delaval, /uppdrag/qrgeno och /uppdrag/urb-it (sv + en), implementerade från Claude Design-utkasten "Fallstudie *".
    • Uppdrag med fallstudieinnehåll renderar nu en komplett fallstudielayout: hero med faktaruta (kund/period/roller/åtagande), innehållsförteckning, numrerade sektioner (problem, angreppssätt, beslut, utfall, teknik), mörka citatband och relaterade uppdrag.
    • Nya Sanity-typer (caseSection, caseBlock, caseItem, caseQuote, caseRelated) plus fält på assignment (caseTitle, caseLead, caseService, caseSections, caseRelated) — allt innehåll ligger i Sanity, tvåspråkigt, publicerat.
    • ATG:s teknikförteckning kompletterad med Nexus, SonarQube, Prometheus och Grafana (enligt designens anteckning).
    • Utfallsmått utan verifierad siffra visas i streckade "siffra saknas"-rutor (DeLaval har riktiga siffror: 1 000 parallella tester, 30 000 vCPU-timmar/mån, 80 000 byggen/mån). Studion behöver deployas om för de nya fälten.
  • Ny sida: Uppdrag (/uppdrag + /en/uppdrag) — hela uppdragsregistret (21 uppdrag) med filtrering på bransch och roll, fritextsök på teknik/kund, statistik i hero och en mörk "Fallstudier"-sektion med fyra utvalda uppdrag (DeLaval, QRGeno, ATG, Urb-it). Implementerad från Claude Design-utkastet "Uppdrag Index", mappad till sajtens designtokens.
    • Assignment-schemat utökat med featured, featuredOrder och fallstudiefält (caseProblem/caseApproach/caseOutcome, sv/en) — ifyllda och publicerade för de fyra utvalda uppdragen. OBS: utfallsrutorna är markerade "siffra saknas" tills faktiska siffror fylls i; Studion behöver deployas om för de nya fälten.
    • "Uppdrag" tillagd i huvudnavigationen; sidan ingår i sitemap och prerender (128 rutter) med ItemList-JSON-LD.
  • Ny sektion: Ordlista (/ordlista + /en/ordlista) — 16 tvåspråkiga "Vad är …?"-sidor om plattformsteknik, DevOps, DX och modern mjukvaruleverans (golden path, DORA, GitOps, SRE, MCP m.fl.).
    • Ny Sanity-typ glossaryTerm med term, kort definition, brödtext samt relaterade tjänster och begrepp (svaga referenser). Studion behöver deployas om för att typen ska synas i redigeringsgränssnittet.
    • Varje sida har DefinedTerm-JSON-LD, hreflang (sv/en/x-default) och länkar vidare till tjänstesidor och blogginlägg; sidorna ingår i sitemap och prerender (126 rutter totalt).
    • Ordlistan länkas från footern på alla sidor.
  • SEO-fixar, audit punkt 6–8:
    • Nya sökordsrika sidtitlar: tjänstesidorna får ett nytt seoTitle-fält i Sanity (sv/en, ifyllt och publicerat för alla sex tjänster, t.ex. ”DevOps-konsulter — CI/CD, drift & incidenter”), och sektionssidorna (konsulttjänster, konsulter, branscher, kontakt, anteckningsbok) får seo-titlar via i18n. /konsulttjanster heter inte längre ”Åtaganden” i sökresultat.
    • Ny 1200×630 og-bild (/og-default.png) för länkdelningar; loggan kvar som Organization-logotyp i JSON-LD. Konsultsidornas meta-beskrivningar förlängda (tagline + introduktion).
    • Typsnitten self-hostas nu som variabla woff2 under /fonts/ med preload — inga anrop till fonts.googleapis.com på kritiska vägen.
    • OBS: Studion behöver deployas om för att seoTitle-fältet ska synas i redigeringsgränssnittet.
  • SEO-fixar efter fullständig audit (punkt 1–5 av 8):
    • Startsidans hero-rubrik är nu sidans <h1> (båda språken) — tidigare saknade startsidan H1 helt.
    • Okända URL:er får riktig 404-status: 404-sidan prerendras till 404.html, och SPA-fallbacken pekar på ett neutralt skal (_shell.html) i stället för startsidans snapshot — inga fler soft-404:or med startsidans canonical.
    • Policysidorna (/policy/*, sv + en) prerendras och ingår i sitemapen — tidigare fick crawlers bara startsidans skal.
    • Sanity-bilder optimeras via CDN-parametrar (auto=format&fit=max&w=…&q=75 → WebP/AVIF, nedskalning) med fetchpriority="high" på herobilder och loading="lazy" under fold.
    • Hashade assets under /assets/ cachas nu immutable i ett år (tidigare max-age=0).
  • Engelska har nu egna URL:er under /en/ (t.ex. /en/kontakt) i stället för att renderas på samma URL:er som svenskan. Svenska URL:er är oförändrade och förblir huvudspråk (x-default).
    • Språket styrs av URL:en — localStorage/navigator-detektering borttagen, så sökmotorer och besökare ser samma innehåll för en given URL.
    • Alla tvåspråkiga sidor deklarerar ömsesidig hreflang (sv/en/x-default), språkspecifik canonical, og:locale:alternate och <html lang>.
    • Sitemap listar båda språkvarianterna med xhtml:link-alternates (och /kontakt som tidigare saknades); prerendern renderar alla /en-rutter.
    • Blogginlägg är fortsatt enspråkiga: ingen hreflang, canonical pekar alltid på inläggets språk-URL. Listsidan /anteckningsbok finns även som /en/anteckningsbok.
    • Språkväxlaren i menyn navigerar nu till samma sida i det andra språket.

  • Ny bloggpost publicerad: "Plattform som produkt: sluta bygga verktyg ingen vill använda" (/anteckningsbok/plattform-som-produkt), kategori Plattformsteknik.
  • Lade till markdown-arbetskopia för den tidigare publicerade posten "Ersätta AWS Access Keys med IAM Roles Anywhere": frontend/src/pages/changes.md.

The log is written in Swedish in the repo’s CHANGELOG.md and shown here as is.