Check whether your feed is valid. For more information about JSON Feed, see the specification. Find the validator source code on GitHub.
GET validation response in JSON format.
{ "version": "https://jsonfeed.org/version/1.1", "title": "Engineer — Portefølje og noter", "home_page_url": "https://engineer.company/da/", "feed_url": "https://engineer.company/da/feed.json", "description": "Engineer ApS — softwareudvikling & IT-rådgivning i København, Danmark. En portefølje af dokumenterede ingeniørresultater inden for software, data, cloud og IT.", "language": "da", "icon": "https://engineer.company/assets/images/brand/card.webp", "favicon": "https://engineer.company/assets/icons/apple/apple-touch-icon.png", "authors": [ { "name": "Engineer ApS", "url": "https://engineer.company/da/" } ], "items": [ { "id": "https://engineer.company/da/portfolio/built-a-multilingual-static-site-in-hugo-126/", "url": "https://engineer.company/da/portfolio/built-a-multilingual-static-site-in-hugo-126/", "title": "Har bygget et flersproget statisk site i Hugo på tværs af 108 skabelonfiler heraf 53 partials, der udgiver hver side i fire repræsentationer fra ét indholdstræ på tre sprog, og igen under syv fokuserede subdomæner bygget af det samme træ.", "summary": "Sitet leverer tre sprog og fire repræsentationer fra ét træ, har ingen backend at angribe og intet at fakturere, og det ene stykke JavaScript i det holdes til…", "content_html": "<p><strong>Situation.</strong> Virksomheden havde brug for et offentligt site, der virker på tre sprog, beviser formåen frem for at påstå den, intet koster at drive og ikke overlader sine besøgende til nogen. De fleste af dem er almindelige krav. Tilsammen udelukker de næsten ethvert indholdsstyringssystem, for en backend der kører, er noget der skal sikres, opdateres, betales for og forklares på en privatlivsside.</p>\n<p><strong>Opgave.</strong> Sitet skulle genereres udelukkende ved bygningstid og stadig opføre sig som et moderne — søgbart, installerbart, syndikeret, printbart og læsbart af en skærmlæser på hvert sprog det leveres i.</p>\n<p><strong>Handling.</strong> Det er et statisk site: 108 skabelonfiler, hvoraf 53 er partials og 18 er shortcodes, tre sprog, ingen backend der kører, og ét førstepartsscript givet som en afgrænset undtagelse til et porteføljefilter, hvis samtlige kontroller er skjult, indtil det kører. Formåen tilføjes ved bygningstid frem for i browseren, hvilket er en produktbeslutning og er skrevet ned som en. Hver side udgives i fire repræsentationer fra ét indholdstræ — HTML, en Markdown‑tvilling, et Gemini‑dokument og en Gopher‑menupost — hvor forsiden lægger tre syndikeringsfeeds og et manifest for en installerbar applikation oveni. Taksonomierne er bevidst to frem for én, og skelnen er bærende: en kategori er et emnemærke på arbejdet, en ydelse er noget virksomheden sælger, og at slå dem sammen ville have gjort kataloget til en liste over færdigheder i stedet for en liste over tilbud. Det samme træ bygges derefter syv gange til, én gang per fokuseret subdomæne, ved at pege generatoren på et andet indholdskatalog frem for at forgrene noget som helst.</p>\n<p><strong>Resultat.</strong> Sitet leverer tre sprog og fire repræsentationer fra ét træ, har ingen backend at angribe og intet at fakturere, og det ene stykke JavaScript i det holdes til en kontrakt, en linter håndhæver. Prisen er, at alt interaktivt skal løses ved bygningstid eller slet ikke, hvilket har udelukket flere ting, der ville have været lette med en server, og er grunden til at søgeindekset blev prissat og parkeret frem for sendt ud.</p>\n", "date_published": "2026-09-10T17:53:23+02:00", "date_modified": "2026-09-10T17:53:23+02:00", "language": "da", "tags": [ "Designsystemer & UI", "Frontend‑udvikling", "Full stack‑udvikling", "Internationalisering", "Performanceoptimering", "Webudvikling", "Full stack‑produktudvikling", "Internationalisering & lokalisering", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/built-a-22-linter-commit-gate-127/", "url": "https://engineer.company/da/portfolio/built-a-22-linter-commit-gate-127/", "title": "Har bygget en commit‑gate af 22 en‑linjes lintere plus fem, der fortjener et afsnit, uden et advarselsniveau og uden tilladte inline‑undertrykkelser, med dækning af HTML, CSS, JavaScript, Python, YAML, Markdown, shell, links, stavning, hemmeligheder og typografi.", "summary": "Mekaniske indvendinger fremsættes af en maskine, før et commit findes, og gaten er den eneste anmelder dette projekt har.", "content_html": "<p><strong>Situation.</strong> En bidragsvejledning er et sæt forslag. Alle er enige i den, og så er det fredag, ændringen er lille, og vejledningen taber. På et enmandsprojekt er det værre frem for bedre, for der er slet ingen anmelder — det eneste mellem en dårlig ændring og produktion er den person der skrev den, i det øjeblik hvor de er mindst tilbøjelige til at skændes med sig selv.</p>\n<p><strong>Opgave.</strong> Standarderne skulle være eksekverbare, så at bryde en fejler et commit frem for at vente på at blive bemærket.</p>\n<p><strong>Handling.</strong> Det der voksede frem er en gate på 22 linters uden advarselsniveau: hver diagnostik er en fejl, og sitegeneratoren selv kører med advarsler forfremmet til fejl, så selv en udfasning standser bygningen. De oplagte er der — HTML‑validering, CSS, JavaScript, Python, YAML, Markdown, shell, stavning, hemmeligheder, døde links. De interessante er de projektspecifikke tjek, som intet standardværktøj har en holdning til: at begge farvetemaer maler hver lagdelt flade med det samme antal lag, at intet fotografi vises bredere end halvdelen af sine kildepixels, at det udrullede træ ikke indeholder nogen privat sti eller værtsnavn, at teksten adlyder toneregelsættet på alle tre sprog, at en side har en Markdown‑tvilling og en gyldig repræsentation i et alternativt protokolformat. Inline‑undertrykkelser er direkte forbudt — ingen ignorér‑kommentar, intet deaktiveringsdirektiv, ingen omgåelse af hooket og ingen omdøbning af en fil for at undvige en matcher. Reglen der holder det ærligt er, at en allerede eksisterende fejl ikke er en undskyldning: et tjek der bringer en defekt frem, som ingen indførte, bliver rettet i samme omgang.</p>\n<p><strong>Resultat.</strong> Mekaniske indvendinger fremsættes af en maskine, før et commit findes, og gaten er den eneste anmelder dette projekt har. Prisen er oplyst frem for skjult: det er langsomt at committe, og en dårligt skrevet vagtpost er oprigtigt irriterende at arbejde uden om, hvilket er grunden til at vagtposterne selv senere kom under en formatter og en linter af deres egen.</p>\n", "date_published": "2026-09-10T17:53:23+02:00", "date_modified": "2026-09-10T17:53:23+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Dokumentation", "Frontend‑udvikling", "Python", "Test & QA", "DevOps & CI/CD‑automatisering", "Produktstrategi & kravspecifikation", "Teknisk dokumentation" ] }, { "id": "https://engineer.company/da/portfolio/ran-development-as-49-written-initiatives-141/", "url": "https://engineer.company/da/portfolio/ran-development-as-49-written-initiatives-141/", "title": "Har kørt sitets udvikling som 51 skrevne initiativer fordelt på 54 plandokumenter, hver med sine åbne felter, sine målinger og de beslutninger, den afviste — herunder seks af otte fund om agentparathed afvist med begrundelsen noteret.", "summary": "Enoghalvtreds beslutninger med deres begrundelse vedhæftet, omtrent tre fjerdedele lukket, og de afviste er lige så fremfindelige som de accepterede.", "content_html": "<p><strong>Situation.</strong> Et enmandsfirma har intet planlægningsmøde, ingen backlog‑gennemgang og ingen til at være uenig i en beslutning. Hvad det har i stedet er en stærk tilbøjelighed til at lave det arbejde der lige nu er interessant, og ingen optegnelse bagefter over hvorfor noget blev valgt — hvilket bliver et problem første gang en beslutning skal genovervejes eller forsvares.</p>\n<p><strong>Opgave.</strong> Sitets udvikling havde brug for en skreven optegnelse per initiativ, der overlever at blive genlæst måneder senere, og som dækker dem der blev afvist såvel som dem der blev bygget.</p>\n<p><strong>Handling.</strong> Enoghalvtreds initiativer fordelt på fireoghalvtreds nummererede og indekserede plandokumenter, hver med problemet, de foretagne målinger, beslutningen og dens åbne felter. De er ikke sammendrag skrevet bagefter. De rummer de tal der afgjorde argumentet — planlægningsmålingerne der formede tjekkøreren, billedformatet der blev testet og afvist fordi det kom ud større, prisstigen bekræftet af ejeren, auditen der fandt sitet bevise formåen på tværs af 371 sider uden at oplyse nogen pris, nogen samarbejdsform eller noget minimum nogen steder. Afviste fund får den samme behandling som accepterede: en gennemgang af agentparathed frembragte otte fund, og seks blev afvist, hver med dommen noteret, netop så et senere automatiseret forslag ikke tavst kan omgøre en skreven beslutning. Statustavlen læser en plans afkrydsningsfelter frem for dens tekst, for tekst siger “færdig” og felter siger hvad der er åbent. Den skelnen var ikke teoretisk — en dokumentationslinter fandt senere seks planer markeret som færdige, mens de bar uafkrydsede felter.</p>\n<p><strong>Resultat.</strong> Enoghalvtreds beslutninger med deres begrundelse vedhæftet, omtrent tre fjerdedele lukket, og de afviste er lige så fremfindelige som de accepterede. Hvad det koster er, at hvert initiativ har en opskrivning, hvilket er en reel skat på småt arbejde og lejlighedsvis har betydet, at optegnelsen er længere end den ændring den beskriver.</p>\n", "date_published": "2026-09-10T17:53:23+02:00", "date_modified": "2026-09-10T17:53:23+02:00", "language": "da", "tags": [ "Agile & Scrum", "Dokumentation", "Produkt & krav", "Projektledelse", "Stakeholder & rapportering", "Teknisk ledelse", "Produktstrategi & kravspecifikation", "Projektledelse (Agile)", "Teknisk dokumentation", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/put-the-documentation-under-its-own-linter-142/", "url": "https://engineer.company/da/portfolio/put-the-documentation-under-its-own-linter-142/", "title": "Har sat dokumentationen under sin egen linter — et linjebudget pr. fil, der kun skruer nedad, et indekskrav og et tjek af faktiske stier — som fandt seks af ni planer markeret som færdige, mens de bar uafkrydsede felter, og fjorten opgaver, der spærrede hver commit uden at være nævnt i noget dokument.", "summary": "Seksogtyve dokumenter på i alt omkring 2.666 effektive linjer, hvert under et budget der ikke kan vokse, med hver sti og hvert symbol de navngiver tjekket mod…", "content_html": "<p><strong>Situation.</strong> Repositoryet styrer sig selv med skrevne instruktioner — seksogtyve dokumenter der dækker kvalitetspolitikken, designsystemet, tilgængelighed, print, metadata, oversættelse og git. Instruktioner har et bestemt forfaldsmønster: de bliver længere, de driver væk fra koden, de begynder at henvise til filer der er flyttet, og ved en vis størrelse holder de op med at blive læst. En ulæst regel er ikke en regel.</p>\n<p><strong>Opgave.</strong> Dokumentationen havde brug for den samme behandling som koden — en linter med grænser der fejler frem for at råde.</p>\n<p><strong>Handling.</strong> Otte politikker, hver mekanisk. Et hårdt loft over dokumentlængde og et blødere der advarer. Et linjebudget per fil, der kun nogensinde skruer nedad, så et dokument kan skrumpe og ikke vokse tilbage. Et indekskrav over en tærskel og et afsnitsindeks over en mindre. En placeringsregel om hvilket dokument der ejer hvilket emne. Linkgyldighed. Planærlighed — den status en plan påstår, skal svare til dens egne afkrydsningsfelter. En regel om at hver opgave der spærrer et commit, er navngivet i et eller andet dokument. Og et tjek af virkelige stier, at hver katalogkvalificeret sti og hvert kodesymbol et dokument navngiver, faktisk findes. Den første kørsel var argumentet for hele øvelsen: seks planer markeret som færdige, mens de bar uafkrydsede felter, fjorten opgaver der spærrede hvert commit og var navngivet i intet dokument overhovedet, tre brudte links, ét forældreløst dokument og tre døde stier fordelt på fem dokumenter. Budgettet afviste derefter sin egen forfatter — at skrive sammendraget af dette arbejde ind i udviklingsdokumentet skubbede det over grænsen, hvilket er sådan et særskilt dokument opstod, hvilket er politikken der virker præcis efter hensigten.</p>\n<p><strong>Resultat.</strong> Seksogtyve dokumenter på i alt omkring 2.666 effektive linjer, hvert under et budget der ikke kan vokse, med hver sti og hvert symbol de navngiver tjekket mod træet. Det ubehagelige fund er det der er værd at gentage: fjorten tjek spærrede hvert eneste commit, mens de optrådte i intet dokument, så de regler maskinen håndhævede, og de regler menneskene læste, var allerede kommet fra hinanden.</p>\n", "date_published": "2026-09-10T17:53:23+02:00", "date_modified": "2026-09-10T17:53:23+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Dokumentation", "Projektledelse", "Python", "Teknisk ledelse", "Test & QA", "Projektledelse (Agile)", "Teknisk dokumentation", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/built-the-companys-infrastructure-as-code-108/", "url": "https://engineer.company/da/portfolio/built-the-companys-infrastructure-as-code-108/", "title": "Har bygget virksomhedens egen infrastruktur som 19 Ansible‑playbooks og 34 roller fordelt på 12.065 linjer YAML, der konvergerer en levende vært til en erklæret tilstand med hvert play idempotent.", "summary": "Miljøet kan reproduceres fra repositoryet, og de dele af det, der kun nogensinde var sande fordi nogen huskede dem, er nu assertions der fejler en kørsel.", "content_html": "<p><strong>Situation.</strong> Engineer ApS driver sit eget miljø — en webtilstedeværelse, en git‑forge, en database, sikkerhedskopier, mailtransport og DNS — og der var ingen at overdrage driften til. Et enmandsfirma har de samme fejltilstande som et stort og ingen af redundansen, hvilket gør det sædvanlige svar, en person der husker hvordan værten blev sat op, til den mindst tilgængelige mulighed der findes.</p>\n<p><strong>Opgave.</strong> Hele miljøet skulle beskrives i et repository frem for i et hoved, og beskrives i en form der konvergerer en rigtig vært frem for at dokumentere en.</p>\n<p><strong>Handling.</strong> Det der voksede ud af det er 19 playbooks og 34 roller fordelt på 12.065 linjer YAML. Formen betyder mere end størrelsen. Sammensætning er data frem for flag: en vært ligger i en tier‑gruppe, hvis variabler erklærer hvilke roller den kører, så provisionering uden argumenter konvergerer hver vært til dens erklærede tilstand. Idempotens er en kontrakt frem for en ambition — en konvergeret vært rapporterer nul ændringer, og et play der ikke kan sige det, er ikke færdigt. Fire kommandonavnerum holder løfterne adskilt: en gate der ikke rører nogen vært, rapporter der læser en og aldrig ændrer den, provisionering der ændrer en vært så den matcher repositoryet, og verifikation der ændrer en vært med vilje og returnerer en dom.</p>\n<p><strong>Resultat.</strong> Miljøet kan reproduceres fra repositoryet, og de dele af det, der kun nogensinde var sande fordi nogen huskede dem, er nu assertions der fejler en kørsel. Prisen er reel: hver ændring er langsommere at lave end at redigere en fil på serveren ville være, og en konvergering der halvt gennemføres er værre end en der nægter, hvilket er grunden til at et preflight‑play senere blev sat foran den. Den afvejning blev truffet bevidst, og den har holdt.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Dokumentation", "Drift & backup", "Infrastruktur", "Linux & servere", "Platformarkitektur", "Systemadministration", "Cloud‑infrastruktur & migrering", "DevOps & CI/CD‑automatisering", "Infrastructure as Code" ] }, { "id": "https://engineer.company/da/portfolio/ran-the-whole-company-on-one-512mb-host-109/", "url": "https://engineer.company/da/portfolio/ran-the-whole-company-on-one-512mb-host-109/", "title": "Har kørt hele virksomheden på én vært med 512 MB og én kerne — en git‑forge, en webserver til syv domæner, Tor, to servere til alternative protokoller, backup og udelukkelse af indtrængen — ved at behandle 464 MB brugbar hukommelse som den bindende arkitektoniske begrænsning.", "summary": "Hele virksomheden kører på en maskine der koster mindre om måneden end en frokost, og designet er bedre af disciplinen frem for blot billigere.", "content_html": "<p><strong>Situation.</strong> Virksomhedens produktionsvært er en cloud‑instans med én kerne, 512 MB hukommelse og 10 GB disk, hvoraf omkring 464 MB er anvendelige. Alt hvad forretningen kører offentligt ligger på den: webserveren der terminerer TLS for syv domæner, git‑forgen, en Tor‑onion‑tjeneste, en Gemini‑server, en Gopher‑server, krypterede sikkerhedskopier og indtrængningsblokering. Den sædvanlige reaktion på den liste er at købe en større maskine.</p>\n<p><strong>Opgave.</strong> Begrænsningen skulle behandles som et arkitektonisk input frem for et problem man bruger penge på, for det ærlige spørgsmål var ikke om en større maskine ville virke, men om designet havde brug for en.</p>\n<p><strong>Handling.</strong> Hukommelse blev det argument der afgjorde beslutninger. Der er ingen overvågningsagent, ingen metrikpipeline og intet dashboard — rapportering er et pull, syv kommandoer der læser værten og gengiver Markdown, ændrer intet og kun kører når man spørger. Supervisoren er systemd frem for endnu en proceshåndtering lagt oven på den, og containerplanet er Quadlet‑units under den samme supervisor frem for en dæmon med sin egen. Webpanel‑platforme blev udelukket på designstadiet af samme grund. Da spørgsmålet kom op om værten kunne bære en onion‑tjeneste, kom svaret fra en dags målte stikprøver frem for fra en holdning: tilgængelig hukommelse faldt aldrig under omkring 310 MB af 464, swap lå på 2,6 procent og processoren var 99,7 procent inaktiv.</p>\n<p><strong>Resultat.</strong> Hele virksomheden kører på en maskine der koster mindre om måneden end en frokost, og designet er bedre af disciplinen frem for blot billigere. Det den kostede er råderum til noget som helst skødesløst — post ligger bevidst slet ikke på denne maskine — den er skrevet som et installationsstillads, der venter på sin egen vært, fordi en mailserver kræver råderum, denne maskine allerede har brugt, og det er skrevet ned frem for opdaget senere.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Drift & backup", "Infrastruktur", "Linux & servere", "Løsningsarkitektur", "Performanceoptimering", "Platformarkitektur", "Systemadministration", "Cloud‑infrastruktur & migrering", "Infrastructure as Code", "Platform- & løsningsarkitektur" ] }, { "id": "https://engineer.company/da/portfolio/found-three-ssh-brute-force-protections-that-never-worked-110/", "url": "https://engineer.company/da/portfolio/found-three-ssh-brute-force-protections-that-never-worked-110/", "title": "Har fundet og lukket tre SSH‑beskyttelser mod brute force, der aldrig havde virket: et bandlysningsfængsel, der holdt øje med port 22, mens dæmonen lyttede på 1986, en ratebegrænsning skygget af en bredere regel over den, og en bandlysningshandling, hvis binærfil aldrig blev fundet, så ingen bandlysning nogensinde var trådt i kraft.", "summary": "Tre beskyttelser, der aldrig én eneste gang havde udløst, gør det nu, og den fejlklasse de tilhører — en kontrol hvis fejltilstand er, at den bliver ved med at…", "content_html": "<p><strong>Situation.</strong> En hærdningsgennemgang af produktionsværten i august 2026 stillede et spørgsmål, der normalt får et selvsikkert svar: virker SSH‑beskyttelserne mod brute force. Alle tre var konfigureret, alle tre optrådte i hver rapport nogen kiggede i, og alle tre havde været uvirksomme siden den dag værten blev bygget.</p>\n<p><strong>Opgave.</strong> Kontrollerne skulle efterprøves mod hvad kernen rent faktisk gør ved en pakke, frem for mod de konfigurationsfiler der beskriver hvad der burde ske med en.</p>\n<p><strong>Handling.</strong> At læse konfigurationen ville have bekræftet det forkerte svar tre gange, så gennemgangen læste det kørende system i stedet. Blokeringsfængslet holdt øje med port 22, mens dæmonen var flyttet til 1986 under den indledende hærdning — hver blokering den skrev navngav en port, som intet lyttede på. Firewallens hastighedsbegrænsning var værre på en mere subtil måde: reglen fandtes, og den lå under en bredere regel, der matchede først. Firewallens brugerregler evalueres oppefra og ned, og det første match vinder, så en bred tilladelse over en hastighedsbegrænsning gør begrænsningen til død kode, der stadig står i hver statusoversigt. Den tredje var den mest tavse af dem: blokeringshandlingen kalder ud til et pakkefilterprogram, som pakkesystemet kun anbefaler frem for kræver, så på en vært uden det starter fængslet, tæller og beslutter, og fejler så i det ene øjeblik det forsøger at blokere. Alle tre rettelser var små. Det der kom ud af det var ikke rettelsen, men to regler der nu styrer repositoryet: en sikkerhedskontrol får en assertion frem for en kommentar, og en firewall verificeres på regelplacering frem for på regelforekomst.</p>\n<p><strong>Resultat.</strong> Tre beskyttelser, der aldrig én eneste gang havde udløst, gør det nu, og den fejlklasse de tilhører — en kontrol hvis fejltilstand er, at den bliver ved med at rapportere sundt — er den klasse platformens tjek nu er bygget til at fange. Alle tre havde været uvirksomme fra bootstrap. Alle tre stod som sunde alle de steder nogen kiggede, hvilket er hele grunden til at de varede ved.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Infrastruktur", "Linux & servere", "Monitorering & observability", "Netværk & VPN", "Sikkerhed", "Systemadministration", "Netværk & VPN‑opsætning", "Sikkerhed & adgangsstyring", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/hardened-ssh-with-three-stage-validation-111/", "url": "https://engineer.company/da/portfolio/hardened-ssh-with-three-stage-validation-111/", "title": "Har hærdet SSH til 24 hævdede direktiver med validering i tre trin — kandidatfilen, den samlede konfiguration og dernæst dæmonens egen tilbagelæsning — efter at tilbagelæsningen fangede den kørende server i stilhed at tilsidesætte to af de fireogtyve.", "summary": "SSH-positionen er nu dæmonens svar frem for repositoryets påstand, og forskellen er ikke teoretisk — den var allerede to indstillinger bred, da tjekket blev…", "content_html": "<p><strong>Situation.</strong> SSH er den eneste interaktive vej ind på virksomhedens vært, og dens konfiguration skrives af en automatiseringsrolle, der havde kørt rent i måneder. En adgangsrapport i september 2026 læste dæmonens egne resolverede indstillinger og fandt to af dem i uoverensstemmelse med det, rollen havde skrevet ved hver eneste konvergering.</p>\n<p><strong>Opgave.</strong> Hærdningen skulle blive noget dæmonen bekræfter frem for noget repositoryet påstår, for afstanden mellem de to havde allerede stået åben i måneder, uden at nogen bemærkede det.</p>\n<p><strong>Handling.</strong> Årsagen var konfigurationsrækkefølge. Styresystemet leverer sine egne standarder ukommenterede, over det sted hvor en drop‑in‑fil lander, og for de to omtalte indstillinger vinder den første forekomst. Rettelsen var et filnavnspræfiks, der sorterer foran leverandørens, hvilket er en ændring på ét tegn og præcis den slags, der forbliver i stykker, fordi ingen tænker på at kigge efter. Det der blev bygget omkring den betyder mere: tre valideringstrin ved hver konvergering. Kandidatfilen syntakstjekkes før den installeres, så en ugyldig konfiguration aldrig når værten. Den samlede konfiguration tjekkes efter installationen. Derefter læses dæmonens eget resolverede output tilbage, og 24 direktiver efterprøves mod det, så en indstilling der er skrevet men overskrevet fejler kørslen. To direktiver blev bevidst udeladt, begge fordi dæmonen ikke længere implementerer dem, og at skrive dem ville kun se grundigt ud.</p>\n<p><strong>Resultat.</strong> SSH‑positionen er nu dæmonens svar frem for repositoryets påstand, og forskellen er ikke teoretisk — den var allerede to indstillinger bred, da tjekket blev skrevet. En konvergering der ikke kan se om en kontrol tog effekt, har ikke verificeret den, og at søge efter et direktiv beviser kun at det blev skrevet.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Infrastruktur", "Linux & servere", "Sikkerhed", "Systemadministration", "Test & QA", "Infrastructure as Code", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/proved-the-intrusion-banning-path-on-every-converge-112/", "url": "https://engineer.company/da/portfolio/proved-the-intrusion-banning-path-on-every-converge-112/", "title": "Har bevist hele bandlysningsstien ved hver hærdningskørsel ved at bandlyse en reserveret testadresse, læse den resulterende kerneregel og ophæve bandlysningen i en garanteret oprydningsblok, så et fængsel, der holder op med at virke, får kørslen til at fejle i stedet for at melde sig rask.", "summary": "En blokeringsvej der holder op med at virke, fejler nu en konvergering i stedet for at blive ved med at rapportere sundt, hvilket var den eneste egenskab der…", "content_html": "<p><strong>Situation.</strong> Indtrængningsblokeringstjenesten var allerede blevet grebet i at blokere en port, som SSH‑dæmonen ikke lyttede på. At rette porten lukkede det tilfælde. Det gjorde intet ved grunden til at fejlen overlevede så længe, nemlig at en blokeringsvej ikke har nogen synlig fejl: tjenesten kører, fængslet står som aktivt, og intet noget sted siger, om en blokering nogensinde når kernen.</p>\n<p><strong>Opgave.</strong> Blokeringsvejen skulle afprøves ved hver konvergering, mod det levende regelsæt, frem for udledes af at tjenesten var oppe.</p>\n<p><strong>Handling.</strong> Konvergeringen blokerer nu en adresse fra den blok, standarderne reserverer til dokumentation, læser den resulterende regel tilbage i kernens pakkefilter og ophæver blokeringen i en oprydningsblok, der kører uanset om tjekket bestod eller fejlede. Det reserverede område er det bærende valg — testadressen tilhører ingen, så en efterladt blokering, der overlever en fejlet kørsel, ikke kan lukke et rigtigt netværk ude. Tilbagelæsningen fra kernen er den anden halvdel: tjenestens egen statusudskrift ville rapportere succes for en blokering, der ikke frembragte nogen regel, hvilket er præcis den fejl der testes for. Ved siden af den er blokeringsbackenden fastlåst frem for overladt til tjenestens egen detektion, og logbackenden er sat til detektion, fordi styresystemet ikke leverer nogen traditionel autentificeringslog, og det forkerte valg fejler tavst ved at holde øje med en fil, der aldrig dukker op.</p>\n<p><strong>Resultat.</strong> En blokeringsvej der holder op med at virke, fejler nu en konvergering i stedet for at blive ved med at rapportere sundt, hvilket var den eneste egenskab der betød noget. Det koster nogle få sekunder ved hver kørsel, og det skriver og trækker en firewallregel tilbage mod produktion hver gang, hvilket er et reelt indgreb i et levende system og blev accepteret på den baggrund, at alternativet allerede var påvist at være værre.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Drift & backup", "Infrastruktur", "Linux & servere", "Sikkerhed", "Test & QA", "Sikkerhed & adgangsstyring", "Site reliability & monitorering", "Systemadministration" ] }, { "id": "https://engineer.company/da/portfolio/verified-firewall-rules-by-position-113/", "url": "https://engineer.company/da/portfolio/verified-firewall-rules-by-position-113/", "title": "Har verificeret firewallregler efter position frem for tilstedeværelse ved at læse den nummererede regelliste og den levende pakkefilterkæde, for en regel, der findes, er ikke en regel, nogen pakke når frem til.", "summary": "Firewallpositionen tjekkes nu på den måde en pakke oplever den. Det mest nyttige udfald var ikke tjekket, men hvad det afslørede om den rapportering der gik…", "content_html": "<p><strong>Situation.</strong> En firewallstatusoversigt viser et sæt regler. Et pakkefilter evaluerer en ordnet liste og standser ved det første match. Det er forskellige ting, og forskellen er usynlig i hvert eneste værktøj der udskriver et sammendrag — hvilket er sådan værten kørte et år med en hastighedsbegrænsning, som ingen pakke nogensinde nåede, liggende under en bredere regel der matchede først.</p>\n<p><strong>Opgave.</strong> Firewallverifikation skulle læse placering frem for medlemskab, for forekomst var allerede påvist ikke at bevise noget.</p>\n<p><strong>Handling.</strong> Tjekkene læser nu den nummererede regeloversigt og den levende filterkæde og efterprøver mod rækkefølge. Der findes præcis én regel per port og altid med en protokol, for en regel uden en udvider tavst overfladen. SSH‑porten bærer en hastighedsbegrænsning og aldrig en tilladelse ved siden af, eftersom en tilladelse over en begrænsning er præcis den overskygning der blev fundet. Den erklærede offentlige overflade er en kort liste på fem porte, efterprøvet som en helhed frem for tjekket enkeltvis, så en port der dukker op uden at være erklæret, fejler frem for at blive bemærket. Sikkerhedsrapporten gengiver reglerne i matchrækkefølge med en advarsel øverst i afsnittet om hvordan den læses, for den næste der åbner den fil, vil ellers læse et sæt.</p>\n<p><strong>Resultat.</strong> Firewallpositionen tjekkes nu på den måde en pakke oplever den. Det mest nyttige udfald var ikke tjekket, men hvad det afslørede om den rapportering der gik forud: hvert eneste involveret værktøj havde udskrevet hastighedsbegrænsningen i et år, korrekt, og ingen af dem var blevet stillet det ene spørgsmål der betød noget.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Infrastruktur", "Linux & servere", "Netværk & VPN", "Sikkerhed", "Test & QA", "Netværk & VPN‑opsætning", "Sikkerhed & adgangsstyring", "Systemadministration" ] }, { "id": "https://engineer.company/da/portfolio/built-encrypted-backups-with-a-monthly-restore-drill-114/", "url": "https://engineer.company/da/portfolio/built-encrypted-backups-with-a-monthly-restore-drill-114/", "title": "Har bygget krypteret backup uden for værten på restic med opbevaringsbeskæring, et integritetstjek og en månedlig automatiseret gendannelsesøvelse, og har derefter auditeret gendannelsespositionen og skrevet hullerne ned frem for at lade dem blive fundet under en hændelse.", "summary": "Virksomheden kan miste værten og få sine data tilbage, og den sætning hviler på en gendannelse der kørte sidste måned frem for på en sikkerhedskopi der kørte i…", "content_html": "<p><strong>Situation.</strong> Alt hvad virksomheden rummer — git‑repositorierne, databasen, sitet — lå på én cloud‑instans, hvis udbyder‑snapshot var hele gendannelsespositionen. Et udbyder‑snapshot er en fin ting at have og en dårlig ting at læne sig på: det ligger i den samme konto som den maskine det beskytter, det er ikke krypteret af nogen her, og ingen havde nogensinde gendannet fra et.</p>\n<p><strong>Opgave.</strong> Sikkerhedskopier skulle være krypterede, uden for værten, beskåret efter en opbevaringspolitik, og — den del der som regel springes over — faktisk gendannet fra, efter en plan, uden at en person skulle huske at gøre det.</p>\n<p><strong>Handling.</strong> Sikkerhedskopieringen kører på en systemd‑timer: et databasedump hvor et sådant findes, derefter et krypteret dedupliceret snapshot til lager hos en anden udbyder over SFTP, derefter en beskæring efter opbevaringspolitik, derefter et integritetstjek. En dead man’s switch pinges kun ved succes, hvilket er den skelnen der gør den til en alarm frem for en log — en kørsel der fejler siger intet, og at sige intet er det der udløser alarmen. Særskilt kører en gendannelsesøvelse månedligt: den trækker en kendt fil ud af repositoryet og sammenligner den, så det der tjekkes er en gendannelse frem for en sikkerhedskopi. Adgangssætningen skrives til en fil, som unitsene læser, frem for at blive sendt gennem miljøet, for supervisoren behandler undvigetegn i miljøværdier, og en adgangssætning med en omvendt skråstreg ville tavst have været en anden end den, der skabte repositoryet. Bagefter blev gendannelsespositionen auditeret og skrevet op, og de tilbageværende huller blev navngivet i dokumentet frem for efterladt til at blive opdaget under en hændelse.</p>\n<p><strong>Resultat.</strong> Virksomheden kan miste værten og få sine data tilbage, og den sætning hviler på en gendannelse der kørte sidste måned frem for på en sikkerhedskopi der kørte i nat. Auditens mest værdifulde output var listen over ting der stadig ikke er dækket, hvilket er den del en grøn sikkerhedskopirapport strukturelt er ude af stand til at fortælle nogen.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Dokumentation", "Drift & backup", "Infrastruktur", "Linux & servere", "Sikkerhed", "Backup & disaster recovery", "Infrastructure as Code", "Site reliability & monitorering", "Systemadministration" ] }, { "id": "https://engineer.company/da/portfolio/built-dead-mans-switch-monitoring-115/", "url": "https://engineer.company/da/portfolio/built-dead-mans-switch-monitoring-115/", "title": "Har bygget dødmandsknap‑monitorering, der kun pinger, mens hukommelse og disk er sunde, så en svækket vært udløser en alarm ved at gå tavs — og fangede seks variabelnavne, der sagde \"free\", hvor tjekket korrekt målte \"available\", en størrelsesorden fra hinanden på en boks med 464 MB.", "summary": "Værten har nu en alarm, hvis fejltilstand er at udløse, og den navngivning der ville have ødelagt den, er rettet.", "content_html": "<p><strong>Situation.</strong> En vært på 464 MB der kører en forge, en database og en webserver har to realistiske måder at dø på: den løber tør for hukommelse, eller den løber tør for diskplads. Ingen af dem melder sig selv. Begge er fuldt forudsigelige nogle timer i forvejen, hvis noget kigger, og intet kiggede.</p>\n<p><strong>Opgave.</strong> Værten havde brug for en alarm der virker, når værten ikke gør — hvilket udelukker alt der skal sende en besked i selve fejløjeblikket.</p>\n<p><strong>Handling.</strong> Svaret er en dead man’s switch på en timer. Hvert kvarter med jitter måler et lille script tilgængelig hukommelse og fri diskplads og pinger kun en ekstern tjeneste, hvis begge ligger over deres tærskler. Tavshed er alarmen. En maskine der er løbet tør for hukommelse, har mistet sit netværk eller er holdt op med at starte, frembringer præcis det samme signal som en der er usund, hvilket er den rigtige opførsel og grunden til at denne form blev valgt frem for en agent der rapporterer en status. Målingen er tilgængelig hukommelse, ikke fri hukommelse, og den skelnen blev til den mest lærerige del af arbejdet: scriptet havde læst den rigtige kolonne hele tiden, mens seks variabelnavne omkring det sagde “fri”. På en sund Linux‑maskine er fri hukommelse tæt på nul, fordi kernen bruger ledig hukommelse til cache, så en læser der holdt de navne op mod en tærskel på ti procent, ville se tolv megabyte fri på en maskine med 464 MB, konkludere at tjekket var i stykker, og rette det med en ændring på ét tegn, der forvandler en fungerende alarm til en der bryder permanent og bliver slået fra inden for en uge.</p>\n<p><strong>Resultat.</strong> Værten har nu en alarm, hvis fejltilstand er at udløse, og den navngivning der ville have ødelagt den, er rettet. Switchens reelle begrænsning står i dens egen dokumentation: den beviser at maskinen er sund, ikke at sitet leverer, og det er forskellige spørgsmål.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Drift & backup", "Infrastruktur", "Linux & servere", "Monitorering & observability", "Performanceoptimering", "Infrastructure as Code", "Site reliability & monitorering", "Systemadministration" ] }, { "id": "https://engineer.company/da/portfolio/made-ansible-check-mode-tell-the-truth-116/", "url": "https://engineer.company/da/portfolio/made-ansible-check-mode-tell-the-truth-116/", "title": "Har fået check mode til at sige sandheden på hele platformen efter at have fundet seks sonder, der traf beslutning på en værdi, værten aldrig gav, fordi Ansibles command‑modul melder succes under --check, mens det springer kommandoen helt over.", "summary": "En prøvekørsel måler nu enten noget eller siger at den ikke gjorde, og ingen af de to er den tredje mulighed den plejede at have.", "content_html": "<p><strong>Situation.</strong> En prøvekørsel mod produktion er ment som den sikre måde at finde ud af, hvad en ændring vil gøre. I september 2026 fejlede en prøvekørsel på en assertion, der ganske enkelt tog fejl om værten, og rådede operatøren til at sætte et flag, der ville have lempet en sikkerhedssandkasse. Prøvekørslen havde ikke læst værten. Den havde læst en værdi, værten aldrig gav, og draget en konklusion af den.</p>\n<p><strong>Opgave.</strong> Hver sonde, hvis resultat fodrer en beslutning, skulle gøres ærlig under check‑tilstand, og de der ikke kunne, skulle sige det højt frem for at forblive tavse.</p>\n<p><strong>Handling.</strong> Årsagen er en egenskab ved værktøjet, som er dokumenteret og let at glemme: kommandomodulet udføres ikke under check‑tilstand, og det den registrerer er ikke et tomt resultat — det er en succes med tomt output. Enhver betingelse der læser den registrering, beslutter derfor på en værdi der aldrig blev målt, og den beslutter i den retning dens egen logik tilfældigvis peger. En gennemgang af hver registreret sonde fandt seks af dem, hver med sin egen løgn: en rapporterede at supervisoren accepterer hvert unit‑direktiv uden at have spurgt supervisoren, en anden rapporterede intet at gøre på en vært med firewallen slået fra. Hver af dem fik en af to former. En sonde der læser eksisterende tilstand, som kørslen ikke har rørt, markeres til at køre også under check‑tilstand. En sonde der ikke kan køre, springes over, og en besked navngiver hvad der specifikt ikke blev verificeret — for tavshed i en kørselslog læses præcis som en bestået.</p>\n<p><strong>Resultat.</strong> En prøvekørsel måler nu enten noget eller siger at den ikke gjorde, og ingen af de to er den tredje mulighed den plejede at have. Den generelle regel kom i udviklingsvejledningen i samme ændring: check‑tilstand må ikke lyve, og en sonde der ikke kan se, er forpligtet til at melde sin blindhed frem for at udlede en dom af den.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Drift & backup", "Infrastruktur", "Linux & servere", "Test & QA", "DevOps & CI/CD‑automatisering", "Infrastructure as Code", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/added-a-preflight-play-for-secrets-117/", "url": "https://engineer.company/da/portfolio/added-a-preflight-play-for-secrets-117/", "title": "Har tilføjet et preflight‑play, der kører den samme kode som konvergensen mod operatørens lokale hemmeligheder på omkring et sekund, efter at en halvt anvendt produktionskørsel døde på sin niende opgave med swap‑indstillingerne allerede skrevet til den levende vært.", "summary": "En hemmelighed der ikke resolverer, koster nu en to-sekunders afvisning på en bærbar i stedet for en delvist gennemført ændring på en levende server.", "content_html": "<p><strong>Situation.</strong> Driftshemmeligheder var lige blevet delt op, så hver vært bærer sit eget sikkerhedskopi‑repository, sin egen adgangssætning og sin egen alarmswitch. Repository‑koden var korrekt. Operatørens krypterede boks holdt stadig den tidligere enkeltværtsværdi, og intet kunne se uoverensstemmelsen, for boksen er bevidst lokal for operatørens maskine og usynlig for repositoryets egen kvalitetsgate.</p>\n<p><strong>Opgave.</strong> Den fejlklasse havde brug for et sted at fejle billigt, for den havde lige fejlet dyrt.</p>\n<p><strong>Handling.</strong> Den første konvergering efter ændringen forbandt til den levende vært, kørte otte opgaver og nægtede på den niende — korrekt, men inde fra en kørsel der allerede havde skrevet swap‑indstillinger til produktion. En halvt gennemført konvergering er et værre svar end en afvisning, så et preflight‑play blev skrevet: det når slet ingen vært, kører lokalt, indsamler ingen facts og udfører de samme to opgavefiler, som den rigtige konvergering bruger, mod operatørens boks. Det svarer på omkring et sekund, og provisionering kører det først. Den bærende beslutning er, at det er den samme kode frem for en anden implementering af den samme regel, for et tjek der gentager en regel på et andet sprog, ender med at være uenig med den, og at være uenig tavst. At skrive det bragte en fælde frem, som det næsten selv skabte: facts sat under et play overlever det play, så at kæde preflight og konvergering sammen i én kørsel ville have givet den rigtige rolle en adgangssætning, som preflight allerede havde resolveret, og bestået tjekket på en boks der stadig var forkert. Det blev reproduceret før det blev afværget, i begge opgavefiler.</p>\n<p><strong>Resultat.</strong> En hemmelighed der ikke resolverer, koster nu en to‑sekunders afvisning på en bærbar i stedet for en delvist gennemført ændring på en levende server. Preflight er bevidst ikke mærket til altid at køre og bevidst ikke serialiseret, og begge dele er noteret som beslutninger frem for standarder.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Drift & backup", "Infrastruktur", "Sikkerhed", "Test & QA", "DevOps & CI/CD‑automatisering", "Infrastructure as Code", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/reconciled-a-dns-zone-declaratively-118/", "url": "https://engineer.company/da/portfolio/reconciled-a-dns-zone-declaratively-118/", "title": "Har afstemt en DNS‑zone med 20 poster deklarativt mod Cloudflares API med separate indgange til audit og BIND‑eksport, og har slået CDN‑proxyen fra igen af hensyn til privatlivet efter at have bygget den.", "summary": "Zonen er versioneret, sammenlignelig og eksporterbar, og den ene beslutning der gik imod den oplagte standard, er skrevet ned med sin begrundelse, så ingen…", "content_html": "<p><strong>Situation.</strong> Virksomhedens DNS bar omkring tyve poster fordelt på en webtilstedeværelse, et git‑underdomæne, to personlige omdirigeringer, mailrouting gennem en hosted postkasseudbyder, et transaktionsafsendelsesdomæne og autentificeringsposterne for begge. Det hele lå i en udbyders webkonsol, hvilket betyder at den aktuelle tilstand var, hvad den sidste person der klikkede havde efterladt.</p>\n<p><strong>Opgave.</strong> Zonen skulle blive til en erklæring i repositoryet, med en måde at sammenligne den erklæring med det, udbyderen faktisk leverer.</p>\n<p><strong>Handling.</strong> Zonen er skrevet som data — levende poster, applikationsposter, transportpolitikposter og en særskilt liste over poster der afventer fjernelse, hvilket er den ærlige måde at registrere en sletning der endnu ikke er sket. Ét play afstemmer den mod udbyderens API. To yderligere indgange ligger bag eksplicitte tilvalgsmærker: en audit der rapporterer forskellen uden at ændre noget, og en eksport der skriver zonen ud i det standardiserede zonefilformat, så den kan læses af noget der ikke er dette repository. Den mest interessante beslutning var en tilbagerulning. At sende de to webflader gennem udbyderens indholdsnetværk blev bygget, virkede, og blev derefter slået fra igen — for det koster den ene sætning, virksomhedens privatlivsside handler om. Med en proxy foran håndterer en anden virksomhed hver besøgendes adresse og hver URL de beder om, før vi gør, under deres politik frem for vores. For en forretning hvis kendetegnende påstand er, at ingen kigger med, er det en dårligere handel end de angreb den forsvarer imod.</p>\n<p><strong>Resultat.</strong> Zonen er versioneret, sammenlignelig og eksporterbar, og den ene beslutning der gik imod den oplagte standard, er skrevet ned med sin begrundelse, så ingen genoptager sagen ved et uheld. Auditvejen er den del der bruges mest, for at kende forskellen er oftere det man vil have end at lukke den.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "API'er & integration", "Automatisering & CI/CD", "Infrastruktur", "Netværk & VPN", "Python", "Sikkerhed", "Infrastructure as Code", "Netværk & VPN‑opsætning", "Sikkerhed & adgangsstyring", "Systemadministration" ] }, { "id": "https://engineer.company/da/portfolio/cut-systemd-sandbox-exposure-across-every-unit-119/", "url": "https://engineer.company/da/portfolio/cut-systemd-sandbox-exposure-across-every-unit-119/", "title": "Har skåret systemd‑sandkassens eksponering ned på hver eneste unit, platformen selv installerer — en dødmandsknap‑tjeneste fra 9,6 UNSAFE til 1,5, en internetvendt git‑forge fra 8,3 EXPOSED til 1,5 — og har tilføjet et parsertjek ved konvergens efter at have fundet et stavefejlsbehæftet direktiv ignoreret i stilhed i tre unit‑skabeloner.", "summary": "Hver unit kører med de privilegier den har brug for og ikke dem den ikke har, tallene er målte frem for påståede, og den fejlklasse hvor en tastefejl bliver…", "content_html": "<p><strong>Situation.</strong> Tretten unit‑filer blev installeret af dette repository og kører på produktionsværten med de privilegier, supervisoren giver som standard, hvilket er de fleste af dem. En hærdningsscore satte dead man’s switch‑tjenesten til 9,6 og kaldte den usikker; den internetvendte git‑forge scorede 8,3 og eksponeret. Begge tal var korrekte, og ingen af dem havde foranlediget noget, for en score uden en tærskel knyttet til sig er et tal, folk lærer at springe over.</p>\n<p><strong>Opgave.</strong> Hver unit havde brug for en sandkasse proportional med hvad den faktisk gør, og sandkasserne havde brug for et tjek, for fejltilstanden ved en forkert sandkasse er, at unitten starter og så går i stykker et sted, parseren ikke kan se.</p>\n<p><strong>Handling.</strong> Privilegiebegrænsninger kom ind i hver unit‑skabelon — et privilegieloft fastlåst ved unittens eget, et systemkaldsfilter, begrænsede adressefamilier og hukommelse der er skrivbar eller eksekverbar, men aldrig begge dele. De målte resultater: dead man’s switch gik fra 9,6 til 1,5, forgen fra 8,3 til 1,5, og begge sikkerhedskopi‑units fra 9,6 til 2,3 og 2,5. At skrive dem bragte noget bedre frem end scorerne. Ét direktiv havde været stavet forkert i tre unit‑skabeloner, så længe de roller havde eksisteret — et plausibelt udseende navn der ikke findes, hvilket supervisoren svarer på ved at logge at den ikke genkender nøglen og starte unitten alligevel. At søge efter et direktiv beviser at det blev skrevet; kun parseren beviser at det tog effekt. Så en verifikationsrolle kører nu hver unit gennem supervisorens egen verifikator under konvergeringen og inkluderes af hver rolle der installerer en unit. Hvad det stadig ikke kan se, står klart i de samme dokumenter: hvert direktiv der kan ødelægge disse units, ødelægger dem ved kørsel, ikke ved parsning, og en score kan ikke sige om et script stadig når sin sidste linje.</p>\n<p><strong>Resultat.</strong> Hver unit kører med de privilegier den har brug for og ikke dem den ikke har, tallene er målte frem for påståede, og den fejlklasse hvor en tastefejl bliver til en tavst fraværende kontrol, fejler nu en kørsel. Målingens grænser er noteret ved siden af den, hvilket er den del der holder den næste læser fra at overfortolke et grønt bånd.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Containere (Docker/Kubernetes)", "Infrastruktur", "Linux & servere", "Sikkerhed", "Test & QA", "Containerisering & orkestrering", "Infrastructure as Code", "Sikkerhed & adgangsstyring", "Systemadministration" ] }, { "id": "https://engineer.company/da/portfolio/built-seven-read-only-host-reporting-roles-120/", "url": "https://engineer.company/da/portfolio/built-seven-read-only-host-reporting-roles-120/", "title": "Har bygget syv skrivebeskyttede rapporteringsroller, der gengiver en levende vært som Markdown — fakta, adgang, git, metrikker, trafik, sikkerhed og udbyderinventar — under en regel om, at intet tal printes, som kørslen ikke har målt.", "summary": "Miljøet kan beskrives fra repositoryet på forlangende, og rapporterne har fundet virkelige ting: de to døde sikkerhedskontroller, en uerklæret lyttende port,…", "content_html": "<p><strong>Situation.</strong> Værten havde intet dashboard og fik ikke et, for en metrikstak passer ikke på 464 MB og ville ikke have været sin pris værd, hvis den gjorde. Det efterlod et ærligt hul: der var ingen måde at besvare spørgsmål som hvem har adgang, hvor meget er dette repository vokset, hvordan ser trafikken ud, eller er hærdningen faktisk håndhævet.</p>\n<p><strong>Opgave.</strong> De spørgsmål havde brug for svar, der kunne frembringes på forlangende, ikke kostede værten noget mens ingen spurgte, og aldrig kunne ændre det de beskrev.</p>\n<p><strong>Handling.</strong> Syv skrivebeskyttede roller, hver der gengiver en levende vært til Markdown i repositoryet. Et faktabillede der dækker styresystem, hardware, lagring, netværk, tjenester, pakker, lyttende sockets og processorudnyttelse beregnet ud fra to stikprøver af kernens egen tæller. En adgangsrapport der dækker konti, sudo‑omfang, nøglefingeraftryk, forge‑brugere og databaseroller. En git‑rapport per repository. En metrikrapport over dagens indsamlede stikprøver. En trafikrapport bygget på webserverens privatlivsmaskerede log. En udbyderoversigt på tværs af fire udbyderflader — DNS, to cloud‑API’er og et API til dedikerede servere. Og en sikkerhedsrapport der besvarer, om hærdningen er håndhævet frem for konfigureret, og udskriver firewallreglerne i matchrækkefølge og det levende blokeringsregelsæt. Den styrende regel på tværs af alle syv er, at intet tal udskrives, som kørslen ikke målte — en rapport der fylder et hul med et plausibelt tal, er værre end en der lader det stå tomt, for det er kun det plausible der bliver troet.</p>\n<p><strong>Resultat.</strong> Miljøet kan beskrives fra repositoryet på forlangende, og rapporterne har fundet virkelige ting: de to døde sikkerhedskontroller, en uerklæret lyttende port, og den iagttagelse at trafikarkivet kun er så gammelt som sidste gang nogen huskede at høste det. Den sidste er skrevet op som en begrænsning med en konkret pris noteret — seksten dages historik for ét site, der roterede væk mellem høstninger og ikke findes nogen steder.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Dokumentation", "Infrastruktur", "Linux & servere", "Monitorering & observability", "Stakeholder & rapportering", "Site reliability & monitorering", "Systemadministration", "Teknisk dokumentation" ] }, { "id": "https://engineer.company/da/portfolio/deployed-the-companys-own-git-forge-121/", "url": "https://engineer.company/da/portfolio/deployed-the-companys-own-git-forge-121/", "title": "Har udrullet virksomhedens egen git‑forge på Soft Serve, privat som udgangspunkt og uden webpanel, med SSH‑porten bundet til loopback bag en jump‑vært, og har gjort landingssiden foran den til et build‑artefakt af hovedsitet frem for en håndholdt kopi.", "summary": "Virksomheden hoster sin egen kode på sin egen hardware, og siden foran den arver hvert tjek hovedsitet består frem for at drive væk fra det på måder, kun en…", "content_html": "<p><strong>Situation.</strong> Virksomhedens kildekode lå hos en tredjeparts hostingtjeneste, hvilket er et rimeligt sted til den og en dårlig pasform for en forretning, hvis argument over for kunderne er, at den ikke overlader deres data til mellemled. At køre en forge i stedet betyder at køre en forge: autentificering, adgangskontrol, lagring, sikkerhedskopier og et offentligt ansigt til den.</p>\n<p><strong>Opgave.</strong> En kanonisk git‑server skulle rejses på den eksisterende vært, med den mindst mulige angrebsflade og intet webadministrationspanel, og en landingsside foran den, der ikke rådner.</p>\n<p><strong>Handling.</strong> Forgen er en enkelt binær overvåget af styresystemet, installeret fra leverandørens pakkerepository, bevidst valgt for at være SSH‑først uden administrativ webgrænseflade — jo mindre grænseflade, jo mindre at forsvare. Den er privat som standard: anonym adgang afvises, nøglefri adgang afvises, og hvert erklæret repository er markeret privat frem for at læne sig på uklarhed. Dens SSH‑lytter binder sig kun til loopback‑grænsefladen på port 23231 og nås udefra gennem en jump host, så firewallens erklærede overflade ikke vokser. En protokolmultiplexer, der ville lægge HTTPS og git‑SSH på én offentlig port ved at inspicere de første bytes af en forbindelse, er skrevet og klar bag en hovedafbryder, og den afbryder er slukket: git for flere brugere over den offentlige port er ikke nødvendigt endnu, og en lytter ingen bruger er overflade. Landingssiden foran den var det mere interessante problem. Den havde været en håndholdt kopi af hovedsitets design i sit eget repository, og hver eneste forskel der nogensinde blev fundet mellem de to, viste sig at være et uheld frem for en beslutning: en typeskala der gengav ordmærket omkring ni procent for stort, en fonterklæring der slog to vægte sammen til én på enhver maskine med familien installeret, ornamenter skjult under en bestemt bredde så de var fraværende på hver telefon, en vægt brugt uden en font leveret til den, og intet hovedlandmærke eller overskrift på øverste niveau på nogen side. Fem ud af fem, og ikke én synlig på et skærmbillede. Kopien blev slettet; landingssiden bygges nu af hovedsitets egne skabeloner og leveres som et artefakt.</p>\n<p><strong>Resultat.</strong> Virksomheden hoster sin egen kode på sin egen hardware, og siden foran den arver hvert tjek hovedsitet består frem for at drive væk fra det på måder, kun en måling kan se. Afvigelse er stadig mulig og skal nu skrives ned.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Infrastruktur", "Linux & servere", "Systemadministration", "Webudvikling", "DevOps & CI/CD‑automatisering", "Infrastructure as Code", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/deployed-a-container-plane-on-podman-and-quadlet-122/", "url": "https://engineer.company/da/portfolio/deployed-a-container-plane-on-podman-and-quadlet-122/", "title": "Har udrullet et containerplan på Podman og Quadlet under systemd frem for Docker, fordi Docker publicerer containerporte over værtens egne firewallregler — og har givet deploys en uprivilegeret bruger med én fast kommando i stedet for root.", "summary": "Containere kører under den supervisor, der allerede var betroet, bag den firewall der allerede var erklæret, og en rutinemæssig release kræver ingen…", "content_html": "<p><strong>Situation.</strong> Platformen havde brug for et sted at køre applikationscontainere. Det oplagte valg var branchestandarden, og det oplagte valg var forkert for denne vært på en bestemt måde: den omskriver kernens pakkefilterregler og publicerer containerporte over de firewallregler, hærdningsrollen installerer, så en container tavst bliver tilgængelig fra internettet uanset hvad firewallen fik at vide.</p>\n<p><strong>Opgave.</strong> Der skulle vælges et containerplan, som ikke går uden om firewallen, ikke tilføjer endnu en supervisor ved siden af den, der allerede var betroet, og ikke kræver at en person er root for at sende en release ud.</p>\n<p><strong>Handling.</strong> Planet er en dæmonløs containermotor, der driver units genereret af styresystemets egen supervisor. Der er ingen anden proceshåndtering: containere er tjenester, de starter som tjenester starter, og de beskrives i den samme deklarative form som alt andet. Containerne bruger værtens netværk uden nogen publicerede porte overhovedet, hvilket fjerner spørgsmålet om firewall‑omgåelse frem for at afbøde det. Units kører på systemniveau frem for rootless, og det er noteret som en afvejning — det holder automatiseringen enkel og normal, på bekostning af den strengere isolation rootless ville give, og noten siger hvilken vej man skal gå, hvis containerudbrud nogensinde kommer til at betyde mere end enkel automatisering. Udrulning blev adskilt fra provisionering: root sætter udrulningsstien op én gang, og derefter genudruller en uprivilegeret bruger ved at køre ét fast script gennem en afgrænset regel, der tillader den kommando og ingen argumenter. Byg imaget, genstart tjenesten. Ingen root‑shell, ingen vilkårlige kommandoer.</p>\n<p><strong>Resultat.</strong> Containere kører under den supervisor, der allerede var betroet, bag den firewall der allerede var erklæret, og en rutinemæssig release kræver ingen privilegeret adgang. Valget mod standarden er skrevet ned med sin begrundelse, hvilket betyder mere end valget — den næste person vil få at vide, at man skal bruge standarden, af hver eneste artikel de læser.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Containere (Docker/Kubernetes)", "DevOps", "Infrastruktur", "Linux & servere", "Sikkerhed", "Cloud‑infrastruktur & migrering", "Containerisering & orkestrering", "DevOps & CI/CD‑automatisering", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/provisioned-a-second-server-from-code-123/", "url": "https://engineer.company/da/portfolio/provisioned-a-second-server-from-code-123/", "title": "Har automatiseret provisioneringen af en anden server hos en anden cloududbyder og oprettet firewallen før maskinen, så den fødes bag en, med begge udbyderes firewalls skrevet direkte mod deres REST‑API'er for at undgå en tredjepartssamling.", "summary": "En anden vært kan oprettes, eller genadopteres, fra repositoryet, bag en firewall der allerede findes, til en pris filen oplyser.", "content_html": "<p><strong>Situation.</strong> Der var brug for endnu en maskine til et applikationslag, hos en anden udbyder end den første, og den første maskines egen historie var argumentet for hvordan: den var blevet oprettet i hånden, og dens firewall var blevet tilføjet bagefter, hvilket efterlader et vindue, hvor en frisk vært med en standardadgangskodepolitik er tilgængelig fra internettet.</p>\n<p><strong>Opgave.</strong> Maskinen skulle oprettes fra repositoryet, og den skulle fødes bag sin firewall frem for at få en kort tid efter.</p>\n<p><strong>Handling.</strong> Rækkefølgen bar designet. Firewall‑playet kører før server‑playet, så regelsættet findes, før der er noget for det at beskytte; hos den anden udbyder kører tagging før cloud‑firewallen, fordi en firewall der retter sig mod tags har brug for, at taggene findes først. Begge udbyderes firewalls skrives direkte mod deres REST‑API’er gennem en generisk HTTP‑opgave frem for gennem en leverandørsamling, hvilket fjerner en afhængighed og dens versionsdrift på bekostning af at skrive forespørgselsformerne i hånden. Den rolle der opretter maskinen, adopterer en eksisterende frem for at duplikere den, hvis den allerede er der, så genkørsel er sikker. Den er også dokumenteret, i sin egen fil, som den ene rolle i repositoryet der bruger penge — instanstypen, regionen, specifikationen og den månedlige omkostning i euro står alle skrevet, for et play der sender nogen en regning, bør sige det, hvor de vil læse det. Det play er også det, der ikke kan afprøves på den sædvanlige måde: den generiske HTTP‑opgave erklærer ingen understøttelse af check‑tilstand, så en prøvekørsel springer hver opgave i den over. Afprøvningen sker mod firewall‑playet i stedet, hvilket er gratis og kan rulles tilbage.</p>\n<p><strong>Resultat.</strong> En anden vært kan oprettes, eller genadopteres, fra repositoryet, bag en firewall der allerede findes, til en pris filen oplyser. Den ene ting prøvekørsel ikke kan dække, er navngivet i den samme fil frem for efterladt som en overraskelse.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "API'er & integration", "Automatisering & CI/CD", "Cloud", "Infrastruktur", "Python", "Sikkerhed", "Cloud‑infrastruktur & migrering", "Infrastructure as Code", "Netværk & VPN‑opsætning", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/wrote-a-scope-rule-after-a-credential-leak-124/", "url": "https://engineer.company/da/portfolio/wrote-a-scope-rule-after-a-credential-leak-124/", "title": "Har skrevet en scoperegel ind i kodebasen, efter at en omstrukturering bar en anden virksomheds inventar, firewalltilladelser og prosa med ind i den — og har holdt det karantæneramte restmateriale under hemmelighedsscanneren frem for at udelukke det.", "summary": "Omfangsgrænsen er en skreven regel med en oplyst test, resterne er synlige frem for begravet, og den konkrete form for legitimationsoplysning der slap igennem,…", "content_html": "<p><strong>Situation.</strong> Arbejde udført for en anden virksomhed kørte med gennem en omstrukturering af repositoryet og blev liggende. Det der fulgte med var ikke abstrakt: en kladdelog med levende legitimationsoplysninger i klartekst, som lå i træet forbi hver eneste vagtpost gennem det meste af repositoryets historie; en forældet inventarliste der navngav den virksomheds vært; og en firewall‑opgavefil der åbnede porte til seksten af dens kundenetværk. Intet af det kørte. Det er præcis derfor det overlevede — noget der ikke kører nogen steder, bliver aldrig gennemgået igen.</p>\n<p><strong>Opgave.</strong> Grænsen skulle blive til en regel med en test knyttet til sig frem for en hensigt, og resterne skulle håndteres på en måde, der ikke blot skjulte dem.</p>\n<p><strong>Handling.</strong> Reglen er nu åbningsafsnittet i repositoryets instruktionssæt: dette repository håndterer én virksomheds infrastruktur og kun den, og en anden parts vært, inventarliste, firewalltilladelse, DNS‑zone eller legitimationsoplysning hører ikke hjemme i det — ikke engang deaktiveret, udkommenteret eller parkeret i en fil, ingen playbook importerer. Den praktiske test står ved siden af: ville denne virksomhed stadig være ansvarlig for dette, hvis forholdet ophørte. Resterne blev sat i karantæne i et tydeligt navngivet katalog frem for slettet, så historikken forbliver læselig, og karantænen er bevidst delvis — de to strukturelle linters springer den over, og hemmelighedsskanneren, boksvagten og emoji‑tjekket læser den bevidst stadig, for det er de tre, der ville fange det, der slap ind. Selve lækagen frembragte en linterregel: et brugerdefineret mønster der markerer en legitimationsoplysning sendt som et kommandolinjeflag, hvilket er den form den lækkede havde, og som standardregelsættet ikke matchede.</p>\n<p><strong>Resultat.</strong> Omfangsgrænsen er en skreven regel med en oplyst test, resterne er synlige frem for begravet, og den konkrete form for legitimationsoplysning der slap igennem, fejler nu et commit. Læren noteret ved siden af er den generelle: det farlige artefakt er ikke det der kører, det er det der ikke gør, for det er det, ingen læser igen.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Data governance", "Dokumentation", "Infrastruktur", "Sikkerhed", "Data governance & datakvalitet", "Sikkerhed & adgangsstyring", "Teknisk dokumentation" ] }, { "id": "https://engineer.company/da/portfolio/split-every-operational-secret-per-host-125/", "url": "https://engineer.company/da/portfolio/split-every-operational-secret-per-host-125/", "title": "Har delt de fire hemmeligheder, der hører til den enkelte vært, efter at have fastslået, at to værter, der deler én dødmandsknap, alarmerer mindre end to knapper, ikke mere, og at en delt backup‑adgangssætning gør to værter til ét repository.", "summary": "Hemmeligheder er per vært ved konstruktion, og de to fejltilstande, der ville være fulgt af at dele dem, er skrevet ned, hvor den næste person redigerer filen.", "content_html": "<p><strong>Situation.</strong> Platformen var bygget til én vært og var ved at få to. Flere driftshemmeligheder var skrevet som enkeltværdier på den antagelse: ét sikkerhedskopi‑repository, én adgangssætning, én alarmswitch. At udvide dem til en anden vært ved at dele dem er vejen med mindst modstand, og den er forkert på to adskilte måder, som begge fejler tavst.</p>\n<p><strong>Opgave.</strong> Hver hemmelighed skulle blive til et opslag med værten som nøgle, og begrundelsen skulle skrives ned, for den delte udgave ser korrekt ud og koster intet, indtil den dag det betyder noget.</p>\n<p><strong>Handling.</strong> To argumenter afgjorde det, og begge er noteret ved siden af konfigurationen. En dead man’s switch delt af to værter alarmerer mindre end to switches, ikke mere: hvis blot den ene vært stadig pinger, holder den tjekket grønt mens den anden er død, så at føje en vært til en delt switch reducerer aktivt dækningen for den, der allerede var der. Og en sikkerhedskopi‑repositorystreng er kun en placering — adgangssætningen er hele krypteringen, så to værter der deler en adgangssætning, er ikke to repositorier med en fælles hemmelighed, de er ét repository med to kataloger i sig. Hver værdi blev til et opslag med inventarværtsnavnet som nøgle, resolveret per vært af en delt opgavefil, som både konvergeringen og preflight kører. Den eksisterende vært blev derefter navngivet i alle fire opslag med sit repository og begge switches efterladt tomme, hvilket er den sandfærdige tilstand og holder de to åbne initiativer ærlige om, at de skylder operatøren halvdelen af arbejdet frem for koden.</p>\n<p><strong>Resultat.</strong> Hemmeligheder er per vært ved konstruktion, og de to fejltilstande, der ville være fulgt af at dele dem, er skrevet ned, hvor den næste person redigerer filen. De tomme poster er den del, der er værd at beholde: de siger, at ledningsføringen findes og værdien ikke gør, hvilket er et andet og mere nyttigt udsagn end en manglende nøgle.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Drift & backup", "Infrastruktur", "Linux & servere", "Løsningsarkitektur", "Sikkerhed", "Backup & disaster recovery", "Infrastructure as Code", "Sikkerhed & adgangsstyring", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/cut-the-visual-quality-gate-to-ten-minutes-128/", "url": "https://engineer.company/da/portfolio/cut-the-visual-quality-gate-to-ten-minutes-128/", "title": "Har skåret sitets browserdrevne kvalitetsgate fra 1.636 sekunder til 615 ved at planlægge dens tjek længst‑først gennem en worker‑pulje begrænset til fire baner, efter at have målt at alfabetisk rækkefølge kostede 320 sekunder mod 224.", "summary": "Gaten gik fra 1.636 sekunder til 615, mens tjekkene blev bredere frem for tyndere — den serielle omkostning steg, og den samlede tid faldt.", "content_html": "<p><strong>Situation.</strong> Elleve af sitets tjek driver en browser uden grænseflade: layout ved hver vinduesform designet tegner, typeskala, udvidelse af oversat tekst, kontrast, tilgængelighedsregler, tvungne farver, maskotstørrelse, bevægelse, print på tværs af seks papirkombinationer, konsolfejl og visuel regression. Kørt én ad gangen tog de 1.636 sekunder, lidt over syvogtyve minutter. En gate der tager syvogtyve minutter er en gate der bliver sprunget over, og et oversprunget tjek kan ikke skelnes fra et der består.</p>\n<p><strong>Opgave.</strong> Den samlede tid skulle ned langt nok til, at det at køre dem var standarden frem for en beslutning, uden at svække nogen af dem.</p>\n<p><strong>Handling.</strong> Arbejdet var måling først. Hvert tjek blev tidsmålt enkeltvis på en maskine med tolv kerner: layout på 405 sekunder, type på 301, oversættelse på 282, kontrast på 189, tilgængelighed på 178, og så videre ned til sytten. To fund formede svaret. At køre dem alle på én gang var langsommere end at køre fire ad gangen — 265 sekunder mod 224 — for hvert tjek er selv en browser der arbejder parallelt, og at overbelaste maskinen koster mere, end samtidigheden vinder. Og at ordne efter længste behandlingstid først slog alfabetisk rækkefølge med næsten en tredjedel, 224 sekunder mod 320, hvilket er det klassiske planlægningsresultat og viser sig her, fordi tjekkene varierer med en faktor tyve i omkostning. Så kørslen er en afgrænset arbejderpulje, dimensioneret ud fra kerneantallet med et gulv på to og et loft på fire, fodret med det længste først. Ved siden af den blev tjekkene udvidet frem for indsnævret: de deler nu én viewport‑tabel med toogtyve vinduesformer, udledt af hver medieforespørgsel stylesheetet faktisk indeholder, hvilket tog layouttjekket alene fra 95 sekunder til 405.</p>\n<p><strong>Resultat.</strong> Gaten gik fra 1.636 sekunder til 615, mens tjekkene blev bredere frem for tyndere — den serielle omkostning steg, og den samlede tid faldt. Målingen er den del der er værd at beholde: to fornuftigt lydende valg, at køre alt på én gang og at køre tingene i den rækkefølge de blev skrevet, var hver især målbart dårligere end alternativet.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Frontend‑udvikling", "Performanceoptimering", "Python", "Test & QA", "DevOps & CI/CD‑automatisering", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/replaced-27-font-sizes-with-a-six-step-scale-129/", "url": "https://engineer.company/da/portfolio/replaced-27-font-sizes-with-a-six-step-scale-129/", "title": "Har erstattet 27 gengivne skriftstørrelser, hvis nærmeste naboer lå 0,6 % fra hinanden, med en seks‑trins Major Third‑skala og har skrevet den linter, der fejler på den otteogtyvende.", "summary": "Seks størrelser hvor der var syvogtyve, med et oplyst forhold, en oplyst tekstbredde og et tjek der afviser den næste uplanlagte værdi.", "content_html": "<p><strong>Situation.</strong> En måling af sitets gengivne tekst fandt syvogtyve forskellige skriftstørrelser i brug. Flere var adskilt af mindre end én procent — fem værdier mellem 0,8 og 0,85 af brødteksten var alle i live på samme tid, hvilket er en forskel ingen læser kan opfatte, og enhver fremtidig redaktør vil lægge til. To af overskrifterne blev slet ikke størrelsessat af stylesheetet og faldt igennem til browserens egne standarder, et forhold på 1,33 der ikke matchede noget andet på siden.</p>\n<p><strong>Opgave.</strong> Størrelserne skulle blive til en skala med et oplyst forhold, og noget skulle forhindre den otteogtyvende.</p>\n<p><strong>Handling.</strong> Skalaen er en stor terts, forhold 1,25, seks navngivne trin fra finskrift til display, hvert et token frem for en værdi. Overskrifter er eksplicit knyttet til trin i stedet for at arve hvad browseren nu mener, hvilket er det der rettede de to, som ikke havde nogen størrelse af deres egen. Kun det nederste trin har et gulv, så finskrift forbliver læselig på en telefon uden at hele skalaen er låst fast. Logotyper er undtaget ved navn frem for ved et tilfælde. Linteren er den del der får det til at holde: den gengiver sitet og fejler på den otteogtyvende forskellige størrelse. Den har allerede vist sit værd to gange — den fangede en overskrift der ankom på 1,17 af brødteksten, hvilket ikke er et trin i noget som helst, og navngav forholdet i fejlbeskeden; og den fangede inline‑kode på 0,9, hvilket er præcis den slags værdi skalaen findes for at forhindre. Sammen med skalaen kom en tekstbredde på omkring tooghalvfjerds tegn, der erstattede en nedarvet fast bredde, som frembragte enoghalvfems tegn på en anmeldelsesside og hundrede og ti på kontaktsiden.</p>\n<p><strong>Resultat.</strong> Seks størrelser hvor der var syvogtyve, med et oplyst forhold, en oplyst tekstbredde og et tjek der afviser den næste uplanlagte værdi. Begrænsningen er reel og lejlighedsvis ubelejlig: et design der vil have en størrelse mellem to trin, må flytte sig til et trin eller argumentere for at ændre skalaen, og det argument er blevet ført og tabt mere end én gang.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Designsystemer & UI", "Dokumentation", "Frontend‑udvikling", "Test & QA", "UX/UI‑design", "Brand, marketing & SEO", "UI/UX‑design & designsystemer" ] }, { "id": "https://engineer.company/da/portfolio/took-wcag-2-2-level-aa-across-the-whole-site-130/", "url": "https://engineer.company/da/portfolio/took-wcag-2-2-level-aa-across-the-whole-site-130/", "title": "Har taget WCAG 2.2 niveau AA på tværs af 32 repræsentative sider — én pr. skabelon pr. sprog — i begge farvetemaer, plus to AAA‑kriterier, med et dokumenteret konformitetsnotat og et tjek, der forsvarer hver påstand.", "summary": "Overholdelse påstås på niveau AA på tre sprog og i to temaer, med to AAA-kriterier derudover, og hver påstand har et tjek bag sig.", "content_html": "<p><strong>Situation.</strong> Et konsulenthus der sælger ingeniørmæssig dømmekraft og leverer et utilgængeligt website, har et troværdighedsproblem, før det har et tilgængelighedsproblem. Sitet har også en usædvanligt bred flade til det: tre sprog, to farvetemaer, et fuldt printstylesheet, en mørk‑først‑palet, håndtegnede annotationer og en illustreret figur — hver eneste af dem er en måde at fejle et kriterium i én opsætning og bestå det i en anden.</p>\n<p><strong>Opgave.</strong> Sitet skulle overholde WCAG 2.2 på niveau AA på tværs af hver side, hvert sprog og begge temaer, og overholdelsen skulle forsvares af tjek frem for påstås i et dokument.</p>\n<p><strong>Handling.</strong> Et konformitetsnotat dokumenterer hvert kriterium i omfanget, hvor hver række navngiver hvad der opfylder det, og hvad der tjekker det. To kriterier på niveau AAA tages ud over målet — visuel præsentation i fuld udstrækning, og fokusfremtoning, som tilbydes frem for påstås. Automatiseringen kører en tilgængelighedsregelmotor med dens bedste praksis‑regler slået til frem for kun konformitetsreglerne, hvilket er en forskel på tredive regler og betød noget med det samme: en af de ekstra regler fejlede i det øjeblik den blev slået til, fordi mærket lå uden for hvert landmærke på hver indre side. Et andet, langsommere tjek kører toogtredive repræsentative sider på tværs af to farveskemaer og to bredder, og det det efterprøver er usædvanligt: fokus bevises i pixels frem for i dokumentet, ved at trykke på tabulatortasten for alvor og sammenligne skærmbilleder, for identiske pixels betyder, at brugeren ikke kan se hvor fokus er, uanset hvad markuppen siger. Det gennemgår også hele siden på jagt efter en tastaturfælde, anvender de angivne tekstafstandsoverstyringer, fordobler rodskriftstørrelsen og måler linjelængde, lige margener, centrering og afsnitsafstand.</p>\n<p><strong>Resultat.</strong> Overholdelse påstås på niveau AA på tre sprog og i to temaer, med to AAA‑kriterier derudover, og hver påstand har et tjek bag sig. Hvad sitet siger om det på sin egen kreditside, er den ærlige del: intet automatisk værktøj finder mere end omkring en tredjedel af WCAG‑fejlene, og der har ikke været testet med rigtige hjælpemidler — den grænse står skrevet, hvor en læser vil se den, frem for udeladt af påstanden.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Designsystemer & UI", "Dokumentation", "Frontend‑udvikling", "Internationalisering", "Test & QA", "UX/UI‑design", "Webudvikling", "Internationalisering & lokalisering", "UI/UX‑design & designsystemer", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/fixed-11-accessibility-defects-the-browser-called-healthy-131/", "url": "https://engineer.company/da/portfolio/fixed-11-accessibility-defects-the-browser-called-healthy-131/", "title": "Har fundet og rettet 11 tilgængelighedsfejl, som browseren meldte sunde — otte footer‑links stadig i tab‑rækkefølgen bag pointer‑events, en scroll‑tidslinje, der klippede kolofonen af tre sider, og en regelmotor, der kørte 70 af sine 105 regler.", "summary": "Elleve defekter rettet og, mere nyttigt, fire regler der overlever dem: en vagtpost der tester én akse, forsvarer én akse, et regelsæt der ikke indeholder…", "content_html": "<p><strong>Situation.</strong> Sitet bestod sine automatiske tilgængelighedsregler. Det havde også elleve defekter, som de regler ikke kunne se, fordi hver af dem var en egenskab ved hvordan siden blev gengivet frem for ved hvad markuppen sagde — den slags som en validator rapporterer som sund, og som en tastaturbruger rammer inden for få sekunder.</p>\n<p><strong>Opgave.</strong> Defekterne skulle findes, rettes og skrives op som et regnskab med den regel hver af dem frembragte, så defektklassen lukkes frem for det enkelte tilfælde.</p>\n<p><strong>Handling.</strong> De faldt i tre grupper. Størrelse: et stylesheet‑nøgleord brugt som var det relativt, låste et helt område til seksten pixels, mens brødteksten løb op til toogtyve; et sænket skrift‑element blev brugt til at betyde “mindre”; at formindske tekst forlængede en linje, indtil en titel løb til seksoghalvfems tegn; og et målt højdeloft blev overskredet af netop den tekstafstandsoverstyring, sitet påstår at understøtte. Fokus og beskæring: otte fodlinks blev i tabulatorrækkefølgen bag en egenskab, der fjerner pegeinteraktion og intet andet; en overløbsregel skabte en rullebeholder, ingen ville have; og en rulledrevet animation, som ganske enkelt er inaktiv på en side der er for kort til at rulle, beskar permanent foden af tre sider. Og to defekter i vagtposterne selv, som er dem der er værd at navngive — hver vinduesform layouttjekket åbnede, var ni hundrede pixels høj, så en telefon i liggende format på 852 gange 393 gav syv pixels mellem to elementer, og intet tjek havde nogensinde kigget; og regelmotoren havde kørt halvfjerds af sine hundrede og fem regler, så den regel der ville have fanget det manglende landmærke, var ikke i sættet.</p>\n<p><strong>Resultat.</strong> Elleve defekter rettet og, mere nyttigt, fire regler der overlever dem: en vagtpost der tester én akse, forsvarer én akse, et regelsæt der ikke indeholder reglen, kan ikke håndhæve den, højde er en dimension så test den, og bevis synlighed i pixels frem for i dokumentet. Da layouttjekket blev åbnet ordentligt igen, fejlede det hundrede og otteogtyve gange på tværs af tre sprog, herunder et telefonstort vindue hvor forsidens afsluttende linje lå enogtyve pixels under kanten.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Designsystemer & UI", "Frontend‑udvikling", "Test & QA", "UX/UI‑design", "Webudvikling", "UI/UX‑design & designsystemer", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/generated-321-achievement-pages-from-a-sqlite-export-132/", "url": "https://engineer.company/da/portfolio/generated-321-achievement-pages-from-a-sqlite-export-132/", "title": "Har genereret 495 achievement‑sider på tre sprog fra en skrivebeskyttet SQLite‑eksport, med sidens adresse forfattet som data, så en rettet sætning ikke længere flyttede siden og brød linket.", "summary": "Fire hundrede og femoghalvfems sider genereres fra én kilde, at rette et udsagn koster intet, og de adresser dette site nogensinde har udgivet, svarer fortsat.", "content_html": "<p><strong>Situation.</strong> Virksomhedens porteføljeindhold skrives i en særskilt database — den samme der frembringer CV’et — og websitet skal udgive det som sider, på tre sprog, uden at de to kopier driver fra hinanden. Den naive tilgang, at skrive siderne i hånden og holde dem i takt, fejler ved den første rettelse.</p>\n<p><strong>Opgave.</strong> Websitet skulle generere sit indhold fra databasen som et byggeinput, med sideadresser der overlever, at sætningerne på dem bliver skrevet om.</p>\n<p><strong>Handling.</strong> En eksportør læser databasen skrivebeskyttet og skriver én side per achievement per sprog — 495 sider — plus de taksonomi- og ydelsesdata skabelonerne har brug for. Den bruger kun standardbiblioteket, så sitets bygning ikke afhænger af generatorens miljø, og det committede output betyder, at sitet bygger selvstændigt. Den mest konsekvensrige beslutning i den handler om adresser. Sitet plejede at udlede en sides URL fra de indledende ord i dens engelske udsagn, så at rette en sætning flyttede tavst siden og ødelagde hvert link til den — et site hvis argument for sig selv er, at det retter ting, og som opkrævede sig selv et dødt link, hver gang det gjorde. Adressen skrives nu som data: én række per adresse per achievement, den første er den aktuelle, og hver senere er en pensioneret adresse, sitet udsender som en omdirigering. To andre fælder er noteret fra det samme arbejde. En periodesammenligning mod en tekstkolonne matchede tavst alle toogtredive rækker på grund af, hvordan databasen tildeler typeaffinitet på tværs af en sammenligning. Og sproglisten er nu den ene akse, som skriveløkken, datafilerne og forespørgslerne alle udledes af, så at tilføje et fjerde sprog er én opslagspost frem for en søgning.</p>\n<p><strong>Resultat.</strong> Fire hundrede og femoghalvfems sider genereres fra én kilde, at rette et udsagn koster intet, og de adresser dette site nogensinde har udgivet, svarer fortsat. Eksportøren ejer også præcis én præsentationsbeslutning — hvordan ydelser grupperes i tematiske afsnit — og det er bevidst: alt andet den skriver, tilhører databasen, og en gruppeoverskrift der mangler et sprog, fejler eksporten frem for at gengive engelsk oven på oversat indhold.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Databaser", "Datapipelines (ETL/ELT)", "Frontend‑udvikling", "Internationalisering", "Python", "SQL", "Webudvikling", "Brand, marketing & SEO", "Internationalisering & lokalisering", "Udvikling af datapipelines (ETL/ELT)", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/closed-the-colour-system-at-28-colours-133/", "url": "https://engineer.company/da/portfolio/closed-the-colour-system-at-28-colours-133/", "title": "Har lukket farvesystemet ved 28 dokumenterede farver med en linter, der fejler på en værdi, der er malet men udokumenteret, dokumenteret men umalet, fejlmålt, omstavet som en literal eller inden for en perceptuel afstand på 0,02 fra en, der allerede findes.", "summary": "Paletten er et lukket, målt sæt, der ikke tavst kan vokse, og de to næsten enslydende stavemåder, der foranledigede den, er væk.", "content_html": "<p><strong>Situation.</strong> Farve på et site med en mørk standard, et lyst tema, et printstylesheet, en tvungne farver‑tilstand og et illustreret mærke forbliver ikke et lille sæt af sig selv. Det var allerede begyndt at drive på den måde det altid gør: to stavemåder af den samme farve liggende tæt nok på, at ingen kunne skelne dem, værdier gentastet som literaler ved siden af de tokens der definerede dem, og dokumenterede farver som intet malede.</p>\n<p><strong>Opgave.</strong> Paletten skulle blive et lukket sæt med en oplyst tærskel for særpræg, og noget skulle håndhæve lukningen.</p>\n<p><strong>Handling.</strong> Otteogtyve farver, hver dokumenteret med det kontrastforhold den måler på den flade den optræder på, og elleve mærkeværdier erklæret én gang som tokens. Tærsklen er talmæssig frem for redaktionel: to farver tættere på hinanden end en perceptuel afstand på 0,02 i et ensartet farverum er én farve med to stavemåder. Linteren fejler på fem forskellige måder — en værdi malet men udokumenteret, dokumenteret men umalet, noteret med det forkerte forhold, inden for tærsklen af en der allerede findes, eller en mærkeværdi gentastet som en literal. Den fandt to med det samme: en temafarve der fandtes i to stavemåder 0,018 fra hinanden, og en baggrundsfarve der stod for en væg, den lå 0,021 fra. Gennemsigtighed bruger relativ farvesyntaks frem for blandingsfunktionen, netop fordi vagtposten ikke kan se gennem en blanding, og en paletvagt der kan omgås, er ikke en vagt. Reglen der følger med er kort: ret et token frem for en regel, tilføj kun en farve når ingen passer, og sænk aldrig et dokumenteret forhold for at få et design til at virke.</p>\n<p><strong>Resultat.</strong> Paletten er et lukket, målt sæt, der ikke tavst kan vokse, og de to næsten enslydende stavemåder, der foranledigede den, er væk. Det er en begrænsning der lejlighedsvis siger nej — et design der vil have en lidt anden blå, må tage den der findes eller føre sagen for en niogtyvende farve, og den sag skal indeholde forholdet.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Designsystemer & UI", "Dokumentation", "Frontend‑udvikling", "Test & QA", "UX/UI‑design", "Brand, marketing & SEO", "UI/UX‑design & designsystemer" ] }, { "id": "https://engineer.company/da/portfolio/found-the-light-theme-missing-an-overlay-layer-134/", "url": "https://engineer.company/da/portfolio/found-the-light-theme-missing-an-overlay-layer-134/", "title": "Har fundet, at det lyse tema havde manglet et fuldsides overlay‑lag siden den dag, det blev skrevet, ved at hævde at begge temaer maler hver lagdelt flade med det samme antal lag.", "summary": "Et lag der mangler i ét tema, fejler nu et commit, og et der havde manglet siden det blev skrevet, males.", "content_html": "<p><strong>Situation.</strong> Sitet leverer to farvetemaer. Det mørke er standarden og bliver set på hele tiden; det lyse er en overstyring der kun optræder under en systemindstilling, hvilket betyder at det ses langt sjældnere og af ingen der tjekker det. Temaparitet er en defektklasse, hvor en flade bygges én gang og gentages én gang, og gentagelsen tavst taber et lag.</p>\n<p><strong>Opgave.</strong> Pariteten havde brug for en assertion, for det eneste alternativ er en person der husker at skifte indstilling og kigge.</p>\n<p><strong>Handling.</strong> Tjekket er lille og strukturelt: for hver lagdelt flade tælles de lag hvert tema maler, og det fejler når tallene er forskellige. Det sammenligner ikke udseender — temaer er ment til at se forskellige ud — det sammenligner sammensætning, hvilket er det der bør være identisk. Det fandt defekten ved sin første kørsel. Sitets helsidesdække er tre lag i det mørke tema, et facetmønster over et slør over væggen, og det lyse tema gentog det med to. Facetoverlægget havde været fraværende fra det lyse tema fra den dag det blev skrevet, og intet havde nogensinde sagt det, for en side der mangler et af tre baggrundslag, ligner en designbeslutning frem for en fejl. Rettelsen var én regel; opskrivningen bagefter noterede hvad overlægget koster i kontrast, så tilføjelsen er målt frem for antaget gratis.</p>\n<p><strong>Resultat.</strong> Et lag der mangler i ét tema, fejler nu et commit, og et der havde manglet siden det blev skrevet, males. Dette er den defektform, hele tjektilgangen sigter mod — intet var i stykker, intet fejlede, siden blev gengivet korrekt, og den havde været forkert i månedsvis.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Designsystemer & UI", "Frontend‑udvikling", "Test & QA", "UX/UI‑design", "UI/UX‑design & designsystemer" ] }, { "id": "https://engineer.company/da/portfolio/made-reduced-motion-honest-135/", "url": "https://engineer.company/da/portfolio/made-reduced-motion-honest-135/", "title": "Har gjort reduceret bevægelse ærlig efter at have fundet elleve selektorer, der stadig animerede under præferencen, fordi en universel transition‑none‑regel taber på specificitet til enhver regel, der bærer en klasse.", "summary": "Indstillingen respekteres i praksis frem for i hensigt, og elleve levende animationer, som en generel regel så ud til at have dækket, er faktisk dækket.", "content_html": "<p><strong>Situation.</strong> Sitet respekterede indstillingen om reduceret bevægelse med en enkelt regel, der slog hver overgang fra. Det læste som fuldstændigt, det linter rent, og under en emuleret indstilling om reduceret bevægelse animerede elleve selektorer stadig.</p>\n<p><strong>Opgave.</strong> Indstillingen skulle respekteres i praksis, og grunden til at den oplagte regel fejlede, skulle blive til noget den næste person ikke kan gentage.</p>\n<p><strong>Handling.</strong> Årsagen er specificitet. En universel regel scorer i bunden af kaskaden og taber til enhver regel der bærer en klasse, så en generel transition‑none bliver slået af hver gennemtænkt animation på siden — hvilket er hver animation, der er værd at slå fra. Rettelsen var strukturel frem for en lap: alt der bevæger sig, ligger nu i én sen stylesheet‑del, og en linter fejler en overgang på transform alle andre steder. Den skelnen reglen trækker er bevidst og snævrere end den oplagte: reducér bevægelse, ikke farve. En farveovergang er ikke bevægelse, og at slå den fra får grænseflader til at føles i stykker for folk der ikke bad om det, så en overgang der opregner farveegenskaber, består, og en overgang på bevægelse gør ikke. Bevægelse skal også udtrykkes som en overgang frem for en keyframe‑animation, for den første kan trivielt afbrydes, og den anden kan ikke. To beslægtede fælder kom ud af det samme arbejde og er noteret med målinger: et menuornament var enogtredive grader inde i en tres graders drejning, før det var halvt synligt, og fordi formen er periodisk ser en halv drejning identisk ud til en tredjedel af prisen; og fem forskellige egenskaber gør hver især et element til den indeholdende blok for alt, der er placeret i forhold til viewporten, hvilket tavst havde skrumpet et afvisningslag til en brøkdel af skærmen.</p>\n<p><strong>Resultat.</strong> Indstillingen respekteres i praksis frem for i hensigt, og elleve levende animationer, som en generel regel så ud til at have dækket, er faktisk dækket. Den generelle lære er den der står øverst i det dokument: en regel der taber på specificitet, fejler tavst, og hver rapport kalder det noget andet.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Designsystemer & UI", "Frontend‑udvikling", "Test & QA", "UX/UI‑design", "Webudvikling", "UI/UX‑design & designsystemer", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/built-a-637-line-print-stylesheet-136/", "url": "https://engineer.company/da/portfolio/built-a-637-line-print-stylesheet-136/", "title": "Har bygget et 637 linjer langt printstylesheet mod fire dokumenterede adfærd i gengivelsesmotoren, herunder run‑in‑overskrifter, der printede hvidt på hvidt for enhver læser, hvis browser foretrak mørkt.", "summary": "Sitet printer på ukendt papir i begge temaer, og de fire motoradfærd er skrevet ned med den defekt, hver af dem frembragte.", "content_html": "<p><strong>Situation.</strong> Sitets casestudier er det artefakt, nogen printer og tager med til et møde. Det gør papir til et reelt output frem for en høflighed, og papir er et andet medie end en smal skærm — læserens papirstørrelse er ukendt, læserens orientering er ukendt, og browserens printadfærd er forskellig mellem motorer på måder, ingen skærmforhåndsvisning afslører.</p>\n<p><strong>Opgave.</strong> Sitet skulle printe korrekt på ukendt papir, i begge farvetemaer, uden at et særskilt dokument skulle vedligeholdes ved siden af webudgaven.</p>\n<p><strong>Handling.</strong> Det er ét stylesheet på 637 linjer med én sideregel og én printblok. Sidereglen sætter en margen og sætter bevidst ingen papirstørrelse, så hvad læseren end vælger i dialogen kommer igennem, og alt andet tilpasser sig det. Rodskriftstørrelsen er fastlåst i punkter, så hver relativ måling har et fysisk anker, og én tekstbredde på omkring tooghalvfjerds tegn anvendes på de fire blokke på øverste niveau — hvilket er arket der vinder over designet, eftersom en liggende side ellers ville lade en linje løbe til omkring hundrede og ti tegn. Fire motoradfærd er dokumenteret som fælder, hver af dem har forårsaget en reel defekt. Viewport‑enheder resolverer til sideboksen i én motor og til vinduet i en anden, så en side printet fra et bredt vindue får en tredjedel af hver linje skåret af i den anden. Fastplacerede elementer males på hvert ark, så de dekorative droppes, og to af dem genbruges som brevhoved og kolofon. Paletten falder tilbage på hvid tekst, fordi standardtemaet er mørkt, hvilket printede de fire indrykkede afsnitsetiketter hvidt på hvidt — ordene var ganske enkelt fraværende. Og paginerede medier kan ikke dele en flex- eller gitterboks, hvilket frembragte et blankt første ark på en achievement‑side. Et tjek i seks dele dækker det og tester seks papir- og orienteringskombinationer, begge farveskemaindstillinger og det virkelige sideantal mod hvad indholdshøjden indebærer.</p>\n<p><strong>Resultat.</strong> Sitet printer på ukendt papir i begge temaer, og de fire motoradfærd er skrevet ned med den defekt, hver af dem frembragte. De kendte grænser er noteret frem for skjult: ingen sidetal eller løbende sidehoveder, én motor ignorerer enke- og forældreløs‑kontrol, og initialer er ikke universelle. En efterladt kommentarafslutning fik engang CSS‑linteren til at sluge en hel regel og bestå rent to gange — kun printtjekket bemærkede det.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Designsystemer & UI", "Dokumentation", "Frontend‑udvikling", "UX/UI‑design", "Webudvikling", "UI/UX‑design & designsystemer", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/mirrored-the-site-to-gemini-and-gopher-137/", "url": "https://engineer.company/da/portfolio/mirrored-the-site-to-gemini-and-gopher-137/", "title": "Har spejlet hele sitet som 777 Gemini‑dokumenter og 777 Gopher‑dokumenter ud fra det samme udrullede træ, med nul bytes ændring i HTML'en.", "summary": "Sitet kan læses over fire protokoller fra én bygning, med 777 dokumenter hver og nul ændring i HTML'en, og de alternative repræsentationer koster omkring tolv…", "content_html": "<p><strong>Situation.</strong> Sitet er statisk, har ingen sporing og ingen backend der kører, og dets argument er, at et dokument ikke har brug for en megabyte JavaScript for at blive læst. Det argument er let at fremføre og svært at påvise. To små internetprotokoller påviser det direkte, for ingen af dem kan bære et script overhovedet.</p>\n<p><strong>Opgave.</strong> Hele sitet skulle udgives over Gemini og Gopher fra det samme indhold, uden et andet indholdstræ og uden at ændre HTML’en.</p>\n<p><strong>Handling.</strong> Begge er outputformater af den samme bygning frem for en særskilt pipeline. Sitegeneratoren fik brugerdefinerede medietyper og outputformater, og hver side, hvert afsnit og hvert taksonomibegreb fik to repræsentationer mere ved siden af sin HTML og sin Markdown‑tvilling. Resultatet er 777 Gemini‑dokumenter og 777 Gopher‑menuer frembragt fra den samme kilde, udrullet til det samme træ og leveret af to små dæmoner på den samme vært. To egenskaber gjorde det værd at gøre frem for en kuriositet. Begge formater er markeret som ikke‑alternative repræsentationer, så intet ved HTML’en ændrede sig — ikke én byte, og det blev målt frem for antaget. Og Gopher‑formatet har ingen måde at lægge et link inde i en sætning, eftersom en menulinje er tabulatoradskilte felter, så teksten hårdombrydes ved otteogtres kolonner under bygningen; den begrænsning skærpede skrivningen på en måde, HTML’en aldrig krævede. Et Tor‑onion‑spejl ligger ved siden af dem, og det er den eneste offentlige flade platformen kunne tilføje uden at åbne en firewallport, for dæmonen ringer ud, og intet ringer ind.</p>\n<p><strong>Resultat.</strong> Sitet kan læses over fire protokoller fra én bygning, med 777 dokumenter hver og nul ændring i HTML’en, og de alternative repræsentationer koster omkring tolv procent af HTML’ens egen vægt på disken. Kun én af de fire måles for trafik, og det står i platformens eget statistikdokument frem for at blive tavst ignoreret.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Frontend‑udvikling", "Infrastruktur", "Internationalisering", "Webudvikling", "DevOps & CI/CD‑automatisering", "Internationalisering & lokalisering", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/replaced-963-structured-data-blocks-with-one-graph-138/", "url": "https://engineer.company/da/portfolio/replaced-963-structured-data-blocks-with-one-graph-138/", "title": "Har erstattet 963 anonyme blokke med strukturerede data, der gentog virksomheden 1.671 gange, med én sammenkædet graf af 16 typer præget fra stabile oprindelsesidentifikatorer.", "summary": "Én graf med stabil identitet erstatter 963 anonyme gentagelser, og siderne bærer mindre frem for mere.", "content_html": "<p><strong>Situation.</strong> Sitet udsendte strukturerede data på den måde de fleste sites gør: en blok per side, hver af dem beskrev organisationen forfra igen. På tværs af sitet blev det til 963 blokke, der gentog den samme virksomhed 1.671 gange, anonymt — ingen stabil identifikator nogen steder, så intet der forbrugte det, kunne se at organisationen på én side var organisationen på en anden.</p>\n<p><strong>Opgave.</strong> De strukturerede data skulle blive én graf med stabil identitet frem for en bunke blokke, der tilfældigvis indeholder de samme ord.</p>\n<p><strong>Handling.</strong> Identifikatorer udstedes nu fra sitets oprindelse — én til organisationen, én til sitet, én til personen — og hver blok refererer til dem frem for at gentage deres indhold. Identifikatorerne er fælles for oprindelsen frem for per sprog, for virksomheden er den samme virksomhed på dansk. Seksten typer er i spil og dækker ydelseskataloget og dets tilbud, anmeldelserne og det arbejde de beskriver, samlingerne og deres brødkrummer. To beslutninger holdt vægten nede. Oversigtssider udgiver deres elementer kun ved URL frem for at indlejre dem, hvilket blev målt: at navngive dem på porteføljeoversigten ville have betydet 107 fulde udsagn og en stigning på 52 procent i den komprimerede side. Og det tjek der vogter det, validerer ikke blot syntaks — det efterprøver at hver blok kan parses, at hver identifikator resolverer, og at hver URL grafen navngiver, faktisk blev bygget, så en graf der peger på en side der ikke findes, fejler gaten.</p>\n<p><strong>Resultat.</strong> Én graf med stabil identitet erstatter 963 anonyme gentagelser, og siderne bærer mindre frem for mere. Den måling der satte arbejdet i gang, er værd at holde sig for øje: sitet havde udsendt den samme organisationsbeskrivelse 1.671 gange, uden at noget var i stand til at føje dem sammen.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "API'er & integration", "Automatisering & CI/CD", "Brand & marketing", "Data governance", "Frontend‑udvikling", "Webudvikling", "Brand, marketing & SEO", "Data governance & datakvalitet", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/cut-the-stylesheet-bundle-from-87kb-to-31kb-139/", "url": "https://engineer.company/da/portfolio/cut-the-stylesheet-bundle-from-87kb-to-31kb-139/", "title": "Har skåret stylesheet‑bundtet fra 87 KB til 31 KB ved at tage en base64‑skrifttype ud af det og har droppet 756 KB fordelt på 22 filer, der blev udgivet ved hver deploy og refereret af intet.", "summary": "Bundtet er 31 KB i stedet for 87, 756 KB døde aktiver holdt op med at blive udrullet, og hver beslutning har en måling knyttet til sig — herunder den der gik…", "content_html": "<p><strong>Situation.</strong> Sitets stylesheet‑bundt var 87 KB, hvilket for et dokumentsite uden framework er det meste af en sidevægt brugt, før noget indhold ankommer. Sitet udgav også et katalog af ikoner ved hver udrulning, genereret én gang og siden refereret af intet.</p>\n<p><strong>Opgave.</strong> Vægten skulle ned uden at designet ændrede sig, og den skulle ned af en grund man kunne pege på frem for ved almindelig oprydning.</p>\n<p><strong>Handling.</strong> Måling først. To tredjedele af det største stylesheet var én base64‑kodet font — omkring 56 KB tekst der koder omkring 42 KB font — indlejret for en gevinst ved første optegning, som en forudindlæst ekstern fil alligevel får, og betalt på hver side, uanset om vægten var nødvendig. At tage den ud tog den fil fra 84 KB til 28 og hele bundtet fra 87 til 31. De tre fontvægte forudindlæses i stedet, og den tredje kom først på listen, da feltdata viste at den lukkede den længste kritiske kæde, frem for fordi tre lød komplet. Særskilt fandt en audit af hvad udrulningen faktisk udgav enogtyve genererede ikoner og en dubleret konfigurationsfil, 756 KB, sendt ud hver gang og refereret fra ingen steder; og sitets ikonfil selv kom ned fra 145 KB til 15. Et moderne billedformat blev vurderet til mærket, målt og afvist — det kom ud større, og udvælgelsesmekanismen vælger efter rækkefølge frem for efter størrelse, så en moderne browser ville have taget den tungere fil overalt. To tjek holder nu linjen: intet fotografi må vises bredere end halvdelen af sine kildepixels, og hver illustration skal udgives i en størrelse dens egne pixels understøtter ved både normal og høj tæthed.</p>\n<p><strong>Resultat.</strong> Bundtet er 31 KB i stedet for 87, 756 KB døde aktiver holdt op med at blive udrullet, og hver beslutning har en måling knyttet til sig — herunder den der gik den anden vej, hvilket er den mest nyttige af de to optegnelser.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Designsystemer & UI", "Frontend‑udvikling", "Performanceoptimering", "Webudvikling", "Brand, marketing & SEO", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/fixed-a-sitemap-with-one-shared-timestamp-140/", "url": "https://engineer.company/da/portfolio/fixed-a-sitemap-with-one-shared-timestamp-140/", "title": "Har rettet et sitemap, hvor 172 af 176 URL'er delte ét ændringstidsstempel, ved at tage datoen fra git‑historikken efter at have fastslået, at eksporten omskriver hver fil ved hver kørsel.", "summary": "En gensynkronisering efter en måneds indholdsredigeringer rører nu otte filer af 186 i stedet for dem alle, og sitemappet siger noget sandt.", "content_html": "<p><strong>Situation.</strong> Sitets sitemap fortalte søgemaskiner, at 172 af dets 176 sider sidst var ændret i det samme øjeblik. Det er ikke en subtil unøjagtighed — en ændringsdato er et løfte til en crawler om, at en sides indhold ændrede sig, og et site der giver det løfte 172 gange samtidig, fortæller enten sandheden om en fuld omskrivning eller fortæller ingen noget brugbart.</p>\n<p><strong>Opgave.</strong> Datoerne skulle beskrive indholdet frem for filen, uden at opfinde en præcision repositoryet ikke har.</p>\n<p><strong>Handling.</strong> Årsagen var, at datoen kom fra filens ændringstidspunkt, og indholdseksporten omskriver hver fil ved hver kørsel, så en enkelt synkronisering stemplede hele korpuset. Rettelsen flytter kilden til versionshistorikken med en faldbagkæde, der først prøver et eksplicit felt, så commit‑historikken, så filen. Fælden i den rettelse er værd at notere: det bogstavelige feltnavn skal optræde på listen, ellers læser generatoren det aldrig, så en konfiguration der ser ud til at foretrække en skreven dato, men udelader navnet, ignorerer tavst hver skreven dato. To andre datobeslutninger kom ud af det samme arbejde. Databasen bærer nu hvornår hver post blev skrevet og sidst revideret, holdt bevidst adskilt fra hvornår arbejdet skete — de ligger år fra hinanden, og at slå dem sammen ville datere en side skrevet i år til et årti siden. Og rækker i den fil er valgfrie, og intet udledes, for repositoryets egen historik begynder efter indholdet gjorde: en manglende dato lader feltet stå tomt frem for at registrere en migrering, ud fra det princip at et præcist forkert tal er værre end et fraværende, eftersom det kun er det forkerte der bliver troet.</p>\n<p><strong>Resultat.</strong> En gensynkronisering efter en måneds indholdsredigeringer rører nu otte filer af 186 i stedet for dem alle, og sitemappet siger noget sandt. Den generelle regel kom i metadatadokumentet ved siden af, for den samme fælde gælder hvert felt der har en faldbagkæde.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Brand & marketing", "Python", "Test & QA", "Webudvikling", "Brand, marketing & SEO", "DevOps & CI/CD‑automatisering", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/brought-the-quality-gate-code-under-a-linter-143/", "url": "https://engineer.company/da/portfolio/brought-the-quality-gate-code-under-a-linter-143/", "title": "Har bragt 10.242 linjer kvalitetsgate‑JavaScript under en formatter og en linter efter at have fastslået, at det var kodebasens største kodemængde og den eneste, intet læste, og har rettet 13 fund uden at undertrykke nogen.", "summary": "Den største og mest bærende kode i repositoryet er nu formateret, lintet og typetjekket, med tretten fund rettet og nul undertrykkelser.", "content_html": "<p><strong>Situation.</strong> Sitets kvalitetsgate er toogtredive scripts på i alt 10.242 linjer JavaScript. Den var med god margin den største kodemængde i repositoryet, og den var den eneste kodemængde, intet læste — ingen formatter, ingen linter, ingen typetjek. De programmer der håndhævede hver regel i projektet, var de eneste programmer, der var underlagt ingen af dem.</p>\n<p><strong>Opgave.</strong> Tjekkerne skulle holdes til den standard, de findes for at håndhæve, og formatteren skulle tilpasses dem frem for omvendt.</p>\n<p><strong>Handling.</strong> Formatteren kom først, og indrykningsbredden var den interessante beslutning. Repositoryets standard er fire mellemrum; ved fire mellemrum ville formatteren have omskrevet 2.173 linjer af den største tjekker. En overstyring til to mellemrum for de filer reducerede det til 38 linjers ægte drift. Princippet noteret sammen med den er, at formatteren tilpasses koden, ikke koden formatteren — at omformatere to tusind linjer for at tilfredsstille en præference ødelægger evnen til at læse filens historik. Derefter linteren, som frembragte tretten reelle fund, alle rettet og ingen undertrykt. Python‑tjekkerne fik den samme behandling gennem et typetjek konfigureret på et mellemniveau af stringens med tredive enkeltstående regler fra det strengeste niveau slået til ovenpå, valgt ved måling: ved den indstilling er træet tavst, og af de tredive kandidater var niogtyve allerede tavse, og én udløste — og den blev rettet frem for undtaget. Typetjekket fandt to reelle defekter, som linteren havde ladet bestå rent, begge om en værdis form frem for dens syntaks.</p>\n<p><strong>Resultat.</strong> Den største og mest bærende kode i repositoryet er nu formateret, lintet og typetjekket, med tretten fund rettet og nul undertrykkelser. Grunden til at det betyder mere, end linjeantallet antyder, står i planen: en defekt i sitets stylesheet viser sig som en side der ser forkert ud, og en defekt i en tjekker viser sig som et tjek der består, når det ikke burde.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Dokumentation", "Frontend‑udvikling", "Python", "Test & QA", "DevOps & CI/CD‑automatisering", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/built-a-database-driven-cv-and-portfolio-generator-144/", "url": "https://engineer.company/da/portfolio/built-a-database-driven-cv-and-portfolio-generator-144/", "title": "Har bygget en databasedrevet generator til CV, referencer, portefølje og ansøgninger i Python — 41 moduler, 10.580 linjer — der gengiver seks outputformater fra én SQLite‑kilde med 23 tabeller samlet af en idempotent pipeline i 19 trin.", "summary": "Seks formater, tre sprog og fem temaer kommer ud af én database, og en rettet sætning rettes én gang.", "content_html": "<p><strong>Situation.</strong> Et CV, et referenceark, en portefølje og en ansøgning er de samme fakta arrangeret på fire måder, og at holde dem som fire dokumenter betyder, at hver rettelse foretages fire gange og til sidst ikke gør. Tre sprog ganger det med tre. Fejltilstanden er ikke, at et dokument er forkert; det er, at to dokumenter er uenige, og intet siger hvilket der er det aktuelle.</p>\n<p><strong>Opgave.</strong> Én kilde til fakta skulle frembringe hvert dokument, på hvert sprog, i hvert format, med arrangementet afgjort af kode frem for af den, der sidst redigerede en fil.</p>\n<p><strong>Handling.</strong> Kilden er en SQLite‑database med treogtyve tabeller — achievements, titler, virksomheder, regioner, kategorier, ydelser, profiler, referencer, resuméer — samlet af en pipeline i nitten trin, der kører fra en tom fil til en komplet database med én kommando. Trinnene er ordnede, og hvert er skrevet til at være sikkert at gentage, så pipelinen kan køres mod en eksisterende database uden at duplikere en række. Enogfyrre Python‑moduler på i alt 10.580 linjer gengiver seks outputformater fra den: HTML, PDF i to varianter, ren tekst, Markdown og JSON, gennem seks Jinja‑skabeloner og otte stylesheets. Kørselsafhængigheder blev holdt til præcis to, en skabelonmotor og en PDF‑gengiver, ud fra det argument at en dokumentgenerator, som ikke kan installeres om fem år, ikke har bevaret noget. Målretning er et førsteklasses begreb frem for en manuel redigering: en profil vælger hvilke achievements der optræder og i hvilken rækkefølge, så et dokument rettet mod én slags læser er en forespørgsel frem for en kopi.</p>\n<p><strong>Resultat.</strong> Seks formater, tre sprog og fem temaer kommer ud af én database, og en rettet sætning rettes én gang. Prisen er, at systemet nu er den eneste måde at frembringe et dokument på — der er ikke længere en fil at åbne og redigere i en fart, og at tilføje et sprog betyder at tilføje det overalt, før noget som helst bygger.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Backend‑udvikling", "Databaser", "Full stack‑udvikling", "Internationalisering", "Løsningsarkitektur", "Python", "SQL", "Backend- & API‑udvikling", "Databasedesign & datamodellering", "Full stack‑produktudvikling", "Internationalisering & lokalisering", "Platform- & løsningsarkitektur" ] }, { "id": "https://engineer.company/da/portfolio/held-the-generator-to-964-tests-and-a-coverage-floor-145/", "url": "https://engineer.company/da/portfolio/held-the-generator-to-964-tests-and-a-coverage-floor-145/", "title": "Har holdt generatoren til 981 testtilfælde med et dækningsgulv på 92 % grendækning og advarsler behandlet som fejl, og har hævdet idempotens ved at køre hele build‑pipelinen to gange fra en tom fil og kræve, at anden gennemløb intet ændrer.", "summary": "Pipelinen kan køres igen mod en levende database uden frygt, hvilket er det der overhovedet gør trinvist indholdsarbejde muligt.", "content_html": "<p><strong>Situation.</strong> En generator der samler en database fra bunden, har en særlig slags fejl: den virker første gang og korrumperer anden gang. Trin der indsætter uden at tjekke, trin der afhænger af rækkefølgen i et tidligere trins output, trin der er sikre alene og ikke sammen. Intet af det viser sig i en test, der starter fra ingenting og kører én gang.</p>\n<p><strong>Opgave.</strong> Bygningen skulle bevises gentagelig frem for blot fungerende, og testsuiten skulle være stor nok og streng nok til, at en regression ikke kunne slippe tavst igennem den.</p>\n<p><strong>Handling.</strong> Idempotenstesten er den stumpe og den mest nyttige: byg hele databasen fra en tom fil, tag et øjebliksbillede, kør hele pipelinen i nitten trin igen over resultatet, og kræv at anden gennemløb ikke ændrer noget. Rækketal, indhold og identifikatorer skal alle stemme. Omkring den ligger 383 testfunktioner — 981 tilfælde når de parametriserede folder sig ud — fordelt på femogfyrre filer og 7.944 linjer, der dækker pipelinen, gengiverne, indholdsindlæserne, målretningslogikken og tjekkerne. Grendækning bærer et gulv på tooghalvfems procent håndhævet i bygningen frem for rapporteret i et sammendrag, og suiten måler i øjeblikket omkring 95 procent, så gulvet har luft uden at være dekorativt. Advarsler er konfigureret som fejl, hvilket er den indstilling der betyder mest i praksis — en udfasningsmeddelelse der udskrives i to år, er en udfasningsmeddelelse ingen læser, og den kørsel der forvandler den til en rød test, er den kørsel der får den rettet.</p>\n<p><strong>Resultat.</strong> Pipelinen kan køres igen mod en levende database uden frygt, hvilket er det der overhovedet gør trinvist indholdsarbejde muligt. Gulvet er et gulv, ikke et mål, og det er værd at sige, at tooghalvfems procent grendækning stadig efterlader grene, intet nogensinde har taget — tallet afgrænser risikoen, det fjerner den ikke, og to af de defekter der senere blev fundet i projektet, lå i kode, dækningsrapporten viste som dækket.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Databaser", "DevOps", "Drift & backup", "Python", "Test & QA", "Backend- & API‑udvikling", "DevOps & CI/CD‑automatisering", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/established-pdf-ua-1-conformance-across-nine-documents-146/", "url": "https://engineer.company/da/portfolio/established-pdf-ua-1-conformance-across-nine-documents-146/", "title": "Har etableret PDF/UA‑1‑konformitet på tværs af ni dokumenter ved 106 af 106 regler og har fundet arkivvarianten fejle én regel ud af 146 — et nærved‑resultat, der læses som bestået af enhver, der ikke kører validatoren.", "summary": "Tilgængelighedsoverholdelse er bevist med fuldt hus, og arkivpåstanden er oplyst ærligt som en nærved-fejl med det konkrete hul navngivet.", "content_html": "<p><strong>Situation.</strong> En PDF der ser rigtig ud på skærmen, fortæller intet om, hvorvidt en skærmlæser kan læse den, om dens overskrifter danner en struktur, om dens sprog er erklæret, eller om den stadig kan åbnes om tyve år. Tilgængelig PDF og arkiv‑PDF er begge maskintjekbare standarder, og et dokument der aldrig er kørt gennem validatoren, er et dokument der fremsætter en uverificeret påstand.</p>\n<p><strong>Opgave.</strong> De genererede dokumenter skulle måles mod begge standarder, med resultatet noteret som et tal frem for som en hensigt.</p>\n<p><strong>Handling.</strong> Ni dokumenter — CV’et i sine varianter, referencearket, porteføljen og ansøgningen, på tværs af sprog — blev kørt gennem en uafhængig validator for tilgængelighedsprofilen og arkivprofilen hver for sig. Tilgængelighedsprofilen består med 106 af 106 regler, hvilket krævede tagget struktur, erklæret dokumentsprog, alternativ tekst på hver ikke‑dekorativ grafik, en rigtig titel i metadataene og en eksplicit læserækkefølge frem for den, layoutet tilfældigvis frembringer. Arkivprofilen er det mere interessante resultat: den fejler præcis én regel af 146. Det er den slags resultat, der er let at fejlrapportere. Hundrede og femogfyrre beståede læser som overholdelse i et sammendrag, og det er ikke overholdelse; det er et dokument der vil blive afvist af et system, der håndhæver standarden. Den fejlende regel er noteret med hvad den er, og hvorfor den ikke er lukket, frem for rundet væk.</p>\n<p><strong>Resultat.</strong> Tilgængelighedsoverholdelse er bevist med fuldt hus, og arkivpåstanden er oplyst ærligt som en nærved‑fejl med det konkrete hul navngivet. Den grænse der er værd at være direkte om, er at begge tal kommer fra én validator; en anden implementering kan være uenig, og en regel der består, er kun bevis for, at denne tjekker intet havde at sige om den.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Dokumentation", "Python", "Test & QA", "UX/UI‑design", "Webudvikling", "Teknisk dokumentation", "UI/UX‑design & designsystemer", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/enabled-every-python-linter-rule-as-an-error-147/", "url": "https://engineer.company/da/portfolio/enabled-every-python-linter-rule-as-an-error-147/", "title": "Har valgt hver eneste regel, Python‑linteren har, som fejl og har arbejdet 1.815 fund ned til nul, hvor hver af de få undtagelser bærer en skreven begrundelse og to af dem er understøttet af et tjek frem for en kommentar.", "summary": "Linteren kører ved fuld styrke med nul fund, og hver afvigelse er dokumenteret på den linje, hvor den tages.", "content_html": "<p><strong>Situation.</strong> De fleste projekter vælger en behagelig delmængde af deres linters regler, og delmængden vælges af hvilke regler der var tavse den dag, den blev konfigureret. Det gør konfigurationen til en optegnelse over kodens eksisterende vaner frem for en standard koden holdes til, og hver regel der er slået fra, er en defektklasse, ingen nogensinde får besked om.</p>\n<p><strong>Opgave.</strong> Standarden skulle vendes om — hver regel værktøjet implementerer slået til som en fejl — og den resulterende efterslæb arbejdes ned til nul frem for forhandlet ned ved at slå regler fra igen.</p>\n<p><strong>Handling.</strong> At vælge det komplette regelsæt frembragte 1.815 fund ved første kørsel. De blev arbejdet igennem efter kategori frem for efter fil, for kategorierne fortæller noget: ubrugte argumenter og skyggede indbyggede navne er støj, men sikkerhedskategorien, kategorien for foranderlige standardværdier og kategorien for undtagelseshåndtering pegede hver især på reel adfærd. Der findes ægte uforeneligheder — en formatter og en linter kan være uenige om den samme linje, og nogle få regler modsiger projektets egne bevidste valg — og hver af det lille antal undtagelser bærer en skreven begrundelse dér hvor undtagelsen tages, som siger hvad reglen ville, og hvorfor denne kode gør noget andet. To af dem går videre og er understøttet af et tjek frem for en kommentar, så undtagelsen ikke tavst kan udvide sig: reglen er slået fra, og en test efterprøver netop den egenskab, reglen ville have håndhævet. Typetjek kører i streng tilstand ved siden af, hvilket er en særskilt og hårdere standard, og det er den, der fangede defekter, linteren ikke kunne se, fordi de handler om hvad en værdi er frem for hvordan den er skrevet.</p>\n<p><strong>Resultat.</strong> Linteren kører ved fuld styrke med nul fund, og hver afvigelse er dokumenteret på den linje, hvor den tages. Prisen er reel og værd at nævne: den strengeste indstilling frembringer fund, der oprigtigt ikke er værd at handle på, og nogen skal træffe den vurdering 1.815 gange frem for én gang.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Dokumentation", "Python", "Test & QA", "Backend- & API‑udvikling", "DevOps & CI/CD‑automatisering", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/vendored-a-qr-encoder-in-522-lines-148/", "url": "https://engineer.company/da/portfolio/vendored-a-qr-encoder-in-522-lines-148/", "title": "Har indlejret en QR‑encoder — Reed‑Solomon over GF(256), fast modullayout, otte maskemønstre — på 522 linjer frem for at tage en tredje runtime‑afhængighed, og har verificeret den ved at læse den færdige matrix tilbage med en uafhængigt skrevet dekoder.", "summary": "Antallet af kørselsafhængigheder forblev to, og indkoderen er den eneste medleverede komponent i projektet.", "content_html": "<p><strong>Situation.</strong> Dokumenterne havde brug for en QR‑kode, der henviser til onlineversionen. Ethvert tilgængeligt bibliotek gør dette, og at tage et ville have tilføjet en tredje kørselsafhængighed til et projekt, der bevidst havde holdt sig til to — en skabelonmotor og en PDF‑gengiver — ud fra det argument, at en dokumentgenerator stadig skal kunne installeres om flere år.</p>\n<p><strong>Opgave.</strong> Enten skulle afhængigheden accepteres eller formatet implementeres, og implementeringen skulle efterprøves som korrekt frem for blot at frembringe noget firkantet og sort.</p>\n<p><strong>Handling.</strong> Indkoderen er på 522 linjer og implementerer de dele af specifikationen, som anvendelsen faktisk har brug for: byte‑tilstandsindkodning, Reed‑Solomon‑fejlkorrektion over Galois‑legemet med 256 elementer med generatorpolynomiet bygget i den krævede grad, det faste modullayout med dets søgemønstre, timingmønstre og justeringsmønstre, format- og versionsinformation samt alle otte datamaskemønstre vurderet mod specifikationens fire strafregler, så den lavest scorende maske vælges frem for en fast. Efterprøvningen er den del, der gjorde det forsvarligt. At teste en indkoder mod dens egen logik beviser intet, så den færdige modulmatrix læses tilbage af en afkoder skrevet uafhængigt ud fra specifikationens læserækkefølge, og testen fastslår, at den afkodede streng er lig med inddata. Det gør “den frembringer et sandsynligt billede” til “den frembringer en kode, der afkoder til den rigtige URL”, og det tjekkes ved hver kørsel frem for én gang med øjet og en telefon.</p>\n<p><strong>Resultat.</strong> Antallet af kørselsafhængigheder forblev to, og indkoderen er den eneste medleverede komponent i projektet. Det skal siges ligeud, at dette ikke er en almen implementering — den understøtter de tilstande og versioner, dokumenterne bruger, og intet andet, og den ærlige begrundelse er afhængighedsbudgettet frem for nogen påstand om, at resultatet er bedre end et modent bibliotek.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Dokumentation", "Performanceoptimering", "Python", "Test & QA", "Backend- & API‑udvikling", "Platform- & løsningsarkitektur" ] }, { "id": "https://engineer.company/da/portfolio/wrote-tests-for-the-checkers-themselves-149/", "url": "https://engineer.company/da/portfolio/wrote-tests-for-the-checkers-themselves-149/", "title": "Har skrevet tests af selve tjekkene efter at have fastslået, at et tjek, der kun fodres med rent input, en dag melder rent, fordi det intet læste — ved at plante en stavefejl for at bekræfte, at stavekontrollen finder den, og ved at tage et id‑interval fra databasen frem for fra et tal i testen.", "summary": "Hvert tjek i projektet har nu en test, der beviser, at det fejler på dårlige inddata, og områdeforventningerne læses fra sandhedskilden.", "content_html": "<p><strong>Situation.</strong> Projektet kører en række egne tjek over sit eget indhold — en stavekontrol, et tjek af identifikatorernes talområde, et stemmetjek, et tjek af taloverensstemmelse. Hvert af dem havde kørt grønt i månedsvis. Et tjek, der kun nogensinde har set rene inddata og kun nogensinde har meldt rent, kan ikke skelnes fra et tjek, der slet intet læser, og der fandtes ingen test i suiten, som kunne kende forskel på de to.</p>\n<p><strong>Opgave.</strong> Tjekkene skulle bringes til at bevise, at de kan fejle, og de fikstursdata, de tjekker imod, skulle holde op med at være håndholdte kopier af det, de beskriver.</p>\n<p><strong>Handling.</strong> Mønsteret, der er anvendt hele vejen igennem, er at plante netop den defekt, tjekket findes for at fange, og kræve at tjekket finder den. Stavekontrollen fodres med et bevidst fejlstavet ord, og testen fejler, hvis kørslen kommer rent tilbage. Stemmetjekket fodres med prosa i første person og skal afvise den. Buzzword‑tjekket fodres med et forbudt ord. Hvert af disse er en lille test, og hver af dem lukkede en reel blind vinkel, for to af tjekkene viste sig at læse et snævrere sæt filer end deres dokumentation påstod og havde tavst sprunget indhold over. Den anden ændring handler om, hvor en test henter sine forventninger: tjekket af identifikatorernes talområde sammenlignede før med et tal skrevet i testfilen, hvilket betød, at enhver tilføjelse af indhold krævede en redigering af en test, og en redaktør, der opdaterede tallet uden at se efter, havde slået tjekket fra. Det udleder nu området fra databasen, så tjekket beskriver dataene frem for en forældet erindring om dem.</p>\n<p><strong>Resultat.</strong> Hvert tjek i projektet har nu en test, der beviser, at det fejler på dårlige inddata, og områdeforventningerne læses fra sandhedskilden. Det ubehagelige er, hvad dette blotlagde: et grønt tjek havde været meningsløst mindst to steder i et ukendt tidsrum, og der er ingen måde at finde ud af med tilbagevirkende kraft, hvad der slap igennem.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Data governance", "Databaser", "Python", "Test & QA", "Data governance & datakvalitet", "DevOps & CI/CD‑automatisering", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/found-the-commit-hooks-and-the-gate-disagreeing-150/", "url": "https://engineer.company/da/portfolio/found-the-commit-hooks-and-the-gate-disagreeing-150/", "title": "Har fundet commit‑hooks og kvalitetsgaten køre forskellige tjek, mens et dokument lovede, at de var de samme, ved at sammenligne de to lister i en test — de fem, der kun nogensinde kørte i hånden, var dem, der læste CV‑prosaen.", "summary": "Hooken og porten kører beviseligt de samme tjek, og prosatjekkene kører nu ved hver commit. Afvejningen er commit-hookens køretid, som voksede og vil blive ved…", "content_html": "<p><strong>Situation.</strong> Projektet havde en commit‑hook, der kører tjek, før en commit accepteres, og en fuld kvalitetsport kørt efter behov. Et dokument angav, at hooken kører porten, så intet kunne nå historikken uden at bestå alt. Begge lister blev vedligeholdt i hånden, i to forskellige filer, og intet sammenlignede dem.</p>\n<p><strong>Opgave.</strong> Påstanden skulle gøres til en efterprøvning, hvilket betød at opregne begge mængder programmatisk og fejle, når de afviger.</p>\n<p><strong>Handling.</strong> Testen læser hook‑konfigurationen og portens opgavedefinitioner, opløser hver af dem til den mængde tjek, den faktisk kalder, og sammenligner. De stemte ikke. Fem tjek fandtes kun i porten og kørte aldrig ved commit, og de fem var ikke tilfældige — det var dem, der læser selve CV’ets prosa: stemmetjekket, der holder anmeldelser upersonlige, buzzword‑tjekket, tjekket af taloverensstemmelse på tværs af sprog, notationstjekket og stavekontrollen over indhold. Med andre ord kørte hvert tjek, der beskytter kode, automatisk, og hvert tjek, der beskytter skriften, kørte kun, når nogen huskede det. Da skriften er hele produktet, var udsattheden vendt om i forhold til, hvor nogen ville have gættet. Rettelsen var at bringe de fem ind i hooken, hvilket krævede at gøre to af dem hurtige nok til at overleve et budget før commit, og derefter at holde sammenligningstesten på plads, så de to lister ikke kan glide fra hinanden igen. Dokumentet, der havde beskrevet en hensigt, beskriver nu noget håndhævet.</p>\n<p><strong>Resultat.</strong> Hooken og porten kører beviseligt de samme tjek, og prosatjekkene kører nu ved hver commit. Afvejningen er commit‑hookens køretid, som voksede og vil blive ved med at vokse, efterhånden som indholdet vokser, og der findes et punkt, hvor en langsom hook bliver omgået — så denne rettelse har en holdbarhed målt i, hvor længe tjekkene forbliver hurtige.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Dokumentation", "Python", "Test & QA", "DevOps & CI/CD‑automatisering", "Teknisk dokumentation", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/rehearsed-the-forge-side-ci-hook-151/", "url": "https://engineer.company/da/portfolio/rehearsed-the-forge-side-ci-hook-151/", "title": "Har prøvekørt CI‑hooken på forge‑siden og har fundet to fejl, der ikke kunne nås ved at læse filen: en fallback, der lagde et uopløseligt argument på hookens input, og gits egen miljøvariabel, der fulgte gaten ind i checkouten og gjorde 19 tests røde.", "summary": "Hooken virker på begge veje, og begge defekter blev fundet, før noget rigtigt push mødte dem. Begrænsningen er, at en prøve stadig er en simulering: den…", "content_html": "<p><strong>Situation.</strong> Den selvhostede git‑forge tager imod pushes og kan køre en hook på serversiden for at afvise arbejde, der ikke består porten. En hook på serversiden er det ene stykke automatik, der ikke kan testes ved at køre den lokalt: den udføres i et bart lager, uden arbejdstræ, under et miljø, forgen sætter, og læser de pushede referencer fra sin standardinddata. At læse skriptet og konkludere, at det er korrekt, er et gæt.</p>\n<p><strong>Opgave.</strong> Hooken skulle prøves af mod et rigtigt bart lager og et rigtigt push, før den blev betroet, frem for at blive udrullet og opdaget.</p>\n<p><strong>Handling.</strong> Prøven opretter et bart lager, installerer hooken, kloner det, committer, pusher og efterprøver både accept- og afvisningsvejen. Den fandt to defekter, hvoraf ingen af dem var synlige i filen. Den første var en nødløsning i argumenthåndteringen: når hooken ikke kunne bestemme en reference, indsatte den en pladsholder, der ikke var et opløseligt objekt, og fordi værdien ankommer på hookens standardinddata frem for som en parameter, viste fejlen sig som en urelateret fejl meget længere nede. Den anden er den, der er værd at huske. Git eksporterer en miljøvariabel, der navngiver lagermappen, når den kalder en hook, og den variabel nedarves af alt, hvad hooken kører. Porten tjekker den pushede revision ud og kører testsuiten inde i den, og testsuiten nedarvede pegepinden til det bare lager — så nitten test, der rører git, opløste mod det forkerte lager og gik røde på kode, der var korrekt. Miljøet skal ryddes ved grænsen, og den grænse er usynlig, medmindre tingen faktisk køres.</p>\n<p><strong>Resultat.</strong> Hooken virker på begge veje, og begge defekter blev fundet, før noget rigtigt push mødte dem. Begrænsningen er, at en prøve stadig er en simulering: den beviser, at hooken overlever én pushform, og forgen ser i drift pushes, som denne prøve ikke konstruerer, herunder tvungne opdateringer og sletning af grene.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Infrastruktur", "Linux & servere", "Python", "Test & QA", "DevOps & CI/CD‑automatisering", "Infrastructure as Code", "Systemadministration" ] }, { "id": "https://engineer.company/da/portfolio/moved-every-user-facing-string-into-a-content-tree-152/", "url": "https://engineer.company/da/portfolio/moved-every-user-facing-string-into-a-content-tree-152/", "title": "Har flyttet hver brugervendt streng ud af Python og ind i et indholdstræ på 874 filer på tre sprog, efter at have fundet døde oversættelser, ingen kunne se var døde, og et tjek, der i stilhed bedømte en tredjedel af achievements.", "summary": "Prosaen kan nu redigeres uden at røre kode og sammenlignes på tværs af sprog ved at tælle. Prisen er, at tilføjelsen af et sprog nu er utvetydig frem for…", "content_html": "<p><strong>Situation.</strong> Hver brugervendt streng i generatoren — hver bedrift, hver anmeldelse, hver afsnitsoverskrift, hvert resumé, på tre sprog — lå inde i Python‑kildekode. Det gør redigeringen af en sætning til en kodeændring, gør gennemgangen af en oversættelse til en diff mod kildekode og gør det umuligt at se med et blik, om et sprog er komplet. Det skjuler også den fejltilstand, der betyder noget: en oversættelse kan være til stede, forkert og uopnåelig på samme tid.</p>\n<p><strong>Opgave.</strong> Strengene skulle flyttes ud af koden og ind i et indholdstræ, der kan tælles, sammenlignes på tværs af sprog og tjekkes uden at køre gengiveren.</p>\n<p><strong>Handling.</strong> Resultatet er 874 filer under en indholdsmappe, ordnet efter art og derefter efter sprog: bedrifter, anmeldelser, resuméer, titler, virksomheder, regioner, kategorier, ydelser, profiler, referencer og ansøgningsbrudstykker. Tabulatorseparerede filer, hvor enheden er en linje med en identifikator, enkeltfiler hvor enheden er et afsnit. Indlæseren læser dem ved bygningstid, og databasen samles ud fra dem, så træet er kilden og databasen er afledt. To fund kom direkte ud af at kunne tælle. Nogle oversatte strenge havde ikke længere nogen engelsk modpart — døde poster, som intet gengav, og som ingen kunne have bemærket, mens de var indlejret i kode, for en urefereret ordbogsnøgle ser præcis ud som en refereret. Og et indholdstjek, der skulle bedømme hver bedrift, læste kun den delmængde, det kunne opløse, bedømte syvogtredive ud af treoghalvfems og meldte succes. At gøre korpusset til en mappe gjorde begge dele synlige som en uoverensstemmelse i filtal.</p>\n<p><strong>Resultat.</strong> Prosaen kan nu redigeres uden at røre kode og sammenlignes på tværs af sprog ved at tælle. Prisen er, at tilføjelsen af et sprog nu er utvetydig frem for gradvis — træet gør et ufuldstændigt sprog åbenlyst, hvilket er pointen, men det betyder også, at delvise oversættelser ikke kan udsendes stille, mens de færdiggøres.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Data governance", "Dokumentation", "Internationalisering", "Migrering & modernisering", "Python", "Backend- & API‑udvikling", "Data governance & datakvalitet", "Databasemigrering & modernisering", "Internationalisering & lokalisering" ] }, { "id": "https://engineer.company/da/portfolio/built-a-cross-language-figure-and-notation-check-153/", "url": "https://engineer.company/da/portfolio/built-a-cross-language-figure-and-notation-check-153/", "title": "Har bygget et tværsprogligt indholdstjek, der fejler, når en oversættelse taber et tal, det engelske angiver, og når et sprog bruger notation, det ikke bruger — og har fundet to danske beskrivelser, der manglede en metrik, og seksten ukrainske spænd, der citerede på engelsk vis.", "summary": "Atten reelle defekter lukket og klassen lukket med dem, da hver fremtidig oversættelse sammenlignes med sin engelske kilde, før den kan committes.", "content_html": "<p><strong>Situation.</strong> Den mest skadelige slags oversættelsesfejl i et CV er ikke en kejtet vending. Det er et tal, der forsvinder. En engelsk sætning, der hævder en halvtredsindstyvedobbelt forbedring, oversat til en sætning, der siger “betydeligt”, er en påstand stille trukket tilbage på ét marked og fastholdt på et andet — og ingen stavekontrol, grammatikkontrol eller menneskelig læsning af målsproget alene vil nogensinde bemærke det, for den oversatte sætning er fuldkommen god prosa.</p>\n<p><strong>Opgave.</strong> Der var brug for et tjek, som læser sprogene mod hinanden frem for hvert enkelt for sig, på de to ting, der skal overleve oversættelse: tallene og den notation, hvert sprog bruger til at skrive dem.</p>\n<p><strong>Handling.</strong> Taltjekket udtrækker hvert tal, hver procentdel, hver multiplikator og hver enhed fra den engelske streng og kræver, at hver af dem optræder i hver oversættelse af den streng, med multiplikatorformerne kortlagt pr. sprog frem for matchet ordret — den engelske halvtredsindstyve‑gange‑form svarer til en bestemt dansk vending og en bestemt ukrainsk vending, og tjekket kender kortlægningen frem for at kræve cifrene alene. Notationstjekket er spejlbilledet af det: hvert sprog har konventioner, det skal bruge, og konventioner, det ikke må bruge, herunder decimalskilletegn, tusindgruppering og anførselstegn. Ukrainsk bruger vinkelanførselstegn; engelske dobbelte anførselstegn i en ukrainsk sætning er lige så forkerte som et manglende tal og langt lettere at indføre ved kopiering. Den første fulde kørsel fandt to danske beskrivelser, hvor et måltal til stede på engelsk var faldet ud, og seksten ukrainske spænd med anførsel i engelsk stil.</p>\n<p><strong>Resultat.</strong> Atten reelle defekter lukket og klassen lukket med dem, da hver fremtidig oversættelse sammenlignes med sin engelske kilde, før den kan committes. Hvad tjekket ikke kan, er at bedømme mening: det beviser, at tallet overlevede, og at tegnsætningen er hjemmevant, og en oversættelse, der bevarer hvert tal, mens den vender påstanden om, består det uden videre.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Data governance", "Dokumentation", "Internationalisering", "Python", "Test & QA", "Data governance & datakvalitet", "Internationalisering & lokalisering", "Teknisk dokumentation" ] }, { "id": "https://engineer.company/da/portfolio/built-a-native-macos-messaging-client-in-swift-6-154/", "url": "https://engineer.company/da/portfolio/built-a-native-macos-messaging-client-in-swift-6-154/", "title": "Har bygget en native macOS‑beskedklient i Swift 6 og SwiftUI — 11.141 linjer fordelt på 53 filer — oven på C‑grænsefladen til en Rust‑kerne linket som et statisk arkiv fra en fastlåst revision.", "summary": "En hjemmehørende klient, der starter som et Mac-program, bruger platformens egne materialer og kontroller og bærer ingen indlejret browser.", "content_html": "<p><strong>Situation.</strong> Beskedprotokollen havde en velafprøvet kerne skrevet i Rust og klienter på flere platforme, men skrivebordsoplevelsen på macOS var en skal på tværs af platforme — den så ikke ud som et Mac‑program, opførte sig ikke som et, og bar en køretid, et hjemmehørende program ikke har brug for. Kernen blotlægger sin formåen gennem en C‑grænseflade, hvilket betyder, at ethvert sprog, der kan kalde C, kan bygge på den, og det kan Swift.</p>\n<p><strong>Opgave.</strong> En hjemmehørende klient skulle bygges direkte på den C‑grænseflade, i platformens nuværende sprog og nuværende grænsefladeramme, med kernen sammenkædet frem for udsendt ved siden af.</p>\n<p><strong>Handling.</strong> Programmet er 11.141 linjer Swift fordelt på treoghalvtreds filer i app‑målet, bygget på SwiftUI med kernen sammenkædet som et statisk arkiv oversat fra en fastlåst opstrømsrevision. At fastlåse revisionen frem for at følge en gren er den beslutning, der gør bygningen gentagelig: C‑grænsefladen er kontrakten, og en kerne i bevægelse ændrer kontrakten uden at ændre en linje Swift. Lagdelingen holder den usikre overflade lille — en tynd indpakning ejer hver pegepind og hver streng, der krydser grænsen, modeller over den er almindelige Swift‑værdier, og grænsefladelaget ser aldrig en rå pegepind. Alt, hvad C‑grænsefladen returnerer, har en ejerskabsregel, og at få en af dem forkert frembringer enten en lækage eller et nedbrud uden nogen oversætteradvarsel imellem, så indpakningen er der, hvor hele projektets hukommelsesdisciplin bor. Bygningen drives af en opgavekører med seksoghalvtreds mål, der dækker kerneoversættelsen, Swift‑bygningen, testsuiterne, linterne og pakningen.</p>\n<p><strong>Resultat.</strong> En hjemmehørende klient, der starter som et Mac‑program, bruger platformens egne materialer og kontroller og bærer ingen indlejret browser. Det, der ikke er gjort, bør siges: dette er ikke en udsendt udgivelse. Der er ingen notariseret distribution, ingen opdateringskanal og ét sprog i grænsefladen, så det er et fungerende program frem for et produkt, nogen anden kører.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "API'er & integration", "Designsystemer & UI", "Frontend‑udvikling", "Full stack‑udvikling", "Løsningsarkitektur", "UX/UI‑design", "Full stack‑produktudvikling", "Platform- & løsningsarkitektur", "UI/UX‑design & designsystemer" ] }, { "id": "https://engineer.company/da/portfolio/stopped-an-application-filling-memory-at-41mb-a-second-155/", "url": "https://engineer.company/da/portfolio/stopped-an-application-filling-memory-at-41mb-a-second-155/", "title": "Har stoppet en applikation i at fylde hukommelsen med 41 MB i sekundet — registrerede 111 GB komprimerede sider på en maskine med 36 GB — ved at begrænse hver hændelsesstrøm, abonnere efter hændelsestype og lægge et ratebudget på logning, hvilket tog 610.996 loglinjer ned til 1.411.", "summary": "Hukommelsen holder sig flad under vedvarende belastning, og den samme session, der frembragte 610.996 loglinjer, frembringer 1.411.", "content_html": "<p><strong>Situation.</strong> Programmet fyldte hukommelsen, indtil styresystemet slog det ihjel. Den registrerede hændelse nåede 111 GB komprimerede sider på en maskine med 36 GB, stigende med omkring 41 MB i sekundet, og logfilen for en enkelt kort session rummede 610.996 linjer. En maskine i den tilstand er ikke langsom, den er ubrugelig — nedlukningen kommer, efter at swap allerede har fået alt andet på skrivebordet til at holde op med at svare.</p>\n<p><strong>Opgave.</strong> Væksten skulle findes frem for gættes på, og hver ubegrænset vej skulle gives en grænse, for én afgrænset kø ved siden af tre uafgrænsede er ikke en rettelse.</p>\n<p><strong>Handling.</strong> Der var tre gangende årsager, og gangningen er grunden til, at det gik så hurtigt. Hændelsesstrømmen fra kernen blev forbrugt uden nogen grænse, så hændelser ankom hurtigere, end grænsefladen kunne anvende dem, og efterslæbet blev bevaret frem for kasseret. Hver abonnent modtog hver hændelse og filtrerede bagefter, så omkostningen ved én hændelse blev ganget med antallet af lyttere, og hver lytters filtrering allokerede. Og logning var ubudgetteret, så hver hændelse frembragte loglinjer — hvilket er det sammensatte led, for logningens omfang var proportionalt med omfanget af det, der gik galt. Rettelsen tog fat på alle tre: afgrænsede buffere med en udtrykkelig politik for, hvad der sker når de fyldes, abonnement efter hændelsestype så en lytter kun vækkes for hændelser, den vil have, og et hastighedsbudget på logning, der falder gentagelser sammen frem for at skrive hver enkelt. En regressionstest driver en høj hændelseshastighed og efterprøver, at hukommelsesloftet holder, så grænsen er en egenskab ved bygningen frem for en kommentar.</p>\n<p><strong>Resultat.</strong> Hukommelsen holder sig flad under vedvarende belastning, og den samme session, der frembragte 610.996 loglinjer, frembringer 1.411. Den ærlige bemærkning er, at hastighedsbudgettet på logning kasserer information: når noget går galt hurtigt nu, er optegnelsen over det bevidst ufuldstændig, og det er en handel indgået med åbne øjne mod alternativet, en maskine der går i stå.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Drift & backup", "Frontend‑udvikling", "Løsningsarkitektur", "Monitorering & observability", "Performanceoptimering", "Test & QA", "Platform- & løsningsarkitektur", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/adopted-swift-6-strict-concurrency-over-a-c-event-loop-156/", "url": "https://engineer.company/da/portfolio/adopted-swift-6-strict-concurrency-over-a-c-event-loop-156/", "title": "Har taget Swift 6 fuldstændig streng samtidighed i brug uden aktører og har bygget bro fra en blokerende C‑hændelsesløkke til hovedaktøren gennem én producent, én forbruger og én rækkefølge — efter at have fastslået, at en opgave pr. hændelse mister den rækkefølge, grænsefladen afhænger af.", "summary": "Programmet oversættes under fuldstændigt strengt samtidighedstjek uden undertrykkelser, og hændelsesrækkefølgen er en strukturel egenskab frem for et håb.", "content_html": "<p><strong>Situation.</strong> Swift 6’s fuldstændige strenge samtidighedstjek gør datakapløb til oversættelsesfejl i stedet for lejlighedsvise nedbrud. At indføre det over for et C‑bibliotek er der, hvor det bliver svært: kernens hændelsesløkke er et blokerende kald, der skal køre uden for hovedtråden for altid, og de værdier, den leverer tilbage, er pegepinde uden nogen som helst samtidighedsgarantier. Oversætteren kan ikke ræsonnere om noget af det og vil afvise alt, indtil grænsen er beskrevet udtrykkeligt.</p>\n<p><strong>Opgave.</strong> Fuldstændigt tjek skulle slås til uden nogen nødudgange, hvilket betød at udforme overgangen fra en blokerende C‑løkke til hovedaktøren frem for at sætte påtegninger uden om den.</p>\n<p><strong>Handling.</strong> Den nærliggende fremgangsmåde er en aktør pr. delsystem, og den blev forkastet på måling frem for smag. At starte en opgave pr. indkommende hændelse lader køretiden planlægge dem i vilkårlig rækkefølge, og kernens hændelsesstrøm er ordnet — en besked‑ændret‑hændelse, der overhaler den besked‑oprettet‑hændelse, den henviser til, frembringer en grænseflade, der viser en redigering af noget, der endnu ikke findes. Aktørers genindtræden gør dette værre, ikke bedre, for en aktør kan afbryde midt i en metode og behandle et andet kald. Det, der erstattede den, er bevidst enkelt: én producenttråd, der ejer den blokerende løkke, én forbruger, én kø imellem dem og et enkelt hop over på hovedaktøren til sidst. Rækkefølgen bevares, fordi der er præcis én vej, og intet overhaler noget. De usikre typer, der krydser den grænse, er pakket ind i typer, hvis trådsikkerhed efterprøves ved indpakningen frem for antages, og efterprøvningen er dokumenteret med hvorfor den holder — pegepinden ejes af én tråd og kopieres, før den overdrages.</p>\n<p><strong>Resultat.</strong> Programmet oversættes under fuldstændigt strengt samtidighedstjek uden undertrykkelser, og hændelsesrækkefølgen er en strukturel egenskab frem for et håb. Prisen er, at udformningen er mindre parallel, end den kunne være: alt ledes gennem én forbruger, og hvis den forbruger nogensinde bliver en flaskehals, vil rettelsen kræve, at man på ny udleder, hvilke hændelser der trygt kan omordnes, hvilket er præcis den analyse, dette undgik.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Drift & backup", "Frontend‑udvikling", "Løsningsarkitektur", "Performanceoptimering", "Test & QA", "Backend- & API‑udvikling", "Platform- & løsningsarkitektur", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/wrote-a-parser-that-verifies-every-ffi-call-site-157/", "url": "https://engineer.company/da/portfolio/wrote-a-parser-that-verifies-every-ffi-call-site-157/", "title": "Har skrevet en parser, der læser den rigtige C‑header på 7.308 linjer og verificerer hvert kaldssted, hver enum‑konstant og at hver pegerejende klasse er final, efter at en håndskrevet pladsholder‑header lod kald til tre fjernede funktioner kompilere, linke og crashe.", "summary": "Den defektklasse, der frembragte det oprindelige nedbrud, kan ikke gentage sig, for en forældet henvisning er nu en bygningsfejl frem for en kørselsfejl.", "content_html": "<p><strong>Situation.</strong> Tidligt i projektet blev C‑grænsefladen repræsenteret af en håndskrevet header, der beskrev de funktioner, programmet forventede. Den header oversatte, programmet blev sammenkædet, og kald til tre funktioner, der ikke længere fandtes i kernen, nåede frem til at blive kaldt og brød ned. Både oversætteren og sammenkæderen var blevet tilfredsstillet af en beskrivelse af biblioteket frem for af biblioteket, og kløften viste sig først ved kørsel.</p>\n<p><strong>Opgave.</strong> Den rigtige header — 7.308 linjer og 268 erklæringer — skulle blive myndigheden, og hver anvendelse af den i Swift‑koden skulle efterprøves mod den automatisk frem for ved gennemgang.</p>\n<p><strong>Handling.</strong> Tjekket er en parser, der læser den faktiske opstrøms‑header og opbygger mængden af funktioner, enum‑konstanter og typer, den erklærer, og derefter læser Swift‑kildekoden og opløser hvert kaldested og hver konstanthenvisning mod den mængde. Et kald til en funktion, headeren ikke erklærer, fejler bygningen. En henvisning til en enum‑konstant, der er blevet omdøbt, fejler bygningen. Det nuværende tal er 132 af de 268 erklæringer henvist til, og at vide hvilke 136 der er ubrugte, er i sig selv nyttigt, for det siger præcis, hvor meget af kernen klienten ikke har nået. Parseren håndhæver også en regel, oversætteren ikke kan: hver Swift‑klasse, der ejer en pegepind ind i kernen, skal være endelig. En ikke‑endelig klasse, der ejer en pegepind, kan nedarves, og en underklasse, der tilsidesætter deinitialisering eller tilføjer sin egen levetid, ændrer, hvornår pegepinden frigives — en brug efter frigivelse uden noget usikkert nøgleord i nærheden. Reglen tjekkes ved navn på tværs af hele træet.</p>\n<p><strong>Resultat.</strong> Den defektklasse, der frembragte det oprindelige nedbrud, kan ikke gentage sig, for en forældet henvisning er nu en bygningsfejl frem for en kørselsfejl. Begrænsningen er, at parseren forstår headerens erklæringer og ikke dens betydning: den beviser, at en funktion findes med et matchende navn, og en funktion, hvis betydning eller ejerskabsregel ændrede sig opstrøms, mens signaturen blev bevaret, slipper igennem uden bemærkning.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "API'er & integration", "Automatisering & CI/CD", "Backend‑udvikling", "Dokumentation", "Sikkerhed", "Test & QA", "Backend- & API‑udvikling", "DevOps & CI/CD‑automatisering", "Teknisk dokumentation" ] }, { "id": "https://engineer.company/da/portfolio/took-the-test-suite-under-nine-seconds-158/", "url": "https://engineer.company/da/portfolio/took-the-test-suite-under-nine-seconds-158/", "title": "Har taget testsuiten fra seks tests over en grænse på tres sekunder til 135 beståede på 8,9 sekunder ved at profilere hovedtråden og fjerne de to kald, den sad inde i i 3.989 ud af 4.017 stikprøver.", "summary": "Suiten gik fra seks test over tres sekunder — 74 sekunders vægur — til 135 test bestået på 8,9, hvilket bringer den inden for det vindue, hvor den kører ved…", "content_html": "<p><strong>Situation.</strong> Testsuiten havde en tidsgrænse på tres sekunder, og seks test lå over den. En testsuite, der tager over et minut, holder op med at blive kørt før hver ændring, og en suite, der ikke køres før hver ændring, er en rapport om fortiden. Instinktet i den situation er at hæve grænsen, og at hæve grænsen er, hvordan en suite når ti minutter.</p>\n<p><strong>Opgave.</strong> Tiden skulle findes frem for budgetteres til, hvilket betød at profilere suiten i stedet for at ræsonnere om, hvilke test der så dyre ud.</p>\n<p><strong>Handling.</strong> Profilen blev taget på hovedtråden, mens suiten kørte, og resultatet modsagde gættet. Hovedtråden sad inde i to kald i 3.989 stikprøver ud af 4.017 — så de langsomme test var ikke dem, der udførte mest arbejde, og næsten hver eneste test kom gennem de samme to steder. Det første var en fast ventetid brugt til at lade asynkront arbejde falde til ro før efterprøvning — reelt en søvn, betalt af hver test, der rørte hændelsesvejen, uanset om arbejdet allerede var færdigt. Den blev erstattet af at vente på den faktiske betingelse med en tidsfrist, så en test, der er klar på fem millisekunder, tager fem millisekunder, og kun en test, der reelt sidder fast, betaler den fulde ventetid. Det andet var opsætning pr. test, der genopbyggede et dyrt fikstur hver gang, hvor fiksturet var skrivebeskyttet og kunne bygges én gang for suiten. Ingen af dem lå i en test, nogen ville have udpeget som langsom; begge lå i den fælles vej, hvilket er grunden til, at hele suiten var ensartet langsom frem for at nogle få test var afvigere.</p>\n<p><strong>Resultat.</strong> Suiten gik fra seks test over tres sekunder — 74 sekunders vægur — til 135 test bestået på 8,9, hvilket bringer den inden for det vindue, hvor den kører ved hver gemning. Forbeholdet er, at det fælles skrivebeskyttede fikstur nu er et koblingspunkt — en test, der ændrer det, vil frembringe en fejl i en anden test, og suitens hastighed afhænger af en disciplin, oversætteren ikke håndhæver.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Performanceoptimering", "Test & QA", "DevOps & CI/CD‑automatisering", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/named-all-seventeen-interface-icons-for-screen-readers-159/", "url": "https://engineer.company/da/portfolio/named-all-seventeen-interface-icons-for-screen-readers-159/", "title": "Har navngivet hver eneste ikonknap i grænsefladen for skærmlæsere efter at have fundet send‑knappen annonceret som \"arrow up circle, button\", og har skrevet den linter, der kræver etiketten inden for otte linjer fra ikonet.", "summary": "Alle sytten kontroller annoncerer deres funktion, og en attende kan ikke tilføjes uden en. Reglens upræcished er reel og angivet dér, hvor den defineres: den…", "content_html": "<p><strong>Situation.</strong> Grænsefladen brugte systemsymboler til sine kontroller, og et symbol uden en tilgængelighedsetiket læses op af skærmlæseren med sit eget interne navn. Send‑knappen blev annonceret som “pil op cirkel, knap”. Det gjorde hver anden kontrol med kun et ikon i programmet også, på sin egen måde — sytten af dem beskrev deres eget billede i stedet for deres funktion.</p>\n<p><strong>Opgave.</strong> Hver kontrol med kun et ikon havde brug for en etiket, der beskriver hvad den gør, og rettelsen skulle følges af et tjek, for det næste ikon, der blev tilføjet, ville ellers genindføre defekten med det samme.</p>\n<p><strong>Handling.</strong> Hver af de sytten fik en etiket, der navngiver handlingen frem for formen, og hvor kontrollens betydning afhænger af tilstand, følger etiketten tilstanden i stedet for at være fast. Tjekket er den del, der er værd at beskrive, for en almen regel om “er dette tilgængeligt” findes ikke til dette. Linteren leder efter den konstruktion, der skaber en kontrol med kun et ikon, og kræver derefter en tilgængelighedsetiket inden for otte linjer af den. Otte linjer er en bevidst grov tommelfingerregel, og den blev valgt ved måling: den er lang nok til at spænde over hver legitim måde, kodebasen skriver en af disse kontroller med dens modifikatorer, og kort nok til at en etiket knyttet til en anden visning længere nede ikke ved et uheld opfylder den. En præcis regel her ville skulle forstå SwiftUI’s modifikatorkæder som et træ, og den billige nærhedsregel fanger den faktiske fejl — som ikke er en fejlmærket kontrol, det er en umærket.</p>\n<p><strong>Resultat.</strong> Alle sytten kontroller annoncerer deres funktion, og en attende kan ikke tilføjes uden en. Reglens upræcished er reel og angivet dér, hvor den defineres: den kan opfyldes af en etiket på en nabovisning, så den beviser, at en etiket findes i nærheden, frem for at bevise at etiketten er korrekt, og korrekthed kræver stadig, at nogen lytter til den.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Designsystemer & UI", "Dokumentation", "Frontend‑udvikling", "Test & QA", "UX/UI‑design", "UI/UX‑design & designsystemer" ] }, { "id": "https://engineer.company/da/portfolio/built-a-48-token-design-system-160/", "url": "https://engineer.company/da/portfolio/built-a-48-token-design-system-160/", "title": "Har bygget et designsystem på 48 tokens oven på platformens eget glasmateriale og har skrevet den linter, der afviser et magisk tal, en hårdkodet skriftstørrelse eller en animation, der ignorerer præferencen for reduceret bevægelse.", "summary": "Otteogfyrre tokens beskriver hele grænsefladen, udseendet følger systemet, og ingen af de tre former for smuldren kan committes.", "content_html": "<p><strong>Situation.</strong> En grænseflade bygget ved at tilføje visninger ophober værdier: en hjørneradius her, en fjortenpunktsskrift der, en animation på to tiendedele sekund et andet sted. Hver for sig er hver af dem rimelig, og tilsammen er de et design, der ikke kan ændres, for der findes ikke noget, der hedder “hjørneradiussen” at ændre — der er fyrre af dem, en smule forskellige, spredt over halvtreds filer.</p>\n<p><strong>Opgave.</strong> Det visuelle ordforråd skulle skæres ned til en navngiven mængde, udtrykt mod platformens eget materiale frem for genopfundet, og forsvaret af et tjek, så det forbliver nedskåret.</p>\n<p><strong>Handling.</strong> Systemet er otteogfyrre tokens i seks grupper: mellemrum, typografi, farve, radius, ophøjning og bevægelse. Farve og materiale er bygget på platformens semantiske farver og dens glasmateriale frem for faste værdier, hvilket er det, der får grænsefladen til at følge systemets udseende, accentfarven og kontrastindstillingerne uden nogen kode, der holder øje med dem. Typografi kortlægges til platformens tekststile, så den skalerer med brugerens størrelsespræference i stedet for at fastnagle en punktværdi. Linteren afviser tre ting: en talliteral hvor et mellemrums- eller radiustoken hører til, en hårdkodet skriftstørrelse hvor som helst, og en animation erklæret uden at respektere præferencen for reduceret bevægelse. Den tredje er den, der ellers ville smuldre hurtigst, for en animation tilføjes i et øjeblik af finpudsning, og præferencetjekket er den del, der bliver sprunget over — så reglen gør selve animationen umulig at skrive uden det frem for at bede nogen huske det.</p>\n<p><strong>Resultat.</strong> Otteogfyrre tokens beskriver hele grænsefladen, udseendet følger systemet, og ingen af de tre former for smuldren kan committes. Begrænsningen er, at et tokensystem begrænser ensartethed og ikke kvalitet — alt passer nu sammen, og at passe sammen er ikke det samme som at være veludformet, hvilket er en vurdering, ingen linter i dette projekt foretager.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Designsystemer & UI", "Frontend‑udvikling", "Test & QA", "UX/UI‑design", "Brand, marketing & SEO", "UI/UX‑design & designsystemer" ] }, { "id": "https://engineer.company/da/portfolio/reached-the-unused-half-of-the-messaging-core-161/", "url": "https://engineer.company/da/portfolio/reached-the-unused-half-of-the-messaging-core-161/", "title": "Har nået den halvdel af beskedkernen, applikationen aldrig havde brugt — backupoverførsel, forsvindende beskeder, redigering og gensendelse af beskeder, verificerede invitationer, proxyer og krypteringspolitik — og har drevet hver test mod det rigtige bibliotek uden mocks.", "summary": "Den tidligere uopnåede halvdel af kernen drives af test mod det rigtige bibliotek, og indpakningen bærer det højeste gulv i projektet.", "content_html": "<p><strong>Situation.</strong> Klienten brugte omtrent halvdelen af det, beskedkernen tilbyder. Den ubrugte halvdel var ikke obskur — sikkerhedskopioverførsel mellem enheder, forsvindende beskeder, redigering og gensendelse af beskeder, verificerede indbydelseslinks, proxykonfiguration og krypteringspolitikken for en samtale. Hver af dem er en funktion, en bruger ville forvente, og hver af dem var et utestet område af C‑grænsefladen, hvilket er den farligere kendsgerning, for et utestet område af en C‑grænseflade er der, hvor ejerskabsfejlene bor.</p>\n<p><strong>Opgave.</strong> Den uopnåede formåen skulle drives og dækkes, og testene skulle køre mod det rigtige bibliotek frem for mod en stedfortræder.</p>\n<p><strong>Handling.</strong> Reglen, der blev vedtaget, var ingen attrapper for kernen. En attrap af en C‑grænseflade indkoder udviklerens tro om, hvad biblioteket gør, og hver defekt værd at finde her er et sted, hvor den tro er forkert — så en bestået attrapbaseret test er bevis om attrappen. I stedet opretter testene rigtige konti i midlertidige mapper, driver det rigtige bibliotek og efterprøver, hvad det faktisk returnerer, hvor hver suite rydder op efter sin egen tilstand. Det var det, der gjorde dækningen meningsfuld: at udøve sikkerhedskopioverførsel betød at håndtere en rigtig overførsels tilstandsmaskine og dens fejlveje, og at udøve verificerede indbydelser betød at konstruere det rigtige linkformat og få biblioteket til at fortolke det. Dækningsgulve blev sat pr. lag frem for som ét tal, ved fireogfirs procent for kerneindpakningen, femoghalvfjerds for hjælpefunktioner og femogtres for modeller, ud fra den betragtning at det lag, der rører rå pegepinde, bør holdes højest, og at en model, der mest består af lagrede egenskaber, ikke bør polstres med test for at ramme et gennemsnit.</p>\n<p><strong>Resultat.</strong> Den tidligere uopnåede halvdel af kernen drives af test mod det rigtige bibliotek, og indpakningen bærer det højeste gulv i projektet. Prisen er hastighed og forudsigelighed: test mod det rigtige bibliotek er langsommere end attrapper, og de kan fejle af miljømæssige grunde, hvilket er prisen for, at de kan fejle af rigtige.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "API'er & integration", "Automatisering & CI/CD", "Backend‑udvikling", "Drift & backup", "Sikkerhed", "Test & QA", "Backend- & API‑udvikling", "DevOps & CI/CD‑automatisering", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/rewrote-the-error-messages-against-a-tone-standard-162/", "url": "https://engineer.company/da/portfolio/rewrote-the-error-messages-against-a-tone-standard-162/", "title": "Har omskrevet applikationens fejlbeskeder efter en skreven tonestandard, efter at et afvist login gav brugeren skylden for at have tastet forkert, mens udbyderen i virkeligheden krævede en applikationsspecifik adgangskode, og har dækket det med en test, der nævner udbyderen.", "summary": "Fejlfladen følger en angivet standard, og det tilfælde, der foranledigede den, er dækket af en test, der fejler på den gamle tekst.", "content_html": "<p><strong>Situation.</strong> En indlogning mod en stor mailudbyder mislykkedes, og programmet fortalte brugeren, at adgangskoden var forkert. Den var ikke forkert. Den udbyder kræver en programspecifik adgangskode til tredjepartsklienter og afviser kontoens adgangskode uanset hvor omhyggeligt den tastes. Beskeden sendte brugeren ud i at genindtaste noget, der aldrig kunne virke, og den faktiske anvisning — gå hen og frembring en anden slags adgangskode — optrådte intetsteds.</p>\n<p><strong>Opgave.</strong> Fejlbeskederne skulle omskrives efter en skreven standard frem for lappes én ad gangen, eftersom denne var det synlige tilfælde af en vane, der løb gennem dem alle.</p>\n<p><strong>Handling.</strong> Standarden har tre krav: sig hvad der skete, antyd aldrig at brugeren gjorde noget forkert, når årsagen ligger andetsteds, og giv den næste handling, når der findes en. Anvendt på tværs af fejlfladen overtrådte de fleste beskeder mindst ét — flere var det underliggende biblioteks fejlstreng ført videre, hvilket beskriver en tilstand for en programmør frem for en situation for et menneske. Udbydertilfældet blev omskrevet til at navngive udbyderen, angive at den kræver en programspecifik adgangskode til andre klienter, og sige hvor man opretter en. Testen er det, der forhindrer tilbagefald, og den er bevidst konkret: den driver en mislykket indlogning mod den udbyder og efterprøver, at beskeden indeholder udbyderens navn og den vending, der beskriver den krævede legitimationstype. En test, der kun efterprøvede at en fejl viste sig, ville bestå på den oprindelige forkerte besked, så efterprøvningen ligger på indholdet, hvilket er den eneste del, der nogensinde var i stykker.</p>\n<p><strong>Resultat.</strong> Fejlfladen følger en angivet standard, og det tilfælde, der foranledigede den, er dækket af en test, der fejler på den gamle tekst. Det, der forbliver uafklaret, er omfanget: standarden håndhæves ved gennemgang og ved én test på én besked, og de øvrige udbydere med deres egne særlige krav har ingen tilsvarende test, så klassen er dokumenteret frem for lukket.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Dokumentation", "Frontend‑udvikling", "Produkt & krav", "Sikkerhed", "Test & QA", "UX/UI‑design", "Produktstrategi & kravspecifikation", "Teknisk dokumentation", "UI/UX‑design & designsystemer" ] }, { "id": "https://engineer.company/da/portfolio/audited-644-rust-crates-for-licence-compatibility-163/", "url": "https://engineer.company/da/portfolio/audited-644-rust-crates-for-licence-compatibility-163/", "title": "Har auditeret 644 Rust‑crates for licenskompatibilitet ved hvert build og har bevist, at tjekket udløses, ved at omskrive én crates licens og ved at flytte den fastlåste kernerevision uden at regenerere.", "summary": "Distributionspositionen tjekkes ved hver bygning, og tjekket vides at virke, fordi det blev bragt til at fejle, frem for fordi det aldrig har sagt noget.", "content_html": "<p><strong>Situation.</strong> At sammenkæde en Rust‑kerne ind i et udsendt program betyder at udsende alt, den kerne afhænger af. Afhængighedsgrafen er 644 crates. Hver af dem bærer en licens, nogle bærer mere end én, og en enkelt copyleft‑crate, der ankommer tre niveauer nede gennem en rutinemæssig versionsopdatering, ændrer, hvad programmet som helhed må distribueres under — tavst, i en låsefil ingen læser linje for linje.</p>\n<p><strong>Opgave.</strong> Licenspositionen skulle efterprøves ved hver bygning frem for gennemgås én gang, med en udtrykkelig tilladelsesliste, så en ændring i grafen er en bygningsfejl og ikke en opdagelse gjort senere af en anden.</p>\n<p><strong>Handling.</strong> Revisionen opløser den fulde transitive graf og tjekker hver crates licensudtryk mod en liste over vilkår, projektet accepterer, og evaluerer de booleske udtryk korrekt — en crate, der tilbyder et valg mellem to licenser, er acceptabel, hvis en af dem står på listen, og en crate, der kræver begge, er kun acceptabel, hvis begge gør. Alt uden match fejler bygningen frem for at advare, og at føje et vilkår til tilladelseslisten er en bevidst redigering med en begrundelse. Et tjek, der aldrig har fejlet, kan ikke skelnes fra et, der ikke kan fejle, så det blev bragt til at fejle med vilje, to gange. Én gang ved at omskrive en crates licensudtryk til noget, listen ikke accepterer, hvilket er formen på en crate, der ændrer sine vilkår opstrøms mellem versioner — det tilfælde ingen menneskelig gennemgang fanger, fordi selve craten ikke er ny. Én gang ved at flytte den fastlåste kernerevision uden at genskabe revisionen, hvilket er formen på en kerneopdatering, der stille bringer en ny afhængighed med sig. Begge provokationer fejlede bygningen som tilsigtet, og tjekket blev efterladt på plads frem for udvidet.</p>\n<p><strong>Resultat.</strong> Distributionspositionen tjekkes ved hver bygning, og tjekket vides at virke, fordi det blev bragt til at fejle, frem for fordi det aldrig har sagt noget. Før det fandtes, fremsatte pakken, licensfilen og projektets eget README alle en påstand om 644 crates, som intet havde efterprøvet. Begrænsningen er præcis og bør ikke overdrives: tjekket læser de erklærede licensmetadata, og metadata kan være forkerte eller ufuldstændige. Det beviser intet om en crate, der fejlerklærer sig selv, og det er ikke juridisk rådgivning.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Data governance", "DevOps", "Dokumentation", "Sikkerhed", "Data governance & datakvalitet", "DevOps & CI/CD‑automatisering", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/built-the-visual-identity-from-two-drawings-164/", "url": "https://engineer.company/da/portfolio/built-the-visual-identity-from-two-drawings-164/", "title": "Har bygget virksomhedens ikon- og favicon‑sæt ud fra to håndlavede tegninger, hvor hvert afledt aktiv genskabes af et script og et tjek fejler, når en afledt fil er committet før sin kilde.", "summary": "To tegninger frembringer hvert udgivet ikon, genskabelse er én kommando, og et forældet aktiv fejler bygningen.", "content_html": "<p><strong>Situation.</strong> Et lille firmas visuelle identitet købes som regel, frembringes maskinelt eller sættes sammen af arkivmateriale, og resultatet er en identitet, der tilhører ingen. Det alternative problem er værre: håndlavet grafik, der kun findes som eksporterede filer, hvor kildetegningen er gået tabt, eksporterne redigeres direkte, og inden for et år er de versioner, der er i brug, uenige med hinanden, og der er ingen original tilbage til at afgøre det.</p>\n<p><strong>Opgave.</strong> Identiteten skulle tegnes frem for skaffes, og rørledningen fra tegning til udgivet aktiv skulle være gentagelig, så hver fil på webstedet kan udledes frem for opbevares.</p>\n<p><strong>Handling.</strong> To håndlavede tegninger ligger til grund for det frembragte billedmateriale — firmamærket og tandhjulet, der blev til faviconet — og hvert ikon sitet udgiver frembringes ud fra dem. Mærket er en rastertegning; tandhjulet er en vektor. Ud fra disse frembringer skripter hver afledt form: favicon‑sættet i dets krævede størrelser, berøringsikonerne, billederne til social forhåndsvisning, de tilpassede rastervarianter i hver bredde og i hvert moderne format samt de temaspecifikke versioner. Frembringelsen er en opgave, enhver kan køre, så spørgsmålet “hvor kommer denne fil fra” har et svar, der er en kommando. Forældelsestjekket er det stykke, der får det til at holde: det sammenligner ændringstidspunktet for hvert afledt aktiv med dets kilde og fejler, når en afledt fil er ældre, hvilket fanger netop den fejl, denne udformning findes for at forhindre — nogen redigerer kilden, glemmer at genskabe, og det udgivne websted bliver ved med at vise grafik, der ikke længere svarer til originalen. Reglen om, at afledte filer aldrig redigeres i hånden, er angivet dér, hvor kilderne bor.</p>\n<p><strong>Resultat.</strong> To tegninger frembringer hvert udgivet ikon, genskabelse er én kommando, og et forældet aktiv fejler bygningen. Afvejningen er en hård afhængighed af værktøjskæden: de afledte filer er committet, så webstedet bygger hvor som helst, men at ændre identiteten kræver, at frembringelsesværktøjet stadig virker, og et kildeformat, der holder op med at kunne læses, tager hele identiteten med sig.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Brand & marketing", "Designsystemer & UI", "Test & QA", "UX/UI‑design", "Webudvikling", "Brand, marketing & SEO", "UI/UX‑design & designsystemer", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/held-five-repositories-to-one-history-standard-165/", "url": "https://engineer.company/da/portfolio/held-five-repositories-to-one-history-standard-165/", "title": "Har holdt fire kodebaser til én historikstandard — konventionel, uden emojis, uden attributionslinjer, håndhævet af en commit‑message‑hook — sammen med 46 instruktionsdokumenter, der styrer, hvordan arbejdet udføres.", "summary": "Fem lagre deler ét historikformat og én anvisningsstruktur, begge håndhævet af hooks frem for af disciplin.", "content_html": "<p><strong>Situation.</strong> Fem lagre bygget over atten måneder af én person er den situation, hvor proces er lettest at springe over, for der er ingen at koordinere med, og prisen for en ulæselig historik betales helt af et fremtidigt jeg, der endnu ikke har beklaget sig. Det er også den situation, hvor en usammenhængende historik er mest sandsynlig, eftersom hvert lager kan glide ind i sine egne vaner uden noget, der trækker dem sammen.</p>\n<p><strong>Opgave.</strong> Én standard for historik og én standard for anvisninger skulle gælde på tværs af hvert lager, arbejdet skrives i, og den skulle håndhæves maskinelt frem for huskes.</p>\n<p><strong>Handling.</strong> Commit‑standarden er et konventionelt præfiks, der navngiver ændringens art, og et virkefelt, en emnelinje under en fast længde, ingen emojis og ingen tilskrivningslinjer af nogen art — det sidste fordi en linje, der krediterer et værktøj, ikke er en kendsgerning om ændringen, og historik er til kendsgerninger om ændringer. En hook på commit‑beskeden afviser alt, der ikke retter sig efter det, i hvert lager, der bærer arbejde, så standarden er en egenskab ved lageret frem for ved den, der committer. Fordelingen i det største lager viser, hvad arbejdet faktisk var: 156 funktions‑commits, 133 rettelser, 128 dokumentation, 81 husholdning, 26 omskrivninger, 20 stil, 5 ydeevne og 3 test. Dokumentation ligger tæt nok på rettelser til at være værd at bemærke, og det er en følge af den anden halvdel af dette — 46 anvisningsdokumenter på tværs af de fire, hvert dækkende ét område, hvert skrevet som regler frem for beskrivelse, alle nåelige fra ét indgangsdokument pr. lager, så der er ét sted at begynde. En dokumentationslinter håndhæver størrelsesbudgetter pr. fil og medlemskab af indekset, så mængden forbliver overskuelig frem for at vokse til et arkiv.</p>\n<p><strong>Resultat.</strong> Fem lagre deler ét historikformat og én anvisningsstruktur, begge håndhævet af hooks frem for af disciplin. Hvad dette ikke gør, er at gøre historikken god: formatet tjekkes, og indholdet gør ikke, så en emnelinje, der retter sig efter formatet og beskriver intet, slipper igennem præcis lige så godt som en, der forklarer ændringen.</p>\n", "date_published": "2026-09-10T14:54:29+02:00", "date_modified": "2026-09-10T14:54:29+02:00", "language": "da", "tags": [ "Agile & Scrum", "Automatisering & CI/CD", "DevOps", "Dokumentation", "Projektledelse", "Teknisk ledelse", "DevOps & CI/CD‑automatisering", "Projektledelse (Agile)", "Teknisk dokumentation", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/increased-brand-popularity-by-200x-through-successful-brand-1/", "url": "https://engineer.company/da/portfolio/increased-brand-popularity-by-200x-through-successful-brand-1/", "title": "Har øget brandets popularitet 200 gange gennem succesfuld brandudvikling på tværs af offline- og onlineplatforme.", "summary": "Øgede brandets rækkevidde omkring 200× på tværs af offline- og onlineplatforme — fra ukendt nytilkommen til et genkendt navn.", "content_html": "<p><strong>Situation.</strong> Engineer ApS var en helt ny konsulentvirksomhed med reel teknisk dybde og næsten ingen, der havde hørt om den. Det er en bestemt slags frustrerende: kunnen er der, arbejdet ville være godt, men intet af det betyder noget, hvis de kunder og partnere, der ville have det, ikke ved, man findes. En ung virksomhed skal ses, før den kan vinde noget som helst.</p>\n<p><strong>Opgave.</strong> Opgaven var at opbygge et brand, folk faktisk ville genkende — på tværs af både offline- og onlinesiden — og at omsætte virksomhedens tekniske troværdighed til synlig tilstedeværelse på markedet frem for at lade den ligge som en velbevaret hemmelighed.</p>\n<p><strong>Handling.</strong> Brandet blev bygget bevidst og holdt konsistent. Det startede med en klar identitet — en tone, et visuelt sprog og en portefølje, der førte an med konkrete tekniske resultater frem for det vage “vi skaber værdi”-sprog, alle andre bruger. Så kørte det på tværs af de kanaler, der betyder noget for den slags virksomhed — websitet, LinkedIn, GitHub, fysiske arrangementer — med hvert kontaktpunkt, der sagde det samme frem for hver at drive af på sin egen måde. Den røde tråd var at føre an med rigtige case‑studier og rigtige resultater, så troværdigheden var noget, man kunne se bevis for, ikke bare en påstand.</p>\n<p><strong>Resultat.</strong> Brandets rækkevidde voksede omkring 200 gange på tværs af offline- og onlineplatforme — en ukendt nytilkommen blev til noget, folk faktisk genkendte. Og det var ikke forfængelig rækkevidde; synligheden begyndte at producere en stabil strøm af indgående henvendelser og muligheder, der simpelthen ikke havde været der før.</p>\n", "date_published": "2026-09-10T09:22:24+02:00", "date_modified": "2026-09-10T09:22:24+02:00", "language": "da", "tags": [ "Brand & marketing", "Produkt & krav", "Stakeholder & rapportering", "Brand, marketing & SEO", "Produktstrategi & kravspecifikation" ] }, { "id": "https://engineer.company/da/portfolio/drove-brand-engagement-and-loyalty-by-100-through-2/", "url": "https://engineer.company/da/portfolio/drove-brand-engagement-and-loyalty-by-100-through-2/", "title": "Har øget brandengagement og -loyalitet med 100 % gennem analyse af markedstendenser og indsigt i forbrugeradfærd.", "summary": "Fordoblede brandengagement og -loyalitet (+100 %) via analyse af markedstendenser og forbrugeradfærd — et publikum, der kom tilbage.", "content_html": "<p><strong>Situation.</strong> Efterhånden som synligheden klatrede, nåede Engineer ApS flere mennesker — men engagementet var tyndt. Folk lagde mærke til det og gik videre; den tidlige interesse blev ikke til relationer, der holdt. Rækkevidde uden engagement er bare støj, og brandet lavede støj mere end forbindelser.</p>\n<p><strong>Opgave.</strong> Målet var at uddybe engagementet og loyaliteten, og gøre det ved faktisk at kigge på, hvad markedet og publikummet reagerede på, frem for at stole på mavefornemmelse.</p>\n<p><strong>Handling.</strong> Så brandet blev datainformeret i stedet for intuitionsstyret. Det betød at kigge på markedstendenserne og på, hvordan publikummet faktisk opførte sig på tværs af kanalerne — ikke hvad nogen antog, de ville kunne lide, men hvad de påviseligt engagerede sig i. Det afslørede de emner og formater, der trak ægte opmærksomhed, og indholdet og opsøgningen blev styret mod dem. Den vigtige del var at stramme loopet: se, hvad der landede, udgiv mere af den form næste gang, og lad hver cyklus være en smule bedre rettet end den forrige.</p>\n<p><strong>Resultat.</strong> Engagement og loyalitet blev fordoblet — en forbedring på 100 % — med et publikum, der kom tilbage og engagerede sig frem for at kigge én gang og gå, og markant stærkere relationer til potentielle kunder og partnere. Brandet holdt op med at udsende ud i tomrummet og begyndte at opbygge noget, der kom tilbage.</p>\n", "date_published": "2026-09-10T09:22:24+02:00", "date_modified": "2026-09-10T09:22:24+02:00", "language": "da", "tags": [ "Brand & marketing", "Dataanalyse", "Produkt & krav", "Stakeholder & rapportering", "Brand, marketing & SEO", "Dataanalyse & BI‑dashboards", "Produktstrategi & kravspecifikation" ] }, { "id": "https://engineer.company/da/portfolio/designed-a-comprehensive-infrastructure-framework-for-dtu-impacting-3/", "url": "https://engineer.company/da/portfolio/designed-a-comprehensive-infrastructure-framework-for-dtu-impacting-3/", "title": "Har designet en omfattende infrastrukturramme for DTU, der berører 14 afdelinger, med fleksible moduler, samlede datapipelines og strukturerede supportstrategier med henblik på langsigtet udbredelse.", "summary": "Designede en omfattende infrastrukturramme for DTU på tværs af 14 afdelinger — modulær, med samlede datapipelines og supportstrategier.", "content_html": "<p><strong>Situation.</strong> På et forskningsinstitut bestående af 14 forskellige forskningsgrupper arbejdede hver gruppe med forskellige datakilder, formater, skalaer og softwareværktøjer. Den tekniske ekspertise og de tilgængelige IT‑ressourcer varierede meget på tværs af grupperne. Mens nogle få havde formået at skabe og udrulle skræddersyede IT‑løsninger, kæmpede mange med kompleksiteten af deres datainfrastrukturbehov, hvilket tog værdifuld tid og fokus fra deres kerneforskning.</p>\n<p><strong>Opgave.</strong> Opgaven var at udtænke en løsning, der ville give forskerne mulighed for at fokusere på deres videnskabelige arbejde frem for IT‑udfordringer. Målet var at designe og implementere en skalerbar, institutdækkende datainfrastruktur, der kunne rumme de brede og forskelligartede behov hos størstedelen af forskningsgrupperne.</p>\n<p><strong>Handling.</strong> En robust og fremtidssikret infrastrukturplan blev udviklet, der balancerede fleksibilitet og standardisering. Planen skitserede nøglekomponenter såsom modulær arkitektur, integrationsveje for forskellige datakilder, brugervenlige grænseflader tilpasset varierende tekniske niveauer samt skalerbare lagrings- og behandlingsløsninger. Den omfattede også strategier for onboarding, support og governance for at sikre udbredelse og bæredygtighed.</p>\n<p><strong>Resultat.</strong> Den resulterende infrastrukturplan var både teknisk solid og strategisk afstemt med instituttets forskningsmål. Den forenede visionen for datahåndtering på tværs af organisationen, gav en klar vej til at reducere IT‑byrden på forskerne og lagde fundamentet for et fælles, effektivt og fremtidsklart forskningsdatamiljø. Planen blev vel modtaget for sin inklusivitet, klarhed og tilpasningsevne og satte en stærk retning for instituttets transformation af datainfrastrukturen.</p>\n", "date_published": "2026-09-10T09:22:24+02:00", "date_modified": "2026-09-10T09:22:24+02:00", "language": "da", "tags": [ "Data engineering", "Data governance", "Datapipelines (ETL/ELT)", "Dokumentation", "Infrastruktur", "Løsningsarkitektur", "Platformarkitektur", "Stakeholder & rapportering", "Teknisk ledelse", "Data governance & datakvalitet", "Platform- & løsningsarkitektur", "Teknisk dokumentation", "Teknisk ledelse & rådgivning", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/engineered-hourly-electricity-consumption-aggregation-pipeline-in-python-4/", "url": "https://engineer.company/da/portfolio/engineered-hourly-electricity-consumption-aggregation-pipeline-in-python-4/", "title": "Har udviklet en pipeline til timebaseret aggregering af elforbrug i Python / SQL / Bash + Jq, der opnår 180 ms for 30‑dages datasæt på tværs af heterogene JSONL‑kilder.", "summary": "Byggede en Python/SQL/Bash-pipeline til elforbrug, der aggregerer 30-dages datasæt på 180 ms på tværs af heterogene JSONL-kilder.", "content_html": "<p><strong>Situation.</strong> Virksomheden behandler store mængder heterogene eldata fra flere datakilder, hver med sin egen rapporteringsfrekvens — fra timeintervaller til 15‑minutters intervaller og i nogle tilfælde uregelmæssige tidsstempler. Denne variabilitet gør det udfordrende at skabe et sammenhængende, sammenligneligt datasæt. For at understøtte præcis energianalyse skal disse data normaliseres til konsistente timeforbrugsværdier, aggregeret pr. zone.</p>\n<p><strong>Opgave.</strong> Målet var at tage rå event‑data fra et historisk datasæt (i JSONL‑format) og konvertere det til timeafstemte elforbrugstal, struktureret som én række pr. time og pr. zone. Konkret indebar opgaven at afstemme tidsstemplede data til strikse timeintervaller, aggregere den samlede elproduktion inden for hvert interval, indregne grænseoverskridende udveksling ved at lægge import til og trække eksport fra, og udskrive de endelige værdier i et struktureret, skalerbart format. Løsningen skulle desuden være effektiv nok til at skalere over lange tidsvinduer (30+ dage) og på tværs af flere lande/zoner.</p>\n<p><strong>Handling.</strong> Tre forskellige varianter af løsningen blev implementeret med henholdsvis Python, JQ (til JSON‑behandling på kommandolinjen) og SQL, hver optimeret til forskellige kontekster. Python blev valgt for sin fleksibilitet og evne til effektivt at håndtere in‑memory‑transformationer: en pipeline, der parser JSONL‑filerne, resampler tidsserien til timeintervaller, aggregerer produktion og beregner nettoelforbrug (produktion + import − eksport) og eksporterer resultaterne. JQ gav en hurtig løsning med minimale afhængigheder, og i PostgreSQL blev dataene importeret, normaliserede tabeller oprettet og en række SQL‑forespørgsler skrevet. For at vurdere ydeevnen blev hver løsning benchmarket med både 1‑dags og 30‑dages datasæt.</p>\n<p><strong>Resultat.</strong> Python‑implementeringen viste sig som den hurtigste og mest skalerbare løsning og gennemførte 1‑dags databehandling på blot 34 ms og 30‑dages datasættet på 180 ms. Det bekræftede egnetheden til større tidsvinduer med sub‑sekund‑ydeevne og gav samtidig klar, vedligeholdelig kode, der let kunne udvides til flere zoner eller integreres i en ETL‑pipeline.</p>\n", "date_published": "2026-09-10T09:22:24+02:00", "date_modified": "2026-09-10T09:22:24+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Data engineering", "Databaser", "Datapipelines (ETL/ELT)", "Dataanalyse", "Performanceoptimering", "PostgreSQL", "Python", "SQL", "Databaseoptimering", "Dataanalyse & BI‑dashboards", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/architected-developed-implemented-supported-infrastructure-data-processing-and-55/", "url": "https://engineer.company/da/portfolio/architected-developed-implemented-supported-infrastructure-data-processing-and-55/", "title": "Arkitekt, udviklet, implementeret, understøttet infrastruktur, databehandling og kortapplikationen i 2 år uden pause, uden weekender, helligdage eller ferie, 10–14 timer om dagen.", "summary": "Arkitekterede, byggede og driftede infrastruktur, databehandling og kortapp i to år uden pause — produktets pålidelige rygrad.", "content_html": "<p><strong>Situation.</strong> En green energy‑startup i en tidlig fase var afhængig af én platform til at spore, overvåge og optimere vedvarende energiaktiver, men havde hverken et dedikeret infrastrukturteam eller en etableret ingeniørorganisation til at bygge og drive den. Hele det tekniske fundament — cloud‑infrastruktur, datapipelines og den kundevendte GIS‑kortapplikation — skulle skabes og holdes kørende uafbrudt, på et marked hvor enhver nedetid eller datamangel direkte svækkede kundernes tillid og omsætningen.</p>\n<p><strong>Opgave.</strong> Opgaven var egenhændigt at arkitektere, bygge og drive hele systemet fra ende til anden — på tværs af platform- og dataingeniørarbejde, DevOps og site reliability. Ud over at skrive softwaren betød det at eje produktionsmiljøet: at provisionere og hærde infrastruktur, designe databehandlingslaget der fodrede kortet, og garantere at applikationen forblev tilgængelig døgnet rundt for en voksende kundebase — alt sammen inden for en hurtig startups rammer og ubønhørlige tempo.</p>\n<p><strong>Handling.</strong> I to år blev infrastrukturen, datapipelines og kortapplikationen designet, implementeret og understøttet uden afbrydelse — uden weekender, helligdage eller ferie, ofte ti til fjorten timer om dagen. En pragmatisk, modulær arkitektur blev valgt for at holde en enmandsdrift vedligeholdelsesvenlig, med automatiseret provisionering, monitorering og alarmering, så problemer kunne opdages og løses hurtigt. Databehandlingen blev løbende optimeret for pålidelighed og ydeevne, releases blev udrullet trinvist, og hvert lag — fra servere til det brugervendte kort — blev personligt vedligeholdt og forbedret ud fra reel kundeanvendelse.</p>\n<p><strong>Resultat.</strong> Platformen forblev konstant tilgængelig og udviklede sig fra en skrøbelig tidlig prototype til produktets pålidelige rygrad og bar virksomheden gennem dens kritiske vækstfase alene på styrken af én ingeniørs ejerskab. Denne praktiske forvaltning holdt infrastruktur, data og kortapplikation pålidelig nok til at understøtte mersalg, datalicensering og tiltrækning af nye kunder og demonstrerede en sjælden grad af engagement, bredde og ansvar fra ende til anden på tværs af hele stakken.</p>\n", "date_published": "2026-09-10T09:22:24+02:00", "date_modified": "2026-09-10T09:22:24+02:00", "language": "da", "tags": [ "Data engineering", "Datapipelines (ETL/ELT)", "Drift & backup", "Full stack‑udvikling", "GIS / Geospatial", "Infrastruktur", "Platformarkitektur", "Full stack‑produktudvikling", "GIS & geospatiale løsninger", "Platform- & løsningsarkitektur", "Site reliability & monitorering", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/optimized-budget-costs-10-times-with-zero-loss-56/", "url": "https://engineer.company/da/portfolio/optimized-budget-costs-10-times-with-zero-loss-56/", "title": "Har optimeret budgetomkostningerne 10 gange uden tab af produktivitet for den saudiarabiske virksomhed ved at nytænke den samlede infrastruktur, fjerne unødvendige tjenester og flytte væk fra AWS‑skyen.", "summary": "Reducerede infrastrukturbudgettet 10× uden produktivitetstab for en saudiarabisk virksomhed ved at nytænke stacken og forlade AWS.", "content_html": "<p><strong>Situation.</strong> En virksomhed med base i Saudi‑Arabien slæbte rundt på slemt oppustede infrastrukturomkostninger. Deres AWS‑setup var blevet overdimensioneret og havde samlet tjenester, de ikke længere brugte, så cloud‑regningen var drevet fuldstændig ud af proportion med, hvad forretningen faktisk havde brug for. Det er en almindelig historie — ingen sætter sig for at overforbruge, det aflejrer sig bare, når ingen holder øje med måleren.</p>\n<p><strong>Opgave.</strong> Opgaven var at skære omkostningerne betydeligt ned uden at miste produktivitet, hvilket betød at gentænke infrastrukturen ordentligt frem for at beskære i kanterne — kantbeskæring flytter sjældent en regning, der er strukturelt for stor.</p>\n<p><strong>Handling.</strong> Så arbejdet gik fra ende til anden. Først kom en audit af, hvad der faktisk blev brugt — hvilket er der, de duplikerede og unødvendige tjenester viser sig — og de blev skåret væk. Så blev det, der var tilbage, right‑sizet til at matche reel efterspørgsel i stedet for de worst‑case‑gæt, det oprindelige setup var bygget på. Og det store træk var at flytte workloads helt væk fra AWS, over på et mere omkostningseffektivt hosting‑arrangement — gjort omhyggeligt, i etaper, så den kørende forretning aldrig mærkede migreringen ske under sig.</p>\n<p><strong>Resultat.</strong> Budgetomkostningerne faldt omkring ti gange, uden tab af produktivitet — den samme kapabilitet til en brøkdel af, hvad de havde betalt. Det frigjorde et reelt beløb, der stille og roligt var sivet ud i en overdimensioneret cloud‑regning måned efter måned, hvilket for forretningen var penge direkte tilbage på bundlinjen.</p>\n", "date_published": "2026-09-10T09:22:24+02:00", "date_modified": "2026-09-10T09:22:24+02:00", "language": "da", "tags": [ "Cloud", "DevOps", "Infrastruktur", "Løsningsarkitektur", "Migrering & modernisering", "Platformarkitektur", "Cloud‑infrastruktur & migrering", "DevOps & CI/CD‑automatisering", "Platform- & løsningsarkitektur" ] }, { "id": "https://engineer.company/da/portfolio/architected-a-layered-maritime-platform-separating-a-next-57/", "url": "https://engineer.company/da/portfolio/architected-a-layered-maritime-platform-separating-a-next-57/", "title": "Har arkitekteret en lagdelt maritim platform, der adskiller en Next.js PWA‑frontend, et Go (Huma/Fiber) API og et PostgreSQL‑funktionslag og holder al forretningslogik i databasen.", "summary": "Arkitekterede en lagdelt maritim platform — Next.js PWA, et Go (Huma/Fiber) API og et PostgreSQL-funktionslag med al forretningslogik.", "content_html": "<p><strong>Situation.</strong> NextMariner skulle være en maritim platform — professionelt netværk, virksomhedsanmeldelser, sponsoreret uddannelse — og det var et ungt produkt, hvilket er en pæn måde at sige, at kravene ville flytte sig en hel del. Det, der skulle undgås, var en arkitektur, hvor en ændret forretningsregel betød, at man skulle røre frontenden, API’et og databasen på én gang. På en lille kodebase, der vokser hurtigt, er det den slags kobling, der gør en to‑linjers ændring til en hel eftermiddag.</p>\n<p><strong>Opgave.</strong> Som arkitekt var beslutningen på forhånd, hvor hver slags logik boede, med grænser tydelige nok til at holde under pres, i stedet for at blive udvisket første gang nogen havde travlt.</p>\n<p><strong>Handling.</strong> Det landede på tre lag, ét job hver. Next.js‑frontenden står for præsentation og interaktivitet og intet andet. Go‑API’et — Huma oven på Fiber — er bevidst tyndt: det router, validerer requesten, laver sikkerhedsfiltreringen og orkestrerer, men det rummer slet ingen forretningslogik. Forretningslogikken bor i PostgreSQL‑funktioner, som samler det fulde resultat og giver det tilbage, som API’et videresender. Så når en regel ændrer sig, ændrer den sig i ét lag, i SQL, og de to andre behøver ikke vide det. Grænserne blev skrevet ned og håndhævet i review, for en konvention, ingen holder øje med, holder op med at være en.</p>\n<p><strong>Resultat.</strong> Tingen forblev nem at holde i hovedet. Forretningslogikken sidder ét sted, man faktisk kan revidere, API’et er en kedelig adapter på den gode måde, og frontenden er ligeglad, når skemaet flytter sig under den. Den adskillelse er det, der lod produktet blive ved med at skrue funktioner på, uden at arkitekturen stille og roligt rådnede.</p>\n", "date_published": "2026-08-17T20:37:52+02:00", "date_modified": "2026-08-17T20:37:52+02:00", "language": "da", "tags": [ "API'er & integration", "Backend‑udvikling", "Databaser", "Frontend‑udvikling", "Full stack‑udvikling", "Løsningsarkitektur", "Platformarkitektur", "PostgreSQL", "Teknisk ledelse", "Backend- & API‑udvikling", "Databasedesign & datamodellering", "Full stack‑produktudvikling", "Platform- & løsningsarkitektur" ] }, { "id": "https://engineer.company/da/portfolio/adopted-uuid-v7-time-ordered-identifiers-postgresql-18-64/", "url": "https://engineer.company/da/portfolio/adopted-uuid-v7-time-ordered-identifiers-postgresql-18-64/", "title": "Har indført tidsordnede UUID v7‑identifikatorer (PostgreSQL 18) som entitetsnøgler for at reducere fragmentering af B‑tree‑indeks og øge forespørgselshastigheden.", "summary": "Indførte tidsordnede UUID v7-nøgler (PostgreSQL 18) — mindre B-tree-fragmentering og hurtigere inserts og range-forespørgsler.", "content_html": "<p><strong>Situation.</strong> Hver entitet har brug for et unikt id, og reflekvalget er en tilfældig UUID. Problemet er, at tilfældige id’er lander over det hele i et B‑tree‑indeks. Inserts spreder sig, indekset fragmenterer, og efterhånden som tabeller vokser, betaler både skrivninger og range‑scans for det. På en platform, der er ment til at blive ved med at vokse, er det en langsom lækage, man helst ikke vil bygge ind.</p>\n<p><strong>Opgave.</strong> Bevare en UUID’s globale unikhed, men slippe af med den fragmentering, tilfældigheden fører med sig.</p>\n<p><strong>Handling.</strong> Standarden blev UUID v7, som er tidsordnet — de førende bits er et tidsstempel, så nye rækker sorterer ind i indekset i stedet for at drysse det til. PostgreSQL 18 har det nativt som uuidv7(), pakket ind i en lille uuid_generate_v7()-funktion, så det samme kald opfører sig rent på Azures Flexible Server, og gjort til default for entiteters primærnøgler på tværs af skemaet. Intet eksotisk ved det; det er den slags beslutning, der er billig, hvis man træffer den tidligt, og en plage at eftermontere senere.</p>\n<p><strong>Resultat.</strong> Id’er forblev globalt unikke, indekset holdt op med at fragmentere, som tilfældige UUID’er får det til, og tidsordnede inserts og range‑forespørgsler blev hurtigere — jævnt, på tværs af hver tabel, uden at nogen skulle tænke over det igen. Som en bonus ender hver entitet med en nøgle, man kan sortere efter tid gratis.</p>\n", "date_published": "2026-08-17T20:37:52+02:00", "date_modified": "2026-08-17T20:37:52+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Data engineering", "Databaser", "Performanceoptimering", "PostgreSQL", "Databasedesign & datamodellering", "Databaseoptimering" ] }, { "id": "https://engineer.company/da/portfolio/migrated-the-http-api-from-fiber-to-huma-60/", "url": "https://engineer.company/da/portfolio/migrated-the-http-api-from-fiber-to-huma-60/", "title": "Har migreret HTTP‑API'et fra Fiber til Huma v2 — 649 paths og 760 operationer — og opnået og fastholdt 100 % paritet mellem de ruter, serveren registrerer, og den OpenAPI‑beskrivelse, den udgiver.", "summary": "Migrerede HTTP-API'et fra Fiber til Huma v2 — 649 paths, 760 operationer — 100 % paritet mellem registrerede ruter og den udgivne OpenAPI-beskrivelse.", "content_html": "<p><strong>Situation.</strong> API’et startede sit liv på Fiber, med request‑validering skrevet ud i hånden, endpoint for endpoint. Det er fint, når der er en håndfuld endpoints. Det holder op med at være fint, efterhånden som fladen vokser: den håndskrevne validering bliver til en vedligeholdelsesskat, og små inkonsistenser sniger sig ind, fordi hvert endpoints tjek er dets eget lille særtilfælde. Og der var ingen enkelt beskrivelse af API’ets form nogen steder.</p>\n<p><strong>Opgave.</strong> Målet var validering, der kom fra typerne i stedet for fra håndskrevne tjek, og en egentlig kontrakt, der beskrev API’et — uden at stoppe op for at lave en big‑bang‑omskrivning.</p>\n<p><strong>Handling.</strong> HTTP‑laget flyttede over på Huma v2, oven på Fiber, så den eksisterende runtime blev bevaret. Hvert endpoint får input- og output‑structs, og Huma genererer request‑valideringen og respons‑modelleringen ud fra de typer. En OpenAPI‑beskrivelse falder ud af det gratis, hvilket betyder, at dokumentationen følger koden i stedet for at rådne i en wiki. Alt nyt blev skrevet mod Huma og de eksisterende ruter migreret over, med præcis to endpoints efterladt på rå Fiber — WebSocket‑dem, hvor man reelt vil have socket’en, og Humas request/response‑model ikke passer. Det, det er vokset til, er 649 paths, der bærer 760 operationer, og et tjek i CI, som sammenligner de ruter, serveren faktisk registrerer, med dem, OpenAPI‑beskrivelsen annoncerer. Pariteten er 100 %, og den bliver der, fordi en rute, der ikke er beskrevet, får buildet til at fejle.</p>\n<p><strong>Resultat.</strong> Nye endpoints får validering og opdateret dokumentation, uden at nogen laver ekstra arbejde for det, og en hel klasse af request‑fejl — “nå ja, vi glemte at tjekke det felt her”-slagsen — forsvandt. Ved 760 operationer er beskrivelsen den eneste praktiske måde, nogen læser API’et på, så garantien for, at den er komplet, betyder mere, end den gjorde ved halvtreds. Den typede kontrakt gjorde API’et både sikrere at ændre og lettere at give videre til en anden, for typerne fortæller dig, hvad et endpoint forventer.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "API'er & integration", "Backend‑udvikling", "Dokumentation", "Migrering & modernisering", "Platformarkitektur", "Test & QA", "Backend- & API‑udvikling", "Platform- & løsningsarkitektur", "Teknisk dokumentation" ] }, { "id": "https://engineer.company/da/portfolio/built-a-request-schema-validation-contract-with-automated-61/", "url": "https://engineer.company/da/portfolio/built-a-request-schema-validation-contract-with-automated-61/", "title": "Har genereret API‑kontrakten udad fra databasen — OpenAPI, en typet TypeScript‑klient på 44.076 linjer, 61 mock handlers og de grænser, UI'et håndhæver — med en guard i hvert led, der fejler ved drift.", "summary": "Genererede API-kontrakten ud fra databasen — OpenAPI, en typet TypeScript-klient, mock handlers og UI-grænser — med en guard i hvert eneste led.", "content_html": "<p><strong>Situation.</strong> Frontend og backend bevæger sig i deres eget tempo, og deres antagelser om en request‑payload kan glide fra hinanden, uden at nogen bemærker det. Måden, man som regel opdager det på, er et 422 i browseren — efter uoverensstemmelsen allerede er udgivet, hvilket er det dyreste tidspunkt at få det at vide på. At skrive de to sider i hånden ud fra det samme dokument løser det ikke; det flytter bare driften over på den, der glemte at læse dokumentet igen.</p>\n<p><strong>Opgave.</strong> De to sider skulle genereres fra én kilde frem for aftales mellem to, med hvert trin i genereringen tjekket i stedet for taget for givet.</p>\n<p><strong>Handling.</strong> Kæden starter ved databasen og løber udad. Skemaet og dets funktioner definerer formerne; Go‑typerne definerer API’et; Huma udsender OpenAPI‑beskrivelsen ud fra dem; en typet TypeScript‑klient — 44.076 linjer af den — genereres ud fra den beskrivelse; 61 mock‑handlere genereres ved siden af den, så frontendens egne tests kører mod den rigtige kontrakt frem for en håndskrevet fixture; og de grænser, UI’et håndhæver på en formular, kommer fra det samme sted i stedet for at blive tastet ind i en validator igen. Hvert hop har et værn. Et kontrakt‑tjek i CI sammenligner det, frontenden sender, med det, API’et forventer, og får buildet til at fejle ved afvigelse, med en schema‑probe under det, som tjekker de rigtige former frem for en beskrivelse af dem. Det dækker bevidst, hvor drift kan lide at gemme sig: valgfrie body‑felter, hvor “mangler” og “null” bliver forvekslet, og query‑parameter‑enums, hvor de to sider stille og roligt kan være uenige om de tilladte værdier.</p>\n<p><strong>Resultat.</strong> Et felt kan ikke ændre sig på kun den ene side — det fejler ved det første hop, der bemærker det, i et build, minutter efter ændringen. Det tog en tilbagevendende og oprigtigt irriterende klasse af fejl af bordet — den slags, der er usynlig i code review og først dukker op i runtime. Prisen er et genereringstrin midt i det hele: at regenerere er en sur pligt, og kæden er kun så troværdig som sit dårligst bevogtede led, hvilket er grunden til, at hvert hop fik et.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "API'er & integration", "Automatisering & CI/CD", "Backend‑udvikling", "DevOps", "Drift & backup", "Test & QA", "Backend- & API‑udvikling", "DevOps & CI/CD‑automatisering" ] }, { "id": "https://engineer.company/da/portfolio/built-email-as-a-platform-capability-with-failover-62/", "url": "https://engineer.company/da/portfolio/built-email-as-a-platform-capability-with-failover-62/", "title": "Har bygget e‑mail som en kapabilitet i platformen — tre udbydere med failover, delivery‑webhooks, logning af afsendelse og levering, templating og kampagner — bag et opstartstjek, der ikke booter uden en af dem.", "summary": "Byggede e-mail som en kapabilitet i platformen: tre udbydere med failover, delivery-webhooks, logning af afsendelse og levering, templating og kampagner.", "content_html": "<p><strong>Situation.</strong> E‑mail vejer tungt på NextMariner — verificering, notifikationer, digests, kampagner, de ting en bruger faktisk venter på. Og en mailudbyder er præcis den slags afhængighed, der fejler lydløst: konfigurationen ser fin ud, appen booter, og du opdager først, at noget er galt, når et rigtigt menneske aldrig får den besked, det blev lovet. Det er den værste måde at få det at vide på. Én udbyder gør det værre, fordi fejlen er total og en andens at rette.</p>\n<p><strong>Opgave.</strong> E‑mail skulle behandles som en evne, platformen ejer, frem for et klientbibliotek, den kalder — i stand til at overleve et udbyder‑nedbrud, i stand til at sige, hvad der skete med en given besked, og højlydt ved opstart i de miljøer, hvor stilhed er farlig.</p>\n<p><strong>Handling.</strong> Tre udbydere sidder bag én grænseflade — SendGrid som primær, med SMTP2GO og Azure Communication Services bagved — og failover mellem dem er automatisk frem for en konfigurationsændring foretaget under pres. Levering tages ikke for givet: indgående webhooks rapporterer, hvad hver udbyder gjorde med en besked, og begge sider registreres, i en send‑log og en delivery event‑tabel, så “fik det her menneske sin verificeringsmail” er en forespørgsel frem for et gæt. Templating holder beskedteksterne ude af koden, og et separat broadcast‑skema — 5 tabeller og 24 funktioner — bærer kampagner ud til segmenter af brugere, hvilket er et andet problem end transaktionsmail og blev bygget som et. Foran det hele sender et opstartstjek en rigtig besked gennem stakken, bag et flag: i udvikling logger det en advarsel og fortsætter, for ingen vil have deres laptop til at nægte at starte, fordi en sandkasse‑nøgle er udløbet, og i staging og produktion er en fejl fatal, og processen afslutter frem for at deploye et build, der ikke kan sende mail. Selve afsendelsesstien går gennem en SSRF‑beskyttet klient med et 30‑sekunders timeout, og den asynkrone leveringssti har retries og backoff, så et øjebliks udfald ikke taber en besked.</p>\n<p><strong>Resultat.</strong> En hel kategori af lydløs fejl flyttede fra “en bruger opdager det dage senere” til “deployet stopper”, og en udbyder, der har en dårlig eftermiddag, blev til en forringet sti frem for et udfald. Prisen er tre integrationer at holde kørende i stedet for én, og leveringslogs, der vokser og skal beskæres — begge accepteret, fordi e‑mail er den kanal, platformen ikke kan rute uden om.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "API'er & integration", "Backend‑udvikling", "Cloud", "Drift & backup", "Sikkerhed", "Backend- & API‑udvikling", "Sikkerhed & adgangsstyring", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/designed-a-postgresql-function-first-data-layer-across-63/", "url": "https://engineer.company/da/portfolio/designed-a-postgresql-function-first-data-layer-across-63/", "title": "Har designet et function‑first datalag i PostgreSQL — 1.275 stored functions på tværs af 34 skemaer — så hver læsning og skrivning går gennem en funktion, databasen kan tildele rettigheder til, frem for gennem en tabel.", "summary": "Designede et function-first datalag i PostgreSQL — 1.275 stored functions på tværs af 34 skemaer — med adgangsgrænsen håndhævet af databasen selv.", "content_html": "<p><strong>Situation.</strong> Forretningslogik har det med at lække. Lidt ender i API’et, lidt i noget SQL, en handler kører inline, og før længe er den samme regel skrevet på to‑tre lidt forskellige måder, og der er ingen steder, man kan pege hen og sige “det her er, hvad systemet gør med sine data.” Det er sådan, fejl og sikkerhedshuller kommer ind.</p>\n<p><strong>Opgave.</strong> Målet var ét hjem til det hele: hver læsning og skrivning gennem databasen, API’et en tynd adapter, der ikke kender forretningsreglerne, og det hele muligt at låse ned til.</p>\n<p><strong>Handling.</strong> Datalaget er function‑first. Skemaet er delt op efter domæne — identity, organization, review, message, notification og 29 mere, 34 i alt — og hver operation, appen kan udføre, er én af 1.275 PostgreSQL‑funktioner, den kalder; der er slet ingen direkte tabeladgang fra Go. Så håndhæver databasen det. Den rolle, API’et logger ind som, mariner, har EXECUTE på app‑funktionerne og USAGE på skemaerne og intet andet — ingen SELECT, ingen INSERT, ingen måde at røre en tabel direkte — hvilket løber op i cirka 4.000 eksplicitte grants frem for én generel. Funktionerne kører SECURITY DEFINER, ejet af en separat non‑login function_owner‑rolle med en pinned search_path, og superuser‑kontoen holdes reserveret til migrationer og cron, langt væk fra den kørende app.</p>\n<p><strong>Resultat.</strong> Logikken bor ét sted, man faktisk kan revidere, API’et forbliver tyndt og kedeligt på den gode måde, og adgangsgrænsen håndhæves af Postgres selv frem for af, at alle husker reglerne. Hvis API’et på en eller anden måde blev kompromitteret, kunne det stadig ikke gøre noget, funktionerne ikke tillader. Ved 1.275 funktioner koster disciplinen noget reelt — et nyt felt er en migration og en funktionsændring, ikke en linje i en forespørgsel — og den friktion er prisen for, at grænsen holder.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Data governance", "Databaser", "Platformarkitektur", "PostgreSQL", "Sikkerhed", "SQL", "Backend- & API‑udvikling", "Data governance & datakvalitet", "Databasedesign & datamodellering", "Platform- & løsningsarkitektur" ] }, { "id": "https://engineer.company/da/portfolio/built-a-python-vessel-data-scraper-marinetraffic-maritime-66/", "url": "https://engineer.company/da/portfolio/built-a-python-vessel-data-scraper-marinetraffic-maritime-66/", "title": "Har bygget Python‑scraperne til skibsdata (MarineTraffic, Maritime‑Database) og et gentageligt import‑trin, der seeder platformens referencedata — 184.197 rækker, heraf 698 virksomheder og 56.149 skibe.", "summary": "Byggede Python-scrapere til skibsdata og seedede 184.197 rækker maritime referencedata — 698 virksomheder og 56.149 skibe — via en gentagelig pipeline.", "content_html": "<p><strong>Situation.</strong> Et maritimt netværks- og anmeldelsessite er dødt ved ankomst, hvis det er tomt. Ingen melder sig ind i et katalog uden virksomheder i. Så før nogen af de sociale funktioner betød noget, havde platformen brug for et rigtigt korpus af maritime virksomheder og skibe, der allerede sad der, klar til at blive fundet.</p>\n<p><strong>Opgave.</strong> Gå ud og hent de data — rigtige virksomheder og skibe, i nok skala til at føles befolket — og få dem ind i databasen på en måde, der kunne køres igen, ikke en engangs‑scrape, ingen ville kunne reproducere.</p>\n<p><strong>Handling.</strong> Scraperne er skrevet i Python. Én driver MarineTraffic med Playwright; en anden henter fra Maritime‑Database over async httpx; der er også en ClassNK‑fetcher. De skriver CSV’er ud, og et import‑trin renser og normaliserer dem og indlæser dem i Postgres‑skemaet gennem en enkelt task, så at seede databasen er én kommando frem for en eftermiddags manuelt arbejde. Det, der gik ind, endte på 184.197 rækker: 698 virksomheder, 56.149 fartøjer og 33.074 byer, med en senere opdatering, der erstattede 74.794 fartøjsrækker.</p>\n<p><strong>Resultat.</strong> Platformen blev lanceret med et befolket katalog i stedet for tomme tabeller og et grundlag af referencedata, som netværks-, job- og anmeldelsesfunktionerne alle kunne bygge oven på. Fordi pipelinen er reproducerbar, er det bare at køre den igen at opdatere eller udvide den senere — hvilket er sådan, fartøjsopdateringen skete, uden at nogen byggede værktøjet om. Scrapede data ældes, og at holde dem aktuelle er en løbende omkostning frem for et løst problem.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Backend‑udvikling", "Data engineering", "Databaser", "Datapipelines (ETL/ELT)", "Python", "Backend- & API‑udvikling", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/modeled-the-maritime-domain-professionals-companies-ships-jobs-67/", "url": "https://engineer.company/da/portfolio/modeled-the-maritime-domain-professionals-companies-ships-jobs-67/", "title": "Har modelleret det maritime domæne i 348 normaliserede tabeller på tværs af 34 PostgreSQL‑skemaer — professionelle, virksomheder, skibe, jobs, anmeldelser og resten — med SMALLINT‑opslagstabeller og UUID v7‑nøgler.", "summary": "Modellerede det maritime domæne i 348 normaliserede tabeller på tværs af 34 PostgreSQL-skemaer — SMALLINT-opslag, UUID v7-nøgler og én navnekonvention.", "content_html": "<p><strong>Situation.</strong> Hjertet i NextMariner er et tæt maritimt domæne — professionelle, virksomheder, skibe, jobs, anmeldelser — og de her ting refererer konstant til hinanden. En professionel sejler på skibe, arbejder for virksomheder, efterlader anmeldelser; en virksomhed ejer skibe og slår jobs op. Næsten hver funktion er en forespørgsel på tværs af det net, så hvor godt dataene er modelleret, afgør, hvor godt det meste af appen performer, og hvor fornuftigt det er at udvide den.</p>\n<p><strong>Opgave.</strong> Det domæne skulle modelleres, så det holdt sig hurtigt og bevarede sin integritet, og så det at tilføje den næste entitetstype ikke betød at slås med skemaet.</p>\n<p><strong>Handling.</strong> Det er lagt ud som normaliserede PostgreSQL‑skemaer organiseret efter domæne — 348 tabeller på tværs af 34 af dem efterhånden. De mange små, stabile enumerationer — statusser, typer, kategorier — blev SMALLINT‑opslagstabeller, hvilket holder rækkerne kompakte og joins billige i stedet for at gemme tekstkoder overalt. Entiteter får UUID v7‑primærnøgler, så de er globalt unikke, men stadig tidsordnet i indekset. Én navngivningskonvention løber hele vejen igennem — flertalstabeller inde i entalsnavngivne skemaer — anvendt uden undtagelse, hvilket som en bonus undgår en masse kollisioner med reserverede ord. Og relationerne holdes oppe af rigtige foreign keys og constraints, så integritet er databasens job, ikke noget applikationen skal huske at gøre.</p>\n<p><strong>Resultat.</strong> Det, der kom ud, er en datamodel, der er konsistent og hurtig, og forudsigelig at arbejde i, fordi de samme regler holder overalt — der er ingen særtilfælde at huske. At tilføje en funktion betyder som regel at udvide skemaet med den eksisterende retning frem for imod den, hvilket er den eneste grund til, at 34 skemaer er en struktur frem for et vildnis. Det er gulvet, resten af platformen står på.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Data engineering", "Data governance", "Databaser", "Performanceoptimering", "Platformarkitektur", "PostgreSQL", "SQL", "Data governance & datakvalitet", "Databasedesign & datamodellering", "Platform- & løsningsarkitektur" ] }, { "id": "https://engineer.company/da/portfolio/authored-578-go-task-automation-targets-70/", "url": "https://engineer.company/da/portfolio/authored-578-go-task-automation-targets-70/", "title": "Har skrevet 578 go‑task‑automatiseringsmål på tværs af native-, Docker- og HTTPS‑udviklingstilstande, linting, test, database og deployment.", "summary": "Skrev 578 go-task-mål på tværs af native-, Docker- og HTTPS-tilstande — linting, test, database og deployment i én værktøjskæde.", "content_html": "<p><strong>Situation.</strong> NextMariner er et polyglot‑monorepo — Go, TypeScript, SQL, Python, shell — og hvert af dem medbringer sin egen måde at bygge, teste, linte og køre på. Overladt til sig selv betyder det, at alle går rundt med et mentalt spikseddel af værktøjsspecifikke kommandoer, og at nytilkomne bruger deres første dag på bare at finde ud af, hvordan man får tingene til at køre.</p>\n<p><strong>Opgave.</strong> Giv hele projektet én hoveddør: én konsistent måde at køre hvad som helst på, uanset hvilket sprog det tilfældigvis er skrevet i.</p>\n<p><strong>Handling.</strong> Det er bygget ud med go‑task — et Taskfile‑lag, der er vokset til 578 navngivne mål, 50 i rodfilen og 528 i namespacede filer under den. Der er udviklingstilstandene (native, Docker, en HTTPS‑variant til at teste PWA og mobil), kodekvalitetssiden (lint, format, test, fix på tværs af alle sprogene), databasehåndtering og de miljøspecifikke build- og deploy‑opgaver. Der er endda en low‑memory‑tilstand til maskiner, der ikke kan undvære den RAM, det kræver at bygge frontenden på den sædvanlige måde. Pointen var aldrig at have mange tasks; den var, at man aldrig behøver at kende den underliggende kommando.</p>\n<p><strong>Resultat.</strong> Enhver kan køre task –list og se hele værktøjskæden lagt frem og køre en hvilken som helst del af den på samme måde uanset, hvad der er under motorhjelmen. Onboarding blev kortere, og de små, dumme fejl — forkert flag, forkert mappe, halvt husket kommando — forsvandt for det meste. Tallet er også en advarsel: 578 mål er forbi, hvad nogen kan holde i hovedet, så navngivningen og namespacingen er det, der holder det brugbart, frem for at antallet er noget at være stolt af.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Dokumentation", "Teknisk ledelse", "Test & QA", "DevOps & CI/CD‑automatisering", "Teknisk dokumentation" ] }, { "id": "https://engineer.company/da/portfolio/configured-database-backup-retention-as-infrastructure-as-code-72/", "url": "https://engineer.company/da/portfolio/configured-database-backup-retention-as-infrastructure-as-code-72/", "title": "Har konfigureret opbevaring af databasebackups som infrastructure‑as‑code, gennemgået genopretningsberedskabet og dokumenteret restore‑proceduren — med de resterende huller navngivet frem for fundet under en hændelse.", "summary": "Implementerede databasebackups og en disaster recovery-strategi via infrastructure-as-code — hurtig, reproducerbar genopretning.", "content_html": "<p><strong>Situation.</strong> Et produkt, der lever på sine data, har ikke råd til at miste nogen, og “der er backups et sted” er et håb, ikke en genopretningsplan. Den eneste backup, der er noget værd, er en, man ved gendanner, ind i et miljø, man ved, man kan genopbygge. NextMariner havde ingen af de to halvdele skrevet ned.</p>\n<p><strong>Opgave.</strong> Få den genoprettelige position defineret i stedet for antaget — dataene, miljøet omkring dem, og et ærligt billede af, hvor langt det faktisk rækker i dag.</p>\n<p><strong>Handling.</strong> Backup‑retention er konfigureret i Bicep ved siden af den database, den beskytter, så gendannelse til et tidspunkt er en egenskab ved templaten frem for en indstilling, nogen engang klikkede i en portal. Miljøet omkring den er også defineret som infrastructure‑as‑code, hvilket er den stille halvdel, folk glemmer: at gendanne en database ind i et miljø, man skulle genopbygge i hånden efter hukommelsen, er ikke rigtig genopretning. Derefter blev positionen gennemgået og skrevet op — gendannelsesproceduren, den øvelse, der ville måle den, og de huller, der stadig står åbne: geo‑redundans er slået fra, og målet for genopretningstid er foreslået frem for målt, fordi ingen øvelse er kørt endnu.</p>\n<p><strong>Resultat.</strong> Genopretning holdt op med at være en vag beroligelse og blev en dokumenteret position med sine huller navngivet. Det lyder mindre imponerende end “disaster recovery: klaret”, og det er væsentligt mere værd — den næste, der rører ved det, ved, hvad der er dækket, hvad der ikke er, og præcis hvilken øvelse der lukker forskellen. Et navngivet hul kan man lukke; et unavngivet bliver opdaget under en hændelse.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Cloud", "Databaser", "DevOps", "Drift & backup", "Infrastruktur", "PostgreSQL", "Backup & disaster recovery", "Databaseadministration (DBA)", "Infrastructure as Code" ] }, { "id": "https://engineer.company/da/portfolio/built-the-next-js-16-frontend-with-deliberate-73/", "url": "https://engineer.company/da/portfolio/built-the-next-js-16-frontend-with-deliberate-73/", "title": "Har bygget Next.js 16‑frontenden med bevidste SSR-, SSG- og CSR‑strategier og en genbrugelig prefetched‑server‑page‑factory, der fjerner N+1‑kaskaden fra hver autentificeret side, der flyttes over på den.", "summary": "Byggede Next.js 16-frontenden med SSR/SSG/CSR og et server-shell prefetch-and-hydrate-mønster, der eliminerede N+1-fetches.", "content_html": "<p><strong>Situation.</strong> Det autentificerede dashboard skulle føles hurtigt og forblive oprigtigt interaktivt, og de to mål trækker mod hinanden, hvis man er naiv omkring det. Hent alt på klienten, og førsteindlæsningen sløver, og værre endnu får man N+1‑mønsteret, hvor hver komponent vågner og fyrer sin egen request af, så en enkelt side bliver til en kaskade af rundture.</p>\n<p><strong>Opgave.</strong> Hver del af appen skulle renderes på den måde, der faktisk passede til den, uden at opgive client‑side‑interaktiviteten, hvor den betød noget.</p>\n<p><strong>Handling.</strong> Next.js 16‑frontenden bruger den rette tilstand pr. flade i stedet for ét fladt valg. Marketing- og offentlige sider genereres statisk — de ændrer sig ikke pr. bruger, så der er ingen grund til at rendere dem ved hver request. De oprigtigt interaktive dele forbliver client‑renderede. Og dér, hvor N+1‑kaskaden faktisk bider — det autentificerede dashboard og de store oversigtslister — blev løsningen lagt i en factory frem for håndlavet: createPrefetchedServerPage henter sidens data på serveren og overlader dem til klienten allerede udfyldt, så komponenterne kommer op med deres data i stedet for hver at gå af sted for at spørge efter dem. En cache‑invalideringsstrategi er dokumenteret pr. forespørgsel, så data forbliver friske, uden at appen re‑fetcher ting, den allerede har.</p>\n<p><strong>Resultat.</strong> De sider, der er flyttet over på denne factory, kommer op på én rundtur i stedet for en storm af dem, og fordi det er en factory frem for et mønster, folk kopierer i hånden, arver den næste side, der flyttes over, opførslen gratis. Resten af den autentificerede app er stadig client‑renderet og står stadig i kø bagved — en migrering med en fungerende mekanisme og en indlysende næste side frem for et afsluttet sweep.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Frontend‑udvikling", "Full stack‑udvikling", "Performanceoptimering", "Platformarkitektur", "Webudvikling", "Full stack‑produktudvikling", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/established-an-english-danish-internationalization-system-with-linter-78/", "url": "https://engineer.company/da/portfolio/established-an-english-danish-internationalization-system-with-linter-78/", "title": "Har etableret et engelsk/dansk internationaliseringssystem med 12.027 beskeder pr. locale på tværs af 458 namespace‑filer, med linter‑håndhævet ordforråd og et budget på 400 linjer pr. fil.", "summary": "Etablerede et engelsk/dansk i18n-system med 12.027 beskeder pr. locale i 458 filer — linter-håndhævet ordforråd og et budget for filstørrelse.", "content_html": "<p><strong>Situation.</strong> NextMariner kører på engelsk og dansk, og oversættelsessystemer har det med at vokse ud i et rod. Filerne vokser uden grænse, nøgler lækker — til stede i ét sprog, manglende i det andet — og de sprogspecifikke konventioner anvendes ujævnt, så ét sprog ender med at læse, som om det var oversat af et udvalg, der ikke talte sammen. For et professionelt produkt er det ikke en lille skønhedsfejl; det læses som sjusk.</p>\n<p><strong>Opgave.</strong> Internationaliseringssystemet skulle skalere — holde begge sprog konsistente og korrekte og filerne til noget, et menneske stadig kunne vedligeholde et år inde.</p>\n<p><strong>Handling.</strong> Det er bygget på next‑intl med regler, som værktøjerne faktisk håndhæver. Den danske side har et låst ordforråd og en låst stil — literal æøå, det uformelle “du”, de rigtige imperative accenter, og semantiske mappinger for sammensatte ord, så de oversættes efter betydning frem for ord‑for‑ord — og en linter holder den til det. Der er et hårdt budget på 400 linjer pr. namespace‑fil, med en split‑and‑merge‑tilgang, så et stort område deles i nestede filer, der merges rent tilbage, i stedet for at én fil vokser i det uendelige; det budget er grunden til, at 12.027 beskeder pr. sprog bor i 458 filer frem for en håndfuld enorme. Et linter‑tjek sammenligner nøgler på tværs af sprog, så intet lækker eller mangler. Og lagrede overrides merges katalogdrevet frem for med skrøbelige top‑niveau‑fallbacks — den samme deep‑merge‑idé, der blev brugt til præferencer.</p>\n<p><strong>Resultat.</strong> Begge sprog forbliver korrekte og konsistente ved 24.054 beskeder mellem dem, og filerne forbliver vedligeholdelige, efterhånden som antallet af strenge klatrer. Sprogkvalitet blev til noget, værktøjerne garanterer ved hver commit, frem for noget, der stille og roligt forringes, hver gang nogen tilføjer en streng i en fart. Budgettet er den bærende del: uden en grænse pr. fil ville 458 filer have været tolv, og ingen ville åbne dem.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Dokumentation", "Frontend‑udvikling", "Full stack‑udvikling", "Internationalisering", "Internationalisering & lokalisering" ] }, { "id": "https://engineer.company/da/portfolio/set-the-platform-s-founding-decisions-in-november-2025-80/", "url": "https://engineer.company/da/portfolio/set-the-platform-s-founding-decisions-in-november-2025-80/", "title": "Har fastlagt platformens grundlæggende beslutninger i ugerne efter, at kodebasen blev åbnet i november 2025 — lagdelingen, database‑first‑dataadgang og zero‑warnings‑standarden — og de holder stadig ni måneder senere.", "summary": "Fastlagde platformens grundlæggende beslutninger i november 2025 — lagdelingen, database-first-dataadgang og zero-warnings-standarden — og de holder stadig.", "content_html": "<p><strong>Situation.</strong> Repositoriet blev åbnet den 25. november 2025 uden noget i sig. Det, der bliver besluttet i de første par uger af et projekt som det, vejer uforholdsmæssigt tungt: lagdelingen, hvor forretningslogikken må bo, hvad kvalitetsstandarden er. De valg er billige at træffe på dag tre og tæt på umulige at vende om på i måned seks, hvor alt skrevet siden da forudsætter dem.</p>\n<p><strong>Opgave.</strong> De grundlæggende beslutninger skulle træffes bevidst og tidligt og træffes i en form, der kunne overleve at blive givet videre til andre mennesker og til en langt større kodebase, end der fandtes dengang.</p>\n<p><strong>Handling.</strong> Tre beslutninger gjorde det meste af arbejdet. Stakken blev lagdelt, så hver del har ét job — en Next.js‑frontend, et Go‑API, der validerer og videresender, et PostgreSQL‑funktionslag, der ejer forretningsreglerne — frem for at logik blev placeret, hvor det nu var bekvemt den eftermiddag. Dataadgang blev lagt bag stored functions fra starten, hvilket er det valg, alt andet i databasearbejdet følger af; at eftermontere det senere ville have betydet at skrive hver handler om. Og en zero‑warnings‑standard kom ind, før der var meget kode at holde til den, for en standard indført ved commit 5.000 er et oprydningsprojekt, hvorimod den samme standard ved commit 50 bare er, hvordan repositoriet fungerer. Ingen af de tre var den nemme mulighed dengang, og alle tre kostede momentum i den første måned.</p>\n<p><strong>Resultat.</strong> Ni måneder og flere tusinde commits senere holder alle tre stadig: lagene er ikke blevet slørede, ingen handler taler direkte med en tabel, og buildet har stadig ingen warnings i sig. Det er den prøve, det er værd at lægge på en grundlæggende beslutning — ikke om den lød rigtig, men om den overlevede kontakten med den mængde arbejde, der kom efter, hvilket er det punkt, hvor bekvemme valg som regel stille og roligt bliver opgivet.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Databaser", "DevOps", "Frontend‑udvikling", "Full stack‑udvikling", "Løsningsarkitektur", "Platformarkitektur", "Sikkerhed", "Teamledelse", "Teknisk ledelse", "Backend- & API‑udvikling", "Full stack‑produktudvikling", "Platform- & løsningsarkitektur", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/built-a-layered-automated-test-suite-across-four-layers-94/", "url": "https://engineer.company/da/portfolio/built-a-layered-automated-test-suite-across-four-layers-94/", "title": "Har bygget en lagdelt automatiseret testsuite — 981 Go‑tests, 543 frontend- og browserspecs, 494 SQL‑adfærdstests — med mutationstest, property‑based tests og en tilgængelighedsgate.", "summary": "Byggede en lagdelt testsuite — 981 Go-tests, 543 frontend-specs og 494 SQL-adfærdstests — med gates for mutation, property-based test og tilgængelighed.", "content_html": "<p><strong>Situation.</strong> En platform, der holder sin forretningslogik i databasen, har et testproblem, de fleste projekter ikke har. Logikken er ikke i det sprog, testframeworket er godt til — den er i SQL, bag funktionsgrænser, og SQL er præcis den slags kode, der ender utestet, fordi det er akavet at teste. Læg et Go‑API og en Next.js‑frontend oven på det, og “de vigtige dele er dækket” bliver stille og roligt til “de dele, der var nemme at dække, er dækket.”</p>\n<p><strong>Opgave.</strong> Hvert lag skulle have sin adfærd kontrolleret dér, hvor adfærden faktisk bor, frem for at alt blev kontrolleret udefra gennem en browser.</p>\n<p><strong>Handling.</strong> Fire lag fik fire slags test. Go‑API’et bærer 981 testfunktioner fordelt på 310 filer. Frontenden bærer 543 Vitest- og Playwright‑specs, heraf 48 end‑to‑end‑filer og 30 browser‑specs. Databasen bærer 494 adfærdstestfiler — 116.876 linjer SQL, der tjekker sine antagelser gennem 6.654 rejste exceptions — så en stored function bliver testet i databasen frem for gennem tre lag applikation ovenover. Over dem ligger de test, der tester testene: Stryker mutation testing ødelægger med vilje en linje kode og fejler, når intet opdager det, og fast‑check genererer input, ingen tænkte på at skrive ned. En axe‑core‑gate kræver nul WCAG 2.0- og 2.1‑overtrædelser på niveau A og AA, hvilket gør tilgængelighed til noget, der får buildet til at fejle, frem for et revisionsfund måneder senere. Coverage‑tærskler bevæger sig kun opad. Hele suiten kører som 10 jobs i et 612‑linjers workflow.</p>\n<p><strong>Resultat.</strong> At ændre noget strukturelt holdt op med at være skræmmende, og det er det eneste, der holder et kodebase af den størrelse fra at forkalke. Den ærlige omkostning er tid — suiten er langsom, den beskatter hver eneste ændring, og i den størrelse skal den selv vedligeholdes. Det, den køber, er evnen til at blive ved med at bevæge sig hurtigt, og det er mere værd end de minutter, det tager.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Backend‑udvikling", "DevOps", "Drift & backup", "Frontend‑udvikling", "PostgreSQL", "Test & QA", "Backend- & API‑udvikling", "DevOps & CI/CD‑automatisering", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/built-the-repository-s-guard-engine-of-268-checks-95/", "url": "https://engineer.company/da/portfolio/built-the-repository-s-guard-engine-of-268-checks-95/", "title": "Har bygget kodebasens guard‑motor — 268 registrerede commit‑tjek, 277 lint‑regler og 15 egne ESLint‑regler — plus 146 tests af selve tjekkene, så det er builden, der holder standarden, ikke reviewet.", "summary": "Byggede en guard-motor med 268 registrerede commit-tjek, 277 lint-regler og 15 egne ESLint-regler — plus 146 tests af selve tjekkene.", "content_html": "<p><strong>Situation.</strong> Standarder, der er skrevet ned i en contributing‑guide, er forslag. Alle er enige i dem, og så er det fredag, ændringen er lille, og guiden taber. Zero‑warnings‑barren ville kun nogensinde holde, hvis noget andet end velvilje holdt den — og de lintere, der følger med hvert sprog, når slet ikke frem til de projektspecifikke regler, der faktisk betyder noget, dem om, hvordan netop dette kodebase er ment at virke.</p>\n<p><strong>Opgave.</strong> De regler, projektet gik op i, skulle kunne eksekveres, så det at bryde en fik en commit til at fejle frem for at vente på en reviewer med tid og hukommelse nok til at fange det.</p>\n<p><strong>Handling.</strong> Det, der voksede ud af det, er en guard‑motor. Der er 274 check‑scripts, 268 af dem registreret i commit‑hooks, ved siden af 277 JavaScript‑lintere og 96 shell- og 17 Python‑validatorer, der dækker de ting, hyldevareværktøj ikke har nogen mening om — at en migration kan rulles tilbage, at en oversættelsesnøgle findes i begge locales, at en registreret rute optræder i OpenAPI‑beskrivelsen, at ingen stille og roligt har tilføjet en inline‑suppression. Femten custom ESLint‑regler holder husets mønstre på plads i TypeScript. Én regel er værd at nævne for sig: enhver commit med præfikset fix: skal bære en test, der fejler uden den, så en fejl, der er rettet, bliver ved med at være rettet. Og fordi en ødelagt guard er værre end slet ingen guard — den lader alt passere, og ingen opdager det — har selve guard‑laget 146 test.</p>\n<p><strong>Resultat.</strong> Review‑tiden flyttede fra mekanik til design, fordi de mekaniske indvendinger allerede var fremsat af en maskine, før branchen blev pushet. Trade‑off’et er reelt og værd at sige højt: det er langsomt at committe, og en dårligt skrevet guard er oprigtigt irriterende at arbejde uden om. De 146 test findes, fordi netop det skete.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Dokumentation", "Teknisk ledelse", "Test & QA", "DevOps & CI/CD‑automatisering", "Teknisk dokumentation", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/built-the-payments-and-entitlements-layer-96/", "url": "https://engineer.company/da/portfolio/built-the-payments-and-entitlements-layer-96/", "title": "Har bygget betalings- og rettighedslaget — Stripe side om side med Apple og Google in‑app purchase — så directory, søgning og eksport spærres af en adgangsmodel på 11 tabeller, der tjekkes på serveren.", "summary": "Byggede betalings- og rettighedslaget — Stripe med Apple og Google in-app purchase — directory, søgning og eksport spærret af en adgangsmodel.", "content_html": "<p><strong>Situation.</strong> Penge er den del af en platform, ingen har lov til at tage let på. Et abonnement skal overleve, at et kort udløber, en refusion, et planskifte, en webhook, der ankommer to gange, og en webhook, der ankommer i forkert rækkefølge. I det øjeblik betaling kan ske i tre butikker — et kort på nettet, Apple i den ene app store, Google i den anden — findes der tre forskellige udlægninger af, hvad nogen har købt, og produktet har stadig brug for ét svar på ét spørgsmål: hvad må denne person lige nu?</p>\n<p><strong>Opgave.</strong> At tage imod penge og at give adgang skulle være to systemer frem for ét, så det at tilføje en butik ikke betød at omskrive hver eneste gate i produktet.</p>\n<p><strong>Handling.</strong> Stripe håndterer kort og abonnementer gennem 18 Go‑filer, og Apple- og Google‑in‑app‑køb kommer ind gennem deres egen kvitteringsverifikation. Alle tre løber sammen i et payments‑skema på 9 tabeller og 34 funktioner — og stopper så dér. Det, produktet faktisk spørger om, er et separat access‑skema på 11 tabeller og 34 funktioner, som svarer på “må denne konto det her?” uden at vide eller bryde sig om, hvilken butik der har betalt for det. Det svar styrer adgangen til virksomhedskataloget, søgningen og dataeksporten, og det genkontrolleres på serveren ved hver request, for en skjult knap er en høflighed og ikke en kontrol.</p>\n<p><strong>Resultat.</strong> At tilføje en butik rører nu ved payments‑siden og lader alle gates være, og et supportspørgsmål om nogens adgang har én tabel at kigge i frem for tre. Omkostningen er to skemaer, hvor et mindre produkt ville nøjes med ét, plus et rettighedsopslag på requests, der ellers ville have været gratis.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "API'er & integration", "Backend‑udvikling", "Databaser", "PostgreSQL", "Produkt & krav", "Sikkerhed", "Backend- & API‑udvikling", "Databasedesign & datamodellering", "Produktstrategi & kravspecifikation", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/moved-slow-work-onto-a-river-job-queue-97/", "url": "https://engineer.company/da/portfolio/moved-slow-work-onto-a-river-job-queue-97/", "title": "Har flyttet langsomt arbejde væk fra request‑stien og over på en River‑jobkø — 15 worker‑moduler, 8 planlagte opgaver og 20 pg_cron‑jobs — så et request vender tilbage, mens arbejdet bag det kører videre.", "summary": "Flyttede langsomt arbejde væk fra request-stien over på en River-jobkø: 15 worker-moduler, 8 planlagte opgaver og 20 pg_cron-vedligeholdelsesjobs.", "content_html": "<p><strong>Situation.</strong> Noget arbejde har ingen gang på jorden, mens en bruger venter. At sende mail, at genopbygge et søgeindeks, at generere et dokument, at genberegne placeringer — gør noget af det inde i requesten, og brugeren kigger på en spinner for noget, de aldrig bad om at se. Gør det i stedet i en goroutine, og det forsvinder i det øjeblik processen genstarter, hvilket den gør, midt i en deploy, uden spor af, at det nogensinde skulle være sket.</p>\n<p><strong>Opgave.</strong> Baggrundsarbejde havde brug for et holdbart sted at bo: en kø, der overlever en genstart, prøver en fejl igen og kan kigges efter, når noget ikke er sket.</p>\n<p><strong>Handling.</strong> Valget faldt på River, i høj grad fordi den holder sin kø i PostgreSQL — databasen er i forvejen det autoritative register, så et job og de rækker, det rører, committer eller ruller tilbage sammen, og der er ikke et stykke infrastruktur nummer to at køre og ræsonnere om. Bag den ligger 15 worker‑moduler og 8 planlagte opgaver. Under det håndterer 20 pg_cron‑jobs den vedligeholdelse, databasen er bedre placeret til at gøre selv: at beskære partitioner, at rotere salts, at opfriske aggregater. Alt, der var langsomt nok til at blive bemærket, blev flyttet væk fra request‑stien og over på en af de to.</p>\n<p><strong>Resultat.</strong> Requests svarer hurtigt, og det langsomme arbejde bliver stadig færdigt, med retries og en synlig historik, når det ikke gør. At holde køen i Postgres frem for i en dedikeret broker er en bevidst begrænsning: det skalerer ikke i det uendelige, og ved en vis mængde bliver det det forkerte svar. For en platform, hvis flaskehals alligevel er databasen, var én bevægelig del mindre mere værd end luft, der ikke ville blive brugt.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Backend‑udvikling", "Databaser", "Drift & backup", "Performanceoptimering", "PostgreSQL", "Backend- & API‑udvikling", "Databasedesign & datamodellering", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/built-first-party-error-monitoring-and-tracing-98/", "url": "https://engineer.company/da/portfolio/built-first-party-error-monitoring-and-tracing-98/", "title": "Har bygget egen error monitoring og OpenTelemetry‑tracing frem for at købe dem — sanitering af payloads, detektion af spikes og regressioner, symbolication og et syntetisk heartbeat — bag 11 operatørvisninger.", "summary": "Byggede egen error monitoring og OpenTelemetry-tracing — sanitering, detektion af spikes, symbolication og et heartbeat — bag 11 operatørvisninger.", "content_html": "<p><strong>Situation.</strong> Forskellen på en platform, der er oppe, og en platform, der virker, er, om nogen ville vide det. En fejl, en bruger rammer klokken elleve om aftenen, på en side ingen tester, er usynlig, medmindre noget går ud og samler den op. Det sædvanlige svar er at købe en hosted error tracker, og det er et godt svar — og det betyder også, at platformens egne fejl, stack traces og brugerkontekst rejser af sted til en tredjepart.</p>\n<p><strong>Opgave.</strong> Fejl og traces skulle samles, grupperes og gøres til noget, man kunne handle på, uden at platformens indre forlod platformen.</p>\n<p><strong>Handling.</strong> Der blev bygget to stykker. OpenTelemetry står for tracing over OTLP, så en langsom request kan følges på tværs af frontenden, API’et og databasen frem for at blive gættet på. Ved siden af ligger en hjemmebygget fejl‑pipeline — en errmon‑service og en ingest‑service — der renser payloads før lagring, grupperer fejl i tilbagevendende problemer frem for en flad liste, opdager spikes og regressioner med en cooldown, så én dårlig deploy ikke kalder nogen ud fyrre gange, oversætter minificerede frontend‑stack traces tilbage til læsbar kode og kører en syntetisk heartbeat for at bevise, at selve pipelinen er i live. Det hele lander i databasen som error events, error groups, en inbox, API‑latens og stack‑samples, og det kommer frem gennem 11 operatørvisninger, heriblandt én til service level objectives.</p>\n<p><strong>Resultat.</strong> Fejl bliver til en kø, nogen kan arbejde sig igennem, og en regression melder sig selv i stedet for at blive opdaget af en bruger. At bygge frem for at købe kostede reel tid og betyder, at det er én ting mere at vedligeholde — en købt tracker ville have kørt samme eftermiddag. Det, det købte, var, at intet følsomt forlader platformen, og at alarmreglerne passer til netop denne platform frem for til en generisk.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Backend‑udvikling", "DevOps", "Drift & backup", "Infrastruktur", "Monitorering & observability", "Sikkerhed", "Backend- & API‑udvikling", "DevOps & CI/CD‑automatisering", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/kept-the-schema-honest-across-1022-migrations-99/", "url": "https://engineer.company/da/portfolio/kept-the-schema-honest-across-1022-migrations-99/", "title": "Har holdt skemaet ærligt på tværs af 1.022 migrationer med en CI‑gate, der bygger databasen begge veje — en frisk installation og en installation plus hver eneste migration — og fejler, når de to ikke stemmer overens.", "summary": "Holdt skemaet ærligt på tværs af 1.022 migrationer med en CI-gate, der bygger databasen begge veje og fejler, når de to ikke stemmer overens.", "content_html": "<p><strong>Situation.</strong> Et skema er beskrevet to gange i de fleste projekter: én gang af den indledende opsætning, der bygger det fra bunden, og én gang af de ophobede migrationer, der har fået det til at vokse. Begge er ment at producere den samme database. Intet tjekker, at de gør det, så de driver fra hinanden — og afdriften er usynlig, indtil et friskt miljø opfører sig anderledes end produktion, som regel på det værst tænkelige tidspunkt.</p>\n<p><strong>Opgave.</strong> De to beskrivelser skulle beviseligt være identiske, automatisk, frem for periodisk at blive troet at være det.</p>\n<p><strong>Handling.</strong> Databasen er versioneret som 1.022 migrationer, nummereret fra 036 til 1102, og rækkefølgedisciplinen omkring dem er kedelig og ikke til forhandling. Det, der får den til at holde, er et CI‑job, der bygger databasen to gange ved hver ændring: én gang fra det friske initielle skema, én gang fra det initielle skema plus hver eneste migration afspillet i rækkefølge — og så sammenligner de to. Ikke bare strukturen, som er den nemme halvdel, men også de seedede data, for en migration, der backfiller en opslagstabel forkert, er præcis lige så skadelig som en, der glemmer en kolonne, og kun den ene af dem dukker op i en skema‑diff. Enhver uenighed får buildet til at fejle med forskellen printet ud.</p>\n<p><strong>Resultat.</strong> Et friskt miljø og et langtlevende er den samme database, og det bliver tjekket frem for antaget. Omkostningen lander hos den, der skriver en migration: den skal virke afspillet, og den skal virke fra koldt, hvilket er mere tanke, end et hurtigt ALTER som regel får. Det er netop pointen — alternativet er at finde ud af det under en gendannelse, hvor svaret betyder noget, og der ikke er tid til at regne det ud.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Data governance", "Databaser", "DevOps", "Drift & backup", "Migrering & modernisering", "PostgreSQL", "Test & QA", "Databaseadministration (DBA)", "Databasemigrering & modernisering", "DevOps & CI/CD‑automatisering" ] }, { "id": "https://engineer.company/da/portfolio/built-fail-closed-abuse-controls-and-rate-limiting-100/", "url": "https://engineer.company/da/portfolio/built-fail-closed-abuse-controls-and-rate-limiting-100/", "title": "Har bygget fail‑closed misbrugskontroller — 22 Redis‑baserede rate limiters, Cloudflare Turnstile, idempotens på requests og en origin‑lås — så platformen afviser bots og floods i stedet for at stole på sine kaldere.", "summary": "Byggede fail-closed misbrugskontroller: 22 Redis-baserede rate limiters, Cloudflare Turnstile, idempotens på requests og en origin-lås ved indgangen.", "content_html": "<p><strong>Situation.</strong> Et offentligt katalog over virksomheder og fagfolk er et mål fra den dag, det går i luften. Scrapere vil have dataene, spamkonti vil have rækkevidden, og et endpoint, der koster platformen rigtige penge at levere — søgning, eksport, alt der rører en ekstern API — er værd at misbruge alene, fordi det er gratis at kalde. Intet af det er ondskab rettet mod netop denne platform; det er baggrundsvejr på det åbne internet.</p>\n<p><strong>Opgave.</strong> De dyre stier og dem, der inviterer til misbrug, havde brug for grænser, der holder under pres — også presset fra, at limiterens egen afhængighed ikke er tilgængelig.</p>\n<p><strong>Handling.</strong> Der er 22 rate limitere, hver bygget til den sti, den beskytter, frem for ét globalt loft, for et loginforsøg, en søgning og en bulk‑eksport bliver misbrug ved vildt forskellige hastigheder. Tilstanden bor i Redis, så en grænse deles på tværs af instanser i stedet for at være per proces og trivielt at slippe uden om. Den vigtige beslutning er, hvad der sker, når Redis ikke er der: limiterne er fail‑closed. Trafik afvises frem for at blive vinket igennem, hvilket er det mindre bekvemme svar og det eneste forsvarlige. Omkring dem sidder Cloudflare Turnstile på de stier, der er værd at udfordre, 351 linjers idempotency‑middleware, så en genforsøgt skrivning ikke bliver til to, en origin‑lås, der afviser requests, som ikke kommer ind ad hoveddøren, og en skræddersyet challenge på selve kataloget.</p>\n<p><strong>Resultat.</strong> Misbrug bliver dyrt for den, der misbruger, og billigt for platformen, og et udfald i limiterens eget lager degraderer til afvisning frem for til en åben dør. At være fail‑closed betyder ganske vist, at et Redis‑problem bliver et problem, brugeren kan se — accepteret med vilje, fordi alternativet er, at et Redis‑problem bliver et regningsproblem.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "API'er & integration", "Backend‑udvikling", "Drift & backup", "Netværk & VPN", "Performanceoptimering", "Sikkerhed", "Backend- & API‑udvikling", "Sikkerhed & adgangsstyring", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/built-first-party-product-analytics-in-postgresql-101/", "url": "https://engineer.company/da/portfolio/built-first-party-product-analytics-in-postgresql-101/", "title": "Har bygget egen produktanalyse i PostgreSQL — 47 funktioner over partitionerede event‑tabeller, der selv rydder op — pseudonymiseret bag et roterende salt og betinget af den besøgendes samtykke.", "summary": "Byggede egen produktanalyse i PostgreSQL — 47 funktioner over partitionerede tabeller, der selv rydder op — pseudonymiseret og betinget af samtykke.", "content_html": "<p><strong>Situation.</strong> Produktbeslutninger har brug for tal, og den almindelige måde at få dem på er at sætte et third‑party‑tag på hver side og lade en andens servere holde øje med brugerne. Det er hurtigt, det er gratis ved små mængder, og det betyder, at besøgendes adfærd på en professionel netværksplatform — hvem der kiggede på hvilken virksomhed, hvem der søgte efter hvad — bliver et aktiv, en annoncevirksomhed sidder på. For en platform, hvis brugere er identificerbare maritime professionelle, er det en dårlig handel.</p>\n<p><strong>Opgave.</strong> Produktteamet havde brug for funnels, retention og eventdata, indsamlet på en måde, platformen kunne stå inde for, og en bruger kunne sige nej til.</p>\n<p><strong>Handling.</strong> Analytics kom til at ligge i PostgreSQL, i et skema på 7 tabeller og 47 funktioner. Event‑tabellerne er partitionerede — 22 partition‑definitioner — og rydder ud i sig selv på en tidsplan, for prisen ved first‑party analytics er ikke at indsamle events, den er at gemme dem for evigt. Identitet er pseudonymiseret bag et salt, der roterer, så en besøgende ikke kan følges hen over rotationsgrænsen, end ikke indefra databasen; det er et bevidst loft over, hvad dataene kan svare på. Klienten kender samtykkestatus og sender intet, før samtykke er givet, frem for at sende og filtrere bagefter. Konverteringsfunnels beregnes i databasen, ved siden af dataene, i stedet for at blive eksporteret et andet sted hen for at blive joinet tilbage.</p>\n<p><strong>Resultat.</strong> Spørgsmål om, hvordan produktet bruges, besvares fra platformens egne tabeller, uden at noget forlader den, og uden et tag i siden. Begrænsningerne er den ærlige del: at rotere saltet koster kohorteanalyse over lange horisonter, og intet af det her kommer med de dashboards, et hosted værktøj giver dig gratis. Begge dele blev accepteret med åbne øjne.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Data engineering", "Data governance", "Databaser", "Dataanalyse", "Performanceoptimering", "PostgreSQL", "SQL", "Data governance & datakvalitet", "Databasedesign & datamodellering", "Dataanalyse & BI‑dashboards", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/built-the-operator-back-office-for-the-platform-102/", "url": "https://engineer.company/da/portfolio/built-the-operator-back-office-for-the-platform-102/", "title": "Har bygget operatørernes back‑office — omkring 40 admin‑ruter og 128 komponenter, der dækker claims, moderation, feature flags, cache og diagnostik — så platformen kan drives uden databaseadgang.", "summary": "Byggede operatørernes back-office — omkring 40 admin-ruter og 128 komponenter til claims, moderation, feature flags, cache og diagnostik.", "content_html": "<p><strong>Situation.</strong> Enhver platform får stille og roligt en applikation nummer to, og det er som regel den, ingen planlægger. Nogen skal godkende et ejerskabskrav, skjule en anmeldelse, slå en funktion til for en delmængde af brugerne, rydde en cache eller finde ud af, hvorfor én konto ser noget mærkeligt. Når den applikation ikke findes, er svaret en engineer med en databasekonsol — hvilket er langsomt, ulogget og én slåfejl fra en hændelse.</p>\n<p><strong>Opgave.</strong> At drive platformen fra dag til dag skulle kunne lade sig gøre uden en shell, så driften hørte til hos den, der havde vagten, frem for hos den, der havde adgangsoplysningerne.</p>\n<p><strong>Handling.</strong> Back‑officen endte på omkring 40 admin‑ruter bygget af 128 komponenter, og den dækker arbejdet, som det faktisk kommer ind: at afgøre ejerskabskrav på virksomheder, moderere anmeldelser og opslag, vippe feature flags, inspicere og rydde caches, læse diagnostik og den rapportering, CEO’en beder om. Den kører på det samme designsystem og den samme genererede API‑klient som det offentlige produkt, hvilket var det afgørende valg — et internt værktøj bygget på sin egen stak bliver den del, ingen opdaterer, og derefter den del, ingen stoler på.</p>\n<p><strong>Resultat.</strong> De folk, der driver platformen, kan drive den, og hver handling går gennem den samme autorisation og efterlader det samme spor som alt andet. Det er en stor flade at holde testet og tilgængelig for et lille internt publikum, og den omkostning løber videre. Den er stadig billigere end alternativet, som er en engineer, der taster UPDATE mod produktion klokken ni en søndag aften.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Frontend‑udvikling", "Full stack‑udvikling", "Produkt & krav", "Systemadministration", "UX/UI‑design", "Full stack‑produktudvikling", "Produktstrategi & kravspecifikation" ] }, { "id": "https://engineer.company/da/portfolio/built-company-ownership-claims-end-to-end-103/", "url": "https://engineer.company/da/portfolio/built-company-ownership-claims-end-to-end-103/", "title": "Har bygget ejerskabskrav på virksomheder fra ende til anden — en bruger gør krav på en virksomhed, en administrator afgør sagen, og en godkendelse omskriver den autorisationsgraf, der afgør, hvem der må redigere hvad.", "summary": "Byggede ejerskabskrav på virksomheder fra ende til anden: en bruger gør krav, en administrator afgør, og godkendelsen omskriver autorisationsgrafen.", "content_html": "<p><strong>Situation.</strong> Et katalog seedet fra offentlige kilder har et strukturelt problem: virksomhederne i det har ikke selv sat sig der. Før eller siden dukker nogen fra en af dem op og vil rette sin egen post — og der er ingen relation mellem den person og den post, kun en påstand om, at der er en. Giver man det for villigt, redigerer en konkurrent din side. Giver man det for langsomt, bliver kataloget ved med at være forkert.</p>\n<p><strong>Opgave.</strong> Der skulle være en vej fra “det her er min virksomhed” til reel myndighed over posten, med en menneskelig beslutning i midten og et spor bagefter.</p>\n<p><strong>Handling.</strong> Ejerskabskrav blev bygget fra ende til anden, over 108 commits og begge applikationer. En bruger indsender et krav med dokumentation; det lander i en kø i back‑officen; en administrator gennemgår det og godkender eller afviser det med en begrundelse, der går tilbage til den, der rejste kravet. Det interessante er, hvad en godkendelse gør — det er ikke et flag på en række. Godkendelsen omskriver autorisationsgrafen, så kontoen får en rigtig relation til organisationen, og det er den samme relation, som hvert eneste rettighedstjek i platformen allerede slår op i. Gaten er afgørelsen, ikke kodestien, og ingen funktion har været nødt til at lære om ejerskabskrav for at respektere den.</p>\n<p><strong>Resultat.</strong> En virksomhed kan overtage og rette sin egen post, uden at nogen redigerer databasen i hånden, og hver tildeling af myndighed har en navngiven godkender og en begrundelse hæftet på. Den menneskelige gennemgang er flaskehalsen med vilje; et automatisk tjek på et domænenavn ville være hurtigere og ville tage fejl i præcis de tilfælde, der betyder mest.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Frontend‑udvikling", "Full stack‑udvikling", "Produkt & krav", "Sikkerhed", "Full stack‑produktudvikling", "Produktstrategi & kravspecifikation", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/established-a-continuous-security-programme-104/", "url": "https://engineer.company/da/portfolio/established-a-continuous-security-programme-104/", "title": "Har etableret et løbende sikkerhedsprogram — code scanning, DAST, afhængigheds- og sårbarhedstjek, SBOM‑generering, secret scanning og SHA‑pinnede actions — sammen med 21 skrevne sikkerhedsaudits.", "summary": "Etablerede et løbende sikkerhedsprogram — code scanning, DAST, afhængighedstjek, SBOM, secret scanning og pinnede actions — plus 21 audits.", "content_html": "<p><strong>Situation.</strong> Sikkerhedsarbejdet inde i applikationen — Content‑Security‑Policy’en, least‑privilege‑databaserollerne, gentjekket af rettigheder — beskytter platformen ved runtime. Intet af det siger noget om det, der bliver sendt afsted: om en dependency samlede en kendt sårbarhed op i tirsdags, om en adgangsoplysning blev committet og rullet tilbage, eller om en third‑party‑action pinnet til et tag stille og roligt er blevet til et andet stykke kode.</p>\n<p><strong>Opgave.</strong> Supply chain- og kodesikkerhed skulle være løbende og automatiseret, så tilstanden af det var et build‑resultat frem for en holdning.</p>\n<p><strong>Handling.</strong> Statisk analyse kører gennem CodeQL, dynamisk test mod en kørende instans gennem ZAP, og Go‑dependencies tjekkes med govulncheck. En SBOM genereres ved hvert build med Anchore og Syft, så det, der blev sendt afsted, er kendt frem for rekonstrueret bagefter. Gitleaks scanner historikken for adgangsoplysninger. Hver third‑party GitHub Action er pinnet til en commit‑hash frem for et tag, hvilket er den uglamourøse kontrol, der forhindrer, at et tag bliver flyttet under dig. Dependabot holder øje med 6 økosystemer. Ved siden af automatiseringen ligger 21 skrevne sikkerhedsaudits, heriblandt en threat model og en vurdering op mod OWASP‑testguiden — for scannere finder de klasser af problemer, nogen allerede har beskrevet, og en threat model er der, hvor dem, der er specifikke for netop denne platform, får et navn.</p>\n<p><strong>Resultat.</strong> En sårbarhed, der bliver offentliggjort upstream, dukker op som et fejlende build frem for som en nyhed. Mængden af fund er den reelle omkostning — en scanner, der rapporterer alt, træner folk i at ignorere den, og at holde signalet brugbart kræver løbende triage frem for en engangsopsætning.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Dokumentation", "Drift & backup", "Sikkerhed", "DevOps & CI/CD‑automatisering", "Sikkerhed & adgangsstyring", "Teknisk dokumentation" ] }, { "id": "https://engineer.company/da/portfolio/built-the-loyalty-and-reputation-system-105/", "url": "https://engineer.company/da/portfolio/built-the-loyalty-and-reputation-system-105/", "title": "Har bygget loyalitets- og omdømmesystemet — 67 funktioner over et ledger på 31 tabeller, med ligaer, badges og en indløsningsshop — med en row lock på saldoen, der lukker double‑spend‑vinduet.", "summary": "Byggede loyalitets- og omdømmesystemet — 67 funktioner over et ledger på 31 tabeller, ligaer, badges og en shop — med row lock på saldoen.", "content_html": "<p><strong>Situation.</strong> Professionelt netværk har et cold start‑problem: platformen er værd at bruge, når andre allerede bruger den, og indtil da er der ikke meget grund til at komme tilbage. Den sædvanlige løftestang er et belønningssystem — point for at bidrage, en standing der afspejler omdømme — som er nemt at beskrive og lumsk at bygge, for i det øjeblik point kan bruges, er de penge, og hver eneste fejl, penge kan lave, kan laves her.</p>\n<p><strong>Opgave.</strong> Bidrag skulle kunne måles og belønnes, med en saldo, der ikke kunne bruges to gange.</p>\n<p><strong>Handling.</strong> Loyalty‑skemaet løber op i 31 tabeller og 67 funktioner, med standing i yderligere 5 tabeller og 32 funktioner, og discovery- og personalization‑skemaer ved siden af til at afgøre, hvad et givent medlem ser. Oven på ledgeren sidder ligaer, badges og en indløsningsshop, hvor en saldo bliver til noget virkeligt. Det, der krævede omhuen, er den ældste fejl i bogen: tjek saldoen, brug den så, og to requests, der ankommer samtidig, passerer begge tjekket. Hver mutation tager en row lock på walleten, før den læser den, så den anden request venter på, at den første bliver færdig, frem for at kappes med den. At walleten ligger i den samme database som alt andet er det, der overhovedet gør det muligt — saldoen og det, den købte, committer sammen eller slet ikke.</p>\n<p><strong>Resultat.</strong> Bidrag bliver målt og belønnet, og en saldo er et regnestykke frem for en tilnærmelse. Row locking er det langsommere svar og blev valgt alligevel: kamp om den samme wallet bliver til en kø, og alternativet er et medlem, der bruger de samme point to gange, og nogen, der afstemmer det i hånden bagefter.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Databaser", "Performanceoptimering", "PostgreSQL", "Produkt & krav", "SQL", "Backend- & API‑udvikling", "Databasedesign & datamodellering", "Produktstrategi & kravspecifikation" ] }, { "id": "https://engineer.company/da/portfolio/built-the-platform-s-social-layer-106/", "url": "https://engineer.company/da/portfolio/built-the-platform-s-social-layer-106/", "title": "Har bygget platformens sociale lag — opslag, feed, grupper, mentions, en følgergraf og et digest over mærkedage — på samme function‑first datalag som resten af produktet.", "summary": "Byggede platformens sociale lag — opslag, feed, grupper, mentions, følgergraf og et digest over mærkedage — på samme function-first datalag.", "content_html": "<p><strong>Situation.</strong> Et katalog over virksomheder er et opslagsværk. Folk slår op i det og går igen. Det, der gør en professionel platform værd at vende tilbage til, er, at der er andre mennesker på den — hvilket betyder opslag, grupper og en grund til at komme tilbage, som ikke er en mail, der beder dig om det.</p>\n<p><strong>Opgave.</strong> Der skulle lægges et socialt lag på, uden at det blev til et system nummer to med sine egne regler, sine egne rettigheder og sin egen måde at gemme ting på.</p>\n<p><strong>Handling.</strong> Opslag, et feed, grupper, mentions, en follower‑graf og et digest over mærkedage gik ind over 188 commits, alt sammen på det samme function‑first datalag som resten af platformen. Den begrænsning gjorde det meste af arbejdet: follower‑grafen er tabeller og funktioner som alt andet, en mention slås op gennem det samme identity‑skema, som kataloget bruger, og et opslag arver den moderationskø, der allerede var bygget til anmeldelser. Intet her havde brug for sit eget lager eller sin egen rettighedsmodel. Feedet er det ene sted, der satte sig imod, for at samle en personaliseret timeline effektivt er et oprigtigt anderledes problem end at hente en række, og det er der, personalization- og discovery‑arbejdet gør sig fortjent til sin plads.</p>\n<p><strong>Resultat.</strong> Platformen har en grund til at blive åbnet på en dag, hvor ingen har brug for at slå en virksomhed op, og den kom uden en parallel stak at vedligeholde. Om et socialt lag er den rigtige investering for et maritimt katalog, er et produktspørgsmål frem for et engineering‑spørgsmål — det er her, fordi produktet bad om det, og det er bygget på samme måde som alt omkring det.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Databaser", "Frontend‑udvikling", "Full stack‑udvikling", "Produkt & krav", "Webudvikling", "Backend- & API‑udvikling", "Full stack‑produktudvikling", "Produktstrategi & kravspecifikation" ] }, { "id": "https://engineer.company/da/portfolio/built-the-hiring-marketplace-and-career-workspace-107/", "url": "https://engineer.company/da/portfolio/built-the-hiring-marketplace-and-career-workspace-107/", "title": "Har bygget rekrutteringsmarkedspladsen og søfarendes karriere‑workspace — 151 stored functions på tværs af 48 tabeller — med ledige stillinger, ansøgninger, certifikater, avancement i grader og verificeret sejltid.", "summary": "Byggede jobmarkedspladsen og søfarendes karriereområde — 151 stored functions på tværs af 48 tabeller — stillinger matchet på dokumenterede kvalifikationer.", "content_html": "<p><strong>Situation.</strong> Det kommercielle argument for et maritimt professionelt netværk er rekruttering: virksomheder har brug for besætning og officerer, og søfarende har brug for hyre. Begge halvdele fandtes allerede på platformen i den forkerte form — virksomhederne lå i kataloget, de professionelle havde profiler, og der var intet, der forbandt en ledig stilling med den person, der var kvalificeret til at udfylde den. Kvalifikationen er den svære del, for i denne branche er det certifikater, grader og dokumenteret sejltid frem for en jobtitel.</p>\n<p><strong>Opgave.</strong> Ledige stillinger, ansøgninger og verificerbare kvalifikationsbeviser for søfarende skulle modelleres ordentligt frem for som fritekst på en profil.</p>\n<p><strong>Handling.</strong> To domæner blev bygget. Rekrutteringssiden løber op i 32 tabeller og 118 funktioner, der dækker ledige stillinger, ansøgninger, shortlisting og arbejdsgiverens billede af en pipeline. Karriere‑workspacet bærer 16 tabeller og 33 funktioner, der rummer certifikater, gradprogression og sejltid, med dokumentupload og en gennemgangskø, så et kvalifikationsbevis bliver kontrolleret frem for påstået. At modellere progression som en graf frem for en liste er det, der gør matchningen brugbar — en grad er nåelig fra en anden grad givet bestemte certifikater og nok registreret tid til søs, og den struktur er det, der lader en ledig stilling blive matchet mod en karriere frem for mod et nøgleord. Karriere‑workspacet sendes bag et feature flag og er ikke fuldt released; rekrutteringssiden er live.</p>\n<p><strong>Resultat.</strong> En ledig stilling kan matches mod dokumenterede kvalifikationer i stedet for en selvbeskrevet jobtitel, og det er hele forskellen mellem et jobopslagssite og et rekrutteringsværktøj i denne branche. Verifikationen er flaskehalsen, med vilje — en gennemgangskø skalerer ikke, som et automatisk tjek ville, og et automatisk tjek ville certificere folk, der ikke burde certificeres.</p>\n", "date_published": "2026-08-17T18:37:15+02:00", "date_modified": "2026-08-17T18:37:15+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Data governance", "Databaser", "Full stack‑udvikling", "PostgreSQL", "Produkt & krav", "Backend- & API‑udvikling", "Databasedesign & datamodellering", "Full stack‑produktudvikling", "Produktstrategi & kravspecifikation" ] }, { "id": "https://engineer.company/da/portfolio/accelerated-geographical-data-pipeline-performance-by-50x-by-5/", "url": "https://engineer.company/da/portfolio/accelerated-geographical-data-pipeline-performance-by-50x-by-5/", "title": "Har øget ydeevnen af geografiske datapipelines med 50 gange ved at forbedre SQL‑programmering og datamodellering på tværs af PostgreSQL, MS SQL og Google Cloud BigQuery.", "summary": "Accelererede en geografisk datapipeline 50× via SQL- og datamodeloptimering på tværs af PostgreSQL, MS SQL og Google BigQuery.", "content_html": "<p><strong>Situation.</strong> Organisationen oplevede betydelige forsinkelser i behandlingen af store mængder geografiske data, hvilket påvirkede effektiviteten af datadrevne beslutningsprocesser og analytisk rapportering.</p>\n<p><strong>Opgave.</strong> Målet var at optimere den geografiske datapipeline for at forbedre ydeevnen og reducere behandlingstiden, så dataanalyse kunne udføres mere effektivt og i realtid.</p>\n<p><strong>Handling.</strong> De eksisterende SQL‑programmerings- og datamodelleringsstrukturer på tværs af PostgreSQL, MS SQL og Google Cloud BigQuery blev analyseret grundigt, hvilket afslørede flaskehalse relateret til ineffektive indekseringsstrategier, suboptimale forespørgsler og manglende constraints. For at afhjælpe dette blev datamodellerne redesignet og partitioneringsstrategier indført, avancerede indekseringsteknikker (B‑tree og GiST i PostgreSQL, filtrerede indeks i MS SQL og clustering i BigQuery) anvendt, komplekse SQL‑forespørgsler optimeret ved at refaktorere subqueries og reducere joins, samt data integrity‑constraints som foreign keys og check‑constraints indført for at sikre konsistens uden at gå på kompromis med ydeevnen.</p>\n<p><strong>Resultat.</strong> Disse omfattende optimeringer accelererede den geografiske datapipelines ydeevne 50 gange og reducerede databehandlingstiden markant. Forbedringen muliggjorde realtidsanalyse, styrkede rapporteringskapaciteten og gav interessenterne rettidige, datadrevne indsigter, hvilket i sidste ende bidrog til mere velfunderede strategiske beslutninger.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Data engineering", "Data governance", "Data warehousing", "Databaser", "Datapipelines (ETL/ELT)", "GIS / Geospatial", "Performanceoptimering", "PostgreSQL", "SQL", "Databaseoptimering", "Design af data warehouse", "GIS & geospatiale løsninger", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/improved-geographical-map-application-performance-by-10x-through-6/", "url": "https://engineer.company/da/portfolio/improved-geographical-map-application-performance-by-10x-through-6/", "title": "Har forbedret geografiske kortapplikationers ydeevne med 10 gange gennem en strategisk databaseovergang fra MSSQL til PostgreSQL, hvilket har optimeret behandlingen og datasikkerheden.", "summary": "Forbedrede en GIS-kortapplikation 10× ved migrering fra MSSQL til PostgreSQL — spatiale forespørgsler fra 2,5 s til under 250 ms.", "content_html": "<p><strong>Situation.</strong> Organisationens geografiske kortapplikation, der understøttede realtids‑spatialforespørgsler for mange brugere, oplevede alvorlige flaskehalse i ydeevnen. Latens ved rendering af kortlag og forespørgsler på lokationsbaserede data påvirkede både brugeroplevelsen og backend‑tjenesternes pålidelighed. Systemet byggede på en ældre Microsoft SQL Server‑database (MSSQL) uden native geospatial indeksering.</p>\n<p><strong>Opgave.</strong> At forbedre kortapplikationens ydeevne, skalerbarhed og sikkerhed med det specifikke mål at reducere forespørgselslatensen og øge gennemløbet for spatiale operationer.</p>\n<p><strong>Handling.</strong> En strategisk databasemigrering fra MSSQL til PostgreSQL med PostGIS‑udvidelsen blev ledet for at muliggøre native geospatial understøttelse. Et nyt skema optimeret til spatiale data blev designet med GiST- og SP‑GiST‑indeks på geometri- og geografikolonner, strikse foreign key- og check‑constraints defineret, over 50 millioner spatiale records migreret via ETL‑pipelines med transformation til nye SRID‑standarder (EPSG:4326), PostgreSQL‑konfigurationsparametre (work_mem, effective_cache_size) tunet og role‑based access control (RBAC) og row‑level security implementeret.</p>\n<p><strong>Resultat.</strong> En 10‑dobbelt forbedring i spatial forespørgselsydeevne blev opnået, og den gennemsnitlige svartid reduceret fra 2,5 sekunder til under 250 millisekunder. Backend‑CPU‑belastningen faldt med 65 %, og systemtilgængeligheden blev bedre i spidsbelastningsperioder. Sikkerheden blev også styrket med granulære adgangspolitikker og valideringsconstraints, hvilket reducerede risikoen for korruption af spatiale data.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Data engineering", "Databaser", "Datapipelines (ETL/ELT)", "GIS / Geospatial", "Migrering & modernisering", "Performanceoptimering", "PostgreSQL", "Sikkerhed", "SQL", "Databasemigrering & modernisering", "Databaseoptimering", "GIS & geospatiale løsninger" ] }, { "id": "https://engineer.company/da/portfolio/resolved-1-000-issues-in-geographical-data-and-7/", "url": "https://engineer.company/da/portfolio/resolved-1-000-issues-in-geographical-data-and-7/", "title": "Har løst 1.000 problemer i geografiske data og tidsseriedata ved hjælp af GDAL, ArcGIS, PostGIS, Mapbox, QGIS, SQL (PL/pgSQL, Transact‑SQL) og Bash, hvilket har sikret behandling af big data i høj kvalitet.", "summary": "Løste 1.000+ problemer i geografiske og tidsseriedata med GDAL, PostGIS, ArcGIS, Mapbox, QGIS og SQL — 35 % hurtigere forespørgsler.", "content_html": "<p><strong>Situation.</strong> Under arbejdet på et storstilet geospatialt analyseprojekt stødte teamet på talrige uoverensstemmelser og anomalier i de geografiske datasæt og tidsseriedatasæt. Disse problemer påvirkede nøjagtigheden af spatiale analyser og beslutningsværktøjer på tværs af flere afdelinger.</p>\n<p><strong>Opgave.</strong> Ansvaret var at identificere, løse og optimere over 1.000 datakvalitetsproblemer i disse komplekse datasæt for at sikre integriteten og ydeevnen af downstream‑applikationer og visualiseringer.</p>\n<p><strong>Handling.</strong> Spatiale fejl blev systematisk diagnosticeret og rettet med en kombination af værktøjer, herunder GDAL, QGIS og ArcGIS, og automatiserede workflows implementeret med Bash‑scripting til tilbagevendende datarensning. PostGIS blev brugt til avancerede spatiale forespørgsler og spatial indeksering, og robuste procedurer skrevet i PL/pgSQL og Transact‑SQL til at håndtere og transformere både geografiske og tidsmæssige data i PostgreSQL- og SQL Server‑databaserne. Derudover blev de rensede data integreret i interaktive visualiseringer med Mapbox.</p>\n<p><strong>Resultat.</strong> Gennem denne indsats blev over 1.000 kritiske problemer løst, hvilket markant forbedrede datanøjagtigheden og behandlingshastigheden. Det bidrog direkte til en 35 % reduktion i køretider for spatiale forespørgsler og muliggjorde mere pålidelige spatiale analyser. Arbejdet sikrede, at data af høj kvalitet konsekvent var tilgængelige til analyse og rapportering til støtte for strategiske beslutninger på tværs af organisationen.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Data engineering", "Data governance", "Databaser", "Datapipelines (ETL/ELT)", "GIS / Geospatial", "Performanceoptimering", "PostgreSQL", "SQL", "Data governance & datakvalitet", "GIS & geospatiale løsninger", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/designed-implemented-and-administered-6-etl-elt-pipelines-8/", "url": "https://engineer.company/da/portfolio/designed-implemented-and-administered-6-etl-elt-pipelines-8/", "title": "Har designet, implementeret og administreret 6 ETL/ELT‑pipelines med Google BigQuery, MSSQL, PostgreSQL, shell‑scripting, PL/pgSQL og Transact‑SQL og integreret data til effektiv behandling via Python API.", "summary": "Designede og driftede 6 ETL/ELT-pipelines (BigQuery, MSSQL, PostgreSQL) — fra 3 timers manuelt arbejde til under 20 minutter, dagligt.", "content_html": "<p><strong>Situation.</strong> Organisationen havde behov for at konsolidere og behandle store mængder strukturerede data fra et fjernt data warehouse bag en IPSec VPN. Disse data var afgørende for interne analyse‑dashboards og eksterne Python API’er brugt af kunder og partnere.</p>\n<p><strong>Opgave.</strong> Målet var at designe, implementere og vedligeholde et sæt robuste og automatiserede ETL/ELT‑pipelines til sikkert at hente, transformere og indlæse data i Google BigQuery med sikring af datanøjagtighed, ydeevne og skalerbarhed.</p>\n<p><strong>Handling.</strong> 6 end‑to‑end ETL/ELT‑pipelines blev designet, implementeret og administreret på tværs af Google BigQuery, MSSQL, PostgreSQL, shell‑scripting, PL/pgSQL og Transact‑SQL. Sikre forbindelser til en fjernserver bag en IPSec VPN blev etableret, download af komprimerede dataarkiver (Parquet, CSV og .bak) planlagt, Bash- og Python‑scripts udviklet til at udtrække og klassificere filer, .bak‑filer gendannet i en lokal MSSQL Server‑instans, strukturerede data indlæst i staging‑skemaer med bcp, psql og SSIS, modulære PL/pgSQL- og T‑SQL‑procedurer skrevet til rensning og berigelse og upload og skema‑mapping til Google BigQuery automatiseret via bq CLI og Python‑baseret dataindtagelse.</p>\n<p><strong>Resultat.</strong> Disse automatiserede pipelines reducerede den manuelle indsats og behandlingstid markant — fra over 3 timers manuelt arbejde til under 20 minutter end‑to‑end — og forbedrede dataaktualiteten fra ugentlig til daglig synkronisering. Python‑API’erne, der forbrugte dataene, opnåede en 30 % ydeevneforbedring, og den øgede synlighed hjalp forretningsanalytikere med at levere hurtigere indsigter. Løsningen forbliver skalerbar og udvidelig til nye datakilder, efterhånden som forretningen vokser.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Cloud", "Data engineering", "Data warehousing", "Databaser", "Datapipelines (ETL/ELT)", "Netværk & VPN", "PostgreSQL", "Python", "SQL", "Backend- & API‑udvikling", "Databasedesign & datamodellering", "Design af data warehouse", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/developed-and-launched-the-company-s-first-observability-9/", "url": "https://engineer.company/da/portfolio/developed-and-launched-the-company-s-first-observability-9/", "title": "Har udviklet og lanceret virksomhedens første observability‑dashboard, der leverer indsigt i systemets ydeevne i realtid og datavisualisering på det store kontor‑TV.", "summary": "Byggede virksomhedens første observability-dashboard til realtidsindsigt — teams opdagede og løste incidents 40 % hurtigere.", "content_html": "<p><strong>Situation.</strong> Virksomheden havde udfordringer med at overvåge systemets ydeevne i realtid, hvilket ofte førte til forsinket incidenthåndtering og reduceret indblik i infrastrukturens tilstand. Der fandtes ingen central løsning, hvor teams kunne få indsigt i driftsmetrikker.</p>\n<p><strong>Opgave.</strong> Opgaven var at udvikle en løsning, der ville gøre det muligt for både tekniske og ikke‑tekniske interessenter at overvåge centrale systemmetrikker i realtid med fokus på tilgængelighed, klarhed og proaktiv fejlopsporing.</p>\n<p><strong>Handling.</strong> Virksomhedens første observability‑dashboard blev designet og implementeret og indsamlede alle væsentlige systemmetrikker — CPU, RAM, HDD, temperatur med mere — fra fjerne Linux‑servere via SSH, og selv Docker‑containere blev overvåget på denne måde. Senere blev en anden version designet og implementeret med Grafana og Prometheus til mere avanceret visualisering. Samarbejde med DevOps- og engineering‑teams identificerede kritiske metrikker, datapipelines blev konfigureret til at indsamle og behandle ydeevnemetrikker, og dashboardet udrullet på en stor kontor‑TV‑skærm for maksimal synlighed. Alarmering ved overskridelse af tærskler blev også integreret.</p>\n<p><strong>Resultat.</strong> Dashboardet forbedrede markant systemets gennemsigtighed og responstiden på driftsproblemer. Teams kunne opdage og løse incidents 40 % hurtigere. Det fremmede også en kultur af fælles ejerskab over systemets tilstand ved at gøre ydeevnedata tilgængelige for alle på kontoret, hvilket i sidste ende bidrog til et mere stabilt og effektivt produktionsmiljø.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Containere (Docker/Kubernetes)", "Dataanalyse", "DevOps", "Drift & backup", "Infrastruktur", "Linux & servere", "Monitorering & observability", "Dataanalyse & BI‑dashboards", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/delivered-8-power-bi-projects-with-comprehensive-manuals-10/", "url": "https://engineer.company/da/portfolio/delivered-8-power-bi-projects-with-comprehensive-manuals-10/", "title": "Har leveret 8 Power BI‑projekter med omfattende manualer, hvor Microsoft Power BI‑værktøjer er integreret med NodeJS API og Python FastAPI for effektiv dataanalyse og visualisering.", "summary": "Leverede 8 Power BI-analyseprojekter (Node.js API, Python FastAPI) med manualer — 60 % mere effektiv rapportgenerering.", "content_html": "<p><strong>Situation.</strong> Organisationen havde behov for dynamiske, visuelle indsigter i komplekse datasæt om elnettets ydeevne og geografisk fordeling for at understøtte beslutninger på tværs af tekniske og strategiske teams.</p>\n<p><strong>Opgave.</strong> At udvikle interaktive dashboards og rapporteringsløsninger, der effektivt kunne præsentere både realtids- og historiske geografiske og elektriske data, så interessenter hurtigt kunne identificere tendenser, anomalier og nøgletal.</p>\n<p><strong>Handling.</strong> Microsoft Power BI blev integreret med en skræddersyet backend baseret på NodeJS API og Python FastAPI for at strømline dataindtagelse, transformation og visualisering. Dashboards blev designet og implementeret med kortvisualiseringer, målinger af energiforbrug, sporing af nedbrud og indikatorer for neteffektivitet, og genanvendelige skabeloner og detaljeret dokumentation udviklet for at understøtte skalerbarhed og brugervenlighed.</p>\n<p><strong>Resultat.</strong> 8 dataanalyseprojekter blev leveret, hvilket forbedrede effektiviteten af rapportgenerering med 60 % og gjorde det muligt for tværfaglige teams at træffe hurtigere, datadrevne beslutninger. Interessenterne rapporterede en markant øget forståelse af den regionale elektriske ydeevne og nøjagtigheden af ressourceplanlægningen.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "API'er & integration", "Backend‑udvikling", "Data engineering", "Dataanalyse", "Dokumentation", "GIS / Geospatial", "Produkt & krav", "Python", "Backend- & API‑udvikling", "Dataanalyse & BI‑dashboards", "Teknisk dokumentation" ] }, { "id": "https://engineer.company/da/portfolio/released-500-electricity-and-gis-data-analysis-reports-11/", "url": "https://engineer.company/da/portfolio/released-500-electricity-and-gis-data-analysis-reports-11/", "title": "Har udgivet 500 rapporter om analyse af el- og GIS‑data, hvor jeg har anvendt dybdegående forskning og fejlfinding for at sikre nøjagtige geografiske og tidsmæssige big data‑indsigter.", "summary": "Udgav 500 rapporter om el- og GIS-dataanalyse — en tværgående reference for load balancing, energieffektivitet og anomalidetektion.", "content_html": "<p><strong>Situation.</strong> Teamet håndterede enorme datasæt genereret af smart‑metre installeret på tværs af flere geografiske regioner. Disse smart‑metre producerede granulære tidsserie‑data om elforbrug, som blev brugt af energianalytikere, ingeniører og regionale planlæggere til drifts- og strategiske beslutninger.</p>\n<p><strong>Opgave.</strong> Ansvaret var at producere analytiske rapporter af høj kvalitet, gennemsigtige og reproducerbare, som kunne afdække mønstre i energiforbruget, opdage anomalier og identificere regionale forbrugstendenser, samtidig med at ikke‑tekniske interessenter let kunne fortolke og genbruge resultaterne.</p>\n<p><strong>Handling.</strong> Over 500 dybdegående dataanalyserapporter blev oprettet og leveret, med ren SQL til al dataudtræk, transformation og analyse direkte i cloud‑baserede miljøer som PostgreSQL og BigQuery. Dataene omfattede geolokationskoordinater, meter‑ID’er, tidsstemplet energiforbrug og miljømetadata. SQL‑scripts benyttede CTE’er, window functions, subqueries og geospatiale joins for skalerbar og effektiv behandling. Hver rapport indeholdt annoteret SQL‑kode, så kolleger fuldt ud kunne reproducere og revidere forskningen, og fejlfindingsnoter blev tilføjet og almindelige datakvalitetsproblemer dokumenteret med anbefalede håndteringsprocedurer.</p>\n<p><strong>Resultat.</strong> Rapporterne blev en standardreference på tværs af afdelinger og hjalp med regional load balancing, planlægning af energieffektivitet og anomalidetektion. Ved at sikre fuld gennemsigtighed og reproducerbarhed hjalp det med at styrke interessenternes tillid til dataene, og arbejdet bidrog til mere præcise prognosemodeller og en 10–15 % forbedring af driftsplanlægningens effektivitet.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Data governance", "Data warehousing", "Databaser", "Dataanalyse", "Dokumentation", "GIS / Geospatial", "PostgreSQL", "SQL", "Stakeholder & rapportering", "Data governance & datakvalitet", "Dataanalyse & BI‑dashboards", "GIS & geospatiale løsninger" ] }, { "id": "https://engineer.company/da/portfolio/architected-created-and-managed-100-postgresql-ms-sql-12/", "url": "https://engineer.company/da/portfolio/architected-created-and-managed-100-postgresql-ms-sql-12/", "title": "Har arkitekteret, oprettet og administreret 100 data warehouse‑databaser i PostgreSQL, MS SQL og Google BigQuery med primært GIS- og tidsseriedata og optimeret ydeevne og skalerbarhed.", "summary": "Arkitekterede og driftede 100 data warehouse-databaser (PostgreSQL, MS SQL, BigQuery) for GIS- og tidsseriedata — 40–60 % hurtigere.", "content_html": "<p><strong>Situation.</strong> I en hurtigt voksende teknologivirksomhed var der et kritisk behov for at oprette, administrere og optimere en bred portefølje af over 100 databaser på tværs af PostgreSQL, Microsoft SQL Server og Google BigQuery. Disse databaser håndterede primært komplekse datasæt, herunder GIS‑data (spatiale koordinater og lokationsbaseret analyse) og tidsseriedata (sensoraflæsninger, logs og realtidsmetrikker). Den eksisterende infrastruktur havde udfordringer med skalerbarhed, forespørgselsydeevne og datakonsistens, efterhånden som datamængden voksede eksponentielt.</p>\n<p><strong>Opgave.</strong> Det primære mål var at designe, implementere og administrere et skalerbart data warehouse‑økosystem med høj ydeevne skræddersyet til GIS- og tidsseriedata. Det indebar at håndtere flaskehalse i komplekse spatiale og tidsbaserede forespørgsler, sikre skalerbarhed og omkostningseffektivitet og samarbejde med tværfaglige teams om at afstemme databasedesign med forretningsbehov.</p>\n<p><strong>Handling.</strong> Skalerbare arkitekturer blev designet med normaliserede og denormaliserede skemaer til PostgreSQL og SQL Server, spatial indeksering (PostGIS) og tidsseriepartitionering udnyttet samt BigQuerys tidspartitionerede og clustrede tabeller. Optimeringsstrategier som indeksering, materialized views og caching blev indført, datakomprimering og kolonnelagring anvendt i BigQuery og best practices for GIS- og tidsseriedatamodellering dokumenteret til fremtidige projekter.</p>\n<p><strong>Resultat.</strong> Initiativerne førte til betydelige forbedringer: svartider for GIS- og tidsseriedata faldt med 40–60 %, automatiseret overvågning reducerede nedetid med 50 %, og standardiserede processer øgede produktiviteten. Den optimerede infrastruktur gjorde det muligt for virksomheden at lancere nye datadrevne produkter og opfylde krav om data governance og regulatorisk compliance — hvilket styrkede organisationens evne til at håndtere komplekse datamæssige udfordringer.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Data engineering", "Data governance", "Data warehousing", "Databaser", "Drift & backup", "GIS / Geospatial", "Performanceoptimering", "Platformarkitektur", "PostgreSQL", "SQL", "Databaseadministration (DBA)", "Databasedesign & datamodellering", "Databaseoptimering", "Design af data warehouse", "GIS & geospatiale løsninger" ] }, { "id": "https://engineer.company/da/portfolio/designed-deployed-and-maintained-10-postgresql-and-ms-13/", "url": "https://engineer.company/da/portfolio/designed-deployed-and-maintained-10-postgresql-and-ms-13/", "title": "Har designet, idriftsat og vedligeholdt 10 PostgreSQL- og MS SQL‑servere på Ubuntu Linux VPS og sikret optimal serverydeevne og pålidelighed.", "summary": "Designede og vedligeholdt 10 PostgreSQL- og MS SQL-servere på Ubuntu Linux VPS med 99,9 % oppetid og 30 % hurtigere forespørgsler.", "content_html": "<p><strong>Situation.</strong> Som DevOps‑ingeniør i en mellemstor teknologivirksomhed var opgaven at administrere og optimere databaseinfrastrukturen til støtte for en voksende brugerbase og forretningskritiske applikationer. Organisationen var stærkt afhængig af PostgreSQL og Microsoft SQL Server og krævede høj tilgængelighed, skalerbarhed og sikkerhed.</p>\n<p><strong>Opgave.</strong> Det primære ansvar var at arkitektere, udrulle, konfigurere og vedligeholde 10 PostgreSQL- og MS SQL Server‑instanser i Ubuntu Linux VPS‑miljøer med optimal ydeevne, robuste sikkerhedsprotokoller og proaktiv overvågning, samt at skalere infrastrukturen til fremtidig vækst uden at gå på kompromis med omkostninger og compliance.</p>\n<p><strong>Handling.</strong> PostgreSQL 14 og MS SQL Server 2019 blev installeret og konfigureret på Ubuntu 20.04 LTS, automatiserede backups sat op med pg_dump og SQL Server Agent‑jobs med retention og offsite‑lagring, og serverkonfigurationer optimeret (hukommelsesallokering, query caching, connection pooling). Overvågning blev implementeret med Prometheus og Grafana, regelmæssig patching af databaser og OS udført, firewalls (UFW) og RBAC konfigureret og disaster recovery‑procedurer dokumenteret, herunder point‑in‑time‑gendannelse og failover.</p>\n<p><strong>Resultat.</strong> 99,9 % oppetid blev opnået på tværs af alle 10 databaseservere, svartider forbedret med 30 % gennem konfigurationstuning og indeksoptimering, manuelt vedligeholdelsesarbejde reduceret med 50 % via automatisering (over 10 timer om måneden frigjort) og infrastrukturen skaleret til at understøtte en 40 % stigning i brugertrafik uden servicetab — hvilket bidrog til 20 % omsætningsvækst i det følgende kvartal. CTO’en anerkendte implementeringen af sikkerheds‑best‑practices, der forhindrede potentielle databrud.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Databaser", "DevOps", "Drift & backup", "Infrastruktur", "Linux & servere", "Monitorering & observability", "Performanceoptimering", "PostgreSQL", "Sikkerhed", "Systemadministration", "Databaseadministration (DBA)", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/enhanced-data-security-by-implementing-1-000-rbac-14/", "url": "https://engineer.company/da/portfolio/enhanced-data-security-by-implementing-1-000-rbac-14/", "title": "Har forbedret datasikkerheden ved at implementere 1.000 RBAC‑regler for udviklere, applikationsinstanser, PostgreSQL, MS SQL og andre Linux‑servere for at forhindre uautoriseret adgang; dokumenteret med Ansible‑automatisering.", "summary": "Styrkede datasikkerheden med 1.000 RBAC-regler på tværs af udviklere, apps og Linux/DB-servere — 85 % lavere adgangsrisiko, Ansible-drevet.", "content_html": "<p><strong>Situation.</strong> Organisationen havde behov for at styrke adgangskontrollen på tværs af flere systemer, herunder udviklermiljøer, applikationsinstanser, PostgreSQL, MS SQL og Linux‑servere. Selvom den underliggende RBAC‑model var enkel, lå udfordringen i at håndtere over 1.000 individuelle regler for forskellige brugerroller og systemkrav uden at introducere kompleksitet.</p>\n<p><strong>Opgave.</strong> At implementere en skalerbar RBAC‑løsning ved at definere og håndhæve over 1.000 regler for adgangskontrol. Det indebar at mappe rettigheder til specifikke roller (udviklere, applikationsinstanser, databaseadministratorer) og at anvende reglerne konsistent på tværs af alle systemer, samt at dokumentere reglerne og automatisere deres udrulning.</p>\n<p><strong>Handling.</strong> Tilgangen fokuserede på at skabe en enkel, modulær RBAC‑struktur, med rettigheder brudt ned i klare, genanvendelige kategorier (f.eks. “read‑only‑adgang til produktionsdatabaser”). Med Ansible blev konfigurationen af hver regel automatiseret og konsistens sikret på tværs af miljøer: udviklere fik kun adgang til deres tildelte servere, mens applikationsinstanser havde begrænsede rettigheder for at forhindre lateral bevægelse. Processen prioriterede klarhed frem for kompleksitet, med hver regel eksplicit knyttet til en rolle og et system.</p>\n<p><strong>Resultat.</strong> Implementeringen sikrede over 1.000 regler uden unødvendig kompleksitet og reducerede risikoen for uautoriseret adgang med 85 %. Automatiseringen strømlinede udrulningen og reducerede opsætningstiden med 60 % sammenlignet med manuelle metoder. Den dokumenterede ramme gjorde det muligt for teams hurtigt at revidere eller ændre regler og sikrede skalerbarhed, efterhånden som infrastrukturen voksede.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Databaser", "DevOps", "Dokumentation", "Infrastruktur", "Linux & servere", "Sikkerhed", "Systemadministration", "Databaseadministration (DBA)", "Infrastructure as Code", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/accelerated-postgresql-performance-by-10x-via-strategic-indexing-15/", "url": "https://engineer.company/da/portfolio/accelerated-postgresql-performance-by-10x-via-strategic-indexing-15/", "title": "Har accelereret PostgreSQL‑ydeevnen 10 gange via strategisk indeksering, partitionering og query‑optimering og forbedret databaseeffektiviteten for bruger-, tenant-, geospatiale og tidsserie‑eldata.", "summary": "Accelererede PostgreSQL 10× via indeksering, partitionering og query-tuning for bruger-, tenant-, geospatiale og tidsseriedata — ressourceforbrug ned 40 %.", "content_html": "<p><strong>Situation.</strong> PostgreSQL‑databasen betjente en mellemstor applikation, der håndterede brugerkonti, tenant‑oplysninger, geografiske data (lokationer, regioner) og daglige elforbrugsmetrikker. Mens systemet kørte under lav belastning, opstod der ydeevneproblemer, efterhånden som datamængderne voksede. Forespørgsler på geospatiale data og tidsserie‑ellogs blev langsomme, og mangel på optimeret indeksering, fragmenterede forespørgsler og upartitionerede tabeller forværrede problemet.</p>\n<p><strong>Opgave.</strong> Målet var at forbedre databasens ydeevne 10 gange uden at ændre den eksisterende arkitektur. Fokus var på at optimere forespørgselsudførelse, reducere latens og sikre skalerbarhed til fremtidig datavækst, herunder bedre svartider for geospatiale og tidsbaserede forespørgsler og bevaret dataintegritet.</p>\n<p><strong>Handling.</strong> Indeksering: Ofte forespurgte kolonner blev analyseret og målrettede indeks oprettet, herunder GiST‑indeks til geospatiale data og composite‑indeks til flerkolonnefiltre. Partitionering: Tidsbaseret range‑partitionering af eltabellen (pr. dag/måned) og hash‑partitionering af geografiske data blev implementeret. Query‑optimering: Komplekse forespørgsler blev omskrevet for at undgå full table scans, CTE’er og materialized views brugt, ineffektive joins identificeret med EXPLAIN ANALYZE og query caching og connection pooling konfigureret.</p>\n<p><strong>Resultat.</strong> Efter optimeringen forbedredes svartiderne 10 gange, med kritiske operationer (brugerautentifikation, geospatiale opslag) på millisekunder. Databasens ressourceforbrug faldt med 40 %, så systemet kunne håndtere øgede datamængder uden ydeevnetab. Brugerne oplevede mere flydende interaktioner, systemet blev mere skalerbart, og ændringerne reducerede behovet for hardwareopgraderinger og sparede omkostninger.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Data engineering", "Databaser", "GIS / Geospatial", "Performanceoptimering", "PostgreSQL", "SQL", "Databaseadministration (DBA)", "Databaseoptimering" ] }, { "id": "https://engineer.company/da/portfolio/automated-gis-saas-application-deployment-data-processing-and-16/", "url": "https://engineer.company/da/portfolio/automated-gis-saas-application-deployment-data-processing-and-16/", "title": "Har automatiseret udrulning af GIS SaaS‑applikationer, databehandling og rapporteringssystem ved hjælp af GitHub Actions CI/CD, Python, Bash og SQL.", "summary": "Automatiserede GIS SaaS-udrulning, databehandling og rapportering med GitHub Actions CI/CD, Python, Bash og SQL — pålidelige releases.", "content_html": "<p><strong>Situation.</strong> Hos Utiligize var det at få GIS SaaS‑appen udrullet, behandle dens data og producere rapporterne alt sammen manuelle trin — og manuelle trin er både langsomme og stille farlige. Hver release åd engineering‑tid og bar chancen for en fejl, og det tilbagevendende data- og rapporteringsarbejde sad der og åd kapacitet uge efter uge.</p>\n<p><strong>Opgave.</strong> At automatisere hele vejen fra kode til produktion, plus det tilbagevendende databehandlings- og rapporteringsarbejde, var opgaven — målet var releases, der var hurtige, sikre og reproducerbare frem for et omhyggeligt manuelt ritual hver gang.</p>\n<p><strong>Handling.</strong> Hele vejen blev automatiseret. GitHub Actions‑pipelines overtog test‑build‑deploy‑cyklussen, så en release holdt op med at afhænge af, at nogen huskede trinene. Den tilbagevendende databehandling og rapporterne flyttede over i planlagte Python-, Bash- og SQL‑jobs, så de bare kørte i stedet for at være nogens pligt. Og konfigurationen og secrets blev standardiseret, så hvert miljø opførte sig ens — hvilket er det, der dræber “det virker på min maskine”-overraskelserne, for der holder op med at være en “min maskine”, der er anderledes end produktion.</p>\n<p><strong>Resultat.</strong> Udrulning, databehandling og rapportering blev alle automatiserede og pålidelige, det manuelle slid kom af teamets bord, og releasecyklussen blev kortere. Teamet kunne rette sin opmærksomhed mod produktet i stedet for driften, der var pakket omkring det.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Cloud", "Data engineering", "Datapipelines (ETL/ELT)", "DevOps", "Drift & backup", "GIS / Geospatial", "Python", "SQL", "DevOps & CI/CD‑automatisering", "GIS & geospatiale løsninger", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/automated-delivery-of-20-gis-data-pipelines-and-17/", "url": "https://engineer.company/da/portfolio/automated-delivery-of-20-gis-data-pipelines-and-17/", "title": "Har automatiseret levering af 20 GIS‑datapipelines og ETL‑processer for appdata og strømlinet infrastrukturautomatisering og rapportering.", "summary": "Automatiserede 20 GIS-datapipelines og ETL-processer for appdata — strømlinet infrastrukturautomatisering og pålidelige, aktuelle data.", "content_html": "<p><strong>Situation.</strong> Platformen kørte på en masse GIS‑datapipelines og ETL‑processer for appdata, og de blev leveret og overvåget i hånden. Håndkørte pipelines skaber flaskehalse, de driver ud af konsistens, og værst af alt bærer de en konstant lav risiko for, at en stille fejler, og ingen bemærker det, før dataene allerede er forkerte længere nede.</p>\n<p><strong>Opgave.</strong> At automatisere leveringen af de pipelines og ETL‑processer — så dataene flød pålideligt og forudsigeligt uden nogen til at føre dem igennem — var opgaven.</p>\n<p><strong>Handling.</strong> Tyve GIS‑datapipelines og ETL‑processerne for appdata kom under automatiseret levering, fra ende til anden. Planlægningen, loggingen og fejlhåndteringen blev standardiseret, så hver pipeline opførte sig ens og, afgørende, man kunne se, når en ikke gjorde — en stille fejl er kun stille, hvis intet holder øje. Og de blev foldet ind i den eksisterende infrastrukturautomatisering og rapportering, så de var en del af ét sammenhængende system frem for en skuffe fuld af scripts, nogen skulle huske at køre.</p>\n<p><strong>Resultat.</strong> Alle tyve pipelines og deres ETL kørte automatisk og forudsigeligt, og hele billedet af infrastrukturautomatisering og rapportering blev pænere for det. Forretningen fik pålidelige, aktuelle data, uden at nogen skulle gå dem igennem i hånden — og uden den stille‑fejl‑risiko hængende over sig.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Data engineering", "Datapipelines (ETL/ELT)", "DevOps", "Drift & backup", "GIS / Geospatial", "Monitorering & observability", "DevOps & CI/CD‑automatisering", "GIS & geospatiale løsninger", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/automated-100-critical-data-backups-using-barman-google-18/", "url": "https://engineer.company/da/portfolio/automated-100-critical-data-backups-using-barman-google-18/", "title": "Har automatiseret 100 kritiske databackups ved hjælp af Barman, Google Cloud, Bash og Python og sikret dataintegritet på tværs af databaser.", "summary": "Automatiserede 100 kritiske databackups med Barman, Google Cloud, Bash og Python — datagenoprettelse gik fra antagelse til testet faktum.", "content_html": "<p><strong>Situation.</strong> De kritiske GIS‑data, applikationsinstanserne og databaserne var spredt over systemer med backups, der var inkonsistente og delvist manuelle. For et produkt, der lever på sine data, er det ikke en risiko, man kan lade ligge — og den dag, man faktisk har brug for en backup, er præcis den værste dag at finde ud af, at den var ufuldstændig.</p>\n<p><strong>Opgave.</strong> Opgaven var at garantere, at alle de kritiske data kunne gendannes, hvilket betød at automatisere omfattende, verificerede backups på tværs af hele estatet — verificerede som det ord, der betyder noget.</p>\n<p><strong>Handling.</strong> Backup‑regimet blev bygget fra ende til anden. Hundrede kritiske databackups blev automatiseret, med Barman på PostgreSQL‑siden og Google Cloud, der holdt offsite‑kopierne, og det hele blev orkestreret og valideret med Bash og Python — for en backup, man har taget, men aldrig tjekket, er ikke rigtig en backup, det er et håb. Så der var retention‑politikker til at holde dem aktuelle og integritetstjek til at bekræfte, at hver enkelt faktisk var god, ikke bare til stede.</p>\n<p><strong>Resultat.</strong> Backups kørte automatisk og kunne verificeres på tværs af hver database, hvilket gjorde datagenoprettelighed fra en antagelse til noget testet. En væsentlig driftsrisiko kom af forretningen og blev erstattet af en genopretningsvej, man faktisk kunne stole på — forskellen var, at denne var tjekket, ikke bare konfigureret.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Cloud", "Databaser", "DevOps", "Drift & backup", "Infrastruktur", "PostgreSQL", "Python", "Backup & disaster recovery", "Databaseadministration (DBA)", "DevOps & CI/CD‑automatisering" ] }, { "id": "https://engineer.company/da/portfolio/deployed-and-maintained-20-docker-containerized-applications-troubleshooting-19/", "url": "https://engineer.company/da/portfolio/deployed-and-maintained-20-docker-containerized-applications-troubleshooting-19/", "title": "Har udrullet og vedligeholdt 20 Docker‑containeriserede applikationer, fejlfundet med Podman og Kubernetes og administreret R‑baserede apps på Google Cloud og AWS.", "summary": "Udrullede og vedligeholdt 20 Docker-containeriserede apps på Google Cloud og AWS — fejlfinding med Podman og Kubernetes, stabile releases.", "content_html": "<p><strong>Situation.</strong> Der var et voksende sæt containeriserede applikationer — herunder nogle R‑baserede analyseapps — der kørte på tværs af både Google Cloud og AWS. Spredt over to clouds og et par container‑runtimes skulle de udrulles konsistent og kunne diagnosticeres hurtigt, når noget gik galt, hvilket er sværere, end det lyder, når ikke to miljøer er helt ens.</p>\n<p><strong>Opgave.</strong> At udrulle og vedligeholde de workloads pålideligt, og at kunne diagnosticere problemer hurtigt på tværs af runtimes og clouds, var jobbet.</p>\n<p><strong>Handling.</strong> Det containeriserede estate blev administreret på tværs af begge clouds — tyve Docker‑applikationer udrullet og vedligeholdt med konsistent konfiguration og overvågning, så de ikke hver var deres eget snefnug. Når ting gik galt, gik fejlfindingen gennem Podman og orkestreringsdebugging gennem Kubernetes. De R‑baserede analyseapps fik dedikeret opmærksomhed i Google Cloud- og AWS‑produktionsmiljøerne, holdt stabile og reproducerbare, hvilket for analyse betyder noget — et resultat, man ikke kan reproducere, er ikke meget af et resultat.</p>\n<p><strong>Resultat.</strong> Det containeriserede estate kørte pålideligt på tværs af begge clouds, problemer blev diagnosticeret hurtigere, og udrulninger forblev stabile og reproducerbare. De applikationer, produktet lænede sig op ad, forblev pålidelige uanset hvilken cloud de nu tilfældigvis kørte i.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Cloud", "Containere (Docker/Kubernetes)", "Dataanalyse", "DevOps", "Drift & backup", "Infrastruktur", "Cloud‑infrastruktur & migrering", "Containerisering & orkestrering", "DevOps & CI/CD‑automatisering" ] }, { "id": "https://engineer.company/da/portfolio/managed-30-ubuntu-linux-vps-instances-implementing-disaster-20/", "url": "https://engineer.company/da/portfolio/managed-30-ubuntu-linux-vps-instances-implementing-disaster-20/", "title": "Har administreret 30 Ubuntu Linux VPS‑instanser, implementeret disaster recovery‑strategier og sikret optimale netværkskonfigurationer.", "summary": "Administrerede 30 Ubuntu Linux VPS-instanser med testede disaster recovery-strategier og solid netværksdrift — et robust vækstfundament.", "content_html": "<p><strong>Situation.</strong> Utiligize kørte på en flåde af Ubuntu Linux VPS‑instanser, hvis opsætning var vokset organisk over tid — hvilket er en pæn måde at sige, den var akkumuleret frem for designet. Det efterlod huller: inkonsistente konfigurationer og genopretnings- og netværksarrangementer, der var mere historisk tilfældighed end plan.</p>\n<p><strong>Opgave.</strong> Opgaven var at få flåden under ordentlig styring, styrke disaster recovery‑siden og gøre netværkskonfigurationen konsistent og fornuftig på tværs af hver instans.</p>\n<p><strong>Handling.</strong> De tredive instanser kom under bevidst styring — behandlet som én sammenhængende flåde frem for tredive individuelle kæledyr. Rigtig disaster recovery gik ind: backups, og gendannelsesprocedurer, der faktisk blev testet, for en utestet gendannelse er bare en teori. Og netværkskonfigurationen blev standardiseret for sikkerhed og ydeevne, så hver instans fulgte den samme hærdede baseline i stedet for, hvad den nu tilfældigvis var endt med.</p>\n<p><strong>Resultat.</strong> Infrastrukturen blev robust og konsistent, med genopretningsveje, der var testet, og netværksdrift, man kunne stole på. Nedetidsrisikoen faldt, og forretningen endte med et solidt fundament at vokse på frem for en lappeløsning, den skulle blive ved med at passe.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "DevOps", "Drift & backup", "Infrastruktur", "Linux & servere", "Netværk & VPN", "Sikkerhed", "Systemadministration", "Backup & disaster recovery", "Netværk & VPN‑opsætning" ] }, { "id": "https://engineer.company/da/portfolio/prevented-security-breaches-by-leading-access-management-initiatives-21/", "url": "https://engineer.company/da/portfolio/prevented-security-breaches-by-leading-access-management-initiatives-21/", "title": "Har forhindret sikkerhedsbrud ved at lede initiativer inden for adgangsstyring med M365, 1Password, Red Hat SSO og OKTA SSO.", "summary": "Ledede adgangsstyring med M365, 1Password, Red Hat SSO og OKTA — markant lavere risiko for uautoriseret adgang og sporbar adgang.", "content_html": "<p><strong>Situation.</strong> Adgang til systemer og tjenester på tværs af Utiligize blev håndteret inkonsistent — rettigheder givet ad hoc, over tid, af forskellige folk. Det er et dobbelt problem: det åbner døren for adgang, ingen havde tænkt sig, og det gør revision næsten umulig, for ingen kan faktisk sige, hvem der kan nå hvad, eller hvorfor de kan.</p>\n<p><strong>Opgave.</strong> Målet var at lukke sikkerhedseksponeringen ved at centralisere og stramme adgangsstyringen på tværs af hele organisationen.</p>\n<p><strong>Handling.</strong> Eftersynet konsoliderede identiteterne og adgangen på tværs af M365, 1Password, Red Hat SSO og OKTA SSO, så der var et sammenhængende billede i stedet for spredte rettigheder pr. system. Least privilege blev håndhævet — personer og systemer havde præcis, hvad de havde brug for, og intet i overskud — og on- og offboarding standardiseret, så adgang blev givet og, lige så vigtigt, tilbagekaldt hurtigt og konsistent frem for at hænge ved, efter nogen var gået videre.</p>\n<p><strong>Resultat.</strong> Risikoen for uautoriseret adgang faldt markant, og adgang blev sporbar og konsistent — man kunne endelig svare på “hvem kan nå det her, og hvorfor.” Det mærkelige er, at det også gjorde hverdagen enklere for teamet: de rigtige døre åbnede let, og de forkerte forblev lukkede, hvilket er, hvad god adgangsstyring faktisk føles som.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Cloud", "Infrastruktur", "Sikkerhed", "Systemadministration", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/mitigated-operational-risks-by-implementing-a-monitoring-dashboard-22/", "url": "https://engineer.company/da/portfolio/mitigated-operational-risks-by-implementing-a-monitoring-dashboard-22/", "title": "Har reduceret driftsrisici ved at implementere et monitoreringsdashboard med Grafana og Prometheus og forbedret systemets pålidelighed.", "summary": "Byggede et Grafana + Prometheus-monitoreringsdashboard, der fanger problemer, før de eskalerer — fra brandslukning til forebyggelse.", "content_html": "<p><strong>Situation.</strong> Problemer hos Utiligize blev som regel først bemærket, efter de allerede havde ramt brugerne, fordi der ikke fandtes ét samlet overblik over, hvordan systemerne havde det. Uden det indblik var teamet permanent på bagfod — reagerede på ting, der allerede var gået galt, i stedet for at se dem komme.</p>\n<p><strong>Opgave.</strong> Målet var at skære driftsrisikoen ned ved at give teamet realtidsindblik i de systemer, de var afhængige af.</p>\n<p><strong>Handling.</strong> Observability‑laget blev bygget ud. Et monitoreringsdashboard på Grafana og Prometheus, de centrale tjenester instrumenteret, og — den del, der faktisk betyder noget — metrikker, der betød noget, frem for forfængelighedstal, der ser travle ud og fortæller en ingenting. Så alarmtærskler sat på dem, synliggjort der, hvor teamet faktisk ville se dem og kunne handle, mens der stadig var tid at handle i.</p>\n<p><strong>Resultat.</strong> Problemer begyndte at blive fanget og håndteret, før de eskalerede, og systemets pålidelighed blev bedre for det. Teamet skiftede fra reaktiv brandslukning til noget roligere og mere proaktivt — fangede problemer, mens de stadig var små nok til at være kedelige.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Drift & backup", "Infrastruktur", "Monitorering & observability", "DevOps & CI/CD‑automatisering", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/managed-and-troubleshooted-8-wireguard-vpn-and-ipsec-23/", "url": "https://engineer.company/da/portfolio/managed-and-troubleshooted-8-wireguard-vpn-and-ipsec-23/", "title": "Har administreret og fejlfundet 8 WireGuard VPN- og IPSEC VPN‑forbindelser og sikret sikker kommunikation på tværs af Google Cloud og Linux‑systemer.", "summary": "Administrerede og fejlfandt 8 WireGuard- og IPSEC VPN-forbindelser på Google Cloud og Linux — stoppede tilbagevendende forbindelsesudfald.", "content_html": "<p><strong>Situation.</strong> Sikker forbindelse mellem cloud’en og on‑premises‑Linux‑systemerne kørte over flere VPN‑tunneler, og de var skrøbelige og bøvlede at diagnosticere, når de faldt. En død tunnel kunne kappe kommunikationen mellem miljøer, og at fejlfinde en var langsomt og usikkert — man var aldrig helt sikker på, om man havde fundet den rigtige årsag.</p>\n<p><strong>Opgave.</strong> At administrere og fejlfinde de forbindelser, for at garantere sikker og uafbrudt kommunikation, var jobbet.</p>\n<p><strong>Handling.</strong> VPN‑estatet kom under kontrol — otte WireGuard- og IPSec‑tunneler på tværs af Google Cloud og Linux‑systemerne, administreret og fejlfundet som et sæt frem for otte separate mysterier. Deres konfiguration blev standardiseret, så de var konsistente og forståelige i stedet for hver at være sit eget særtilfælde, og deres tilstand blev overvåget, med de tilbagevendende routing- og key‑exchange‑problemer tacklet ved roden frem for lappet over med en genstart.</p>\n<p><strong>Resultat.</strong> Alle otte tunneler kørte sikkert og pålideligt, kommunikationen mellem miljøer forblev beskyttet, og de tilbagevendende forbindelsesproblemer, der plejede at afbryde arbejdet, holdt op med at ske. At rette rodårsagerne frem for at pleje symptomerne er det, der gjorde dem fra en tilbagevendende hovedpine til noget, der bare virkede.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Cloud", "Drift & backup", "Infrastruktur", "Linux & servere", "Netværk & VPN", "Sikkerhed", "Systemadministration", "Netværk & VPN‑opsætning", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/led-the-software-development-of-a-gis-map-24/", "url": "https://engineer.company/da/portfolio/led-the-software-development-of-a-gis-map-24/", "title": "Har ledet softwareudviklingen af en GIS‑kortapplikation, øget omsætningen 10 gange og positioneret produktet som et primært dataaktiv.", "summary": "Ledede udviklingen af en GIS-kortapplikation, der blev en platformshjørnesten og drev en 10× omsætningsstigning via mersalg og datalicensering.", "content_html": "<p><strong>Situation.</strong> Kerneudfordringen var at bygge en platform, der kunne spore, overvåge og optimere vedvarende energiaktiver som solpaneler og vindmøller. Den oprindelige løsning manglede dog robuste geospatiale funktioner, hvilket gjorde det svært for kunder at visualisere aktivlokationer, analysere spatiale data eller udlede handlingsorienterede indsigter. Ledelsen prioriterede derfor udviklingen af en GIS‑kortapplikation for at øge platformens værdi.</p>\n<p><strong>Opgave.</strong> Opgaven var at lede udviklingen af GIS‑applikationen, en kritisk komponent til at differentiere produktet på det konkurrenceprægede marked for grøn energi. Rollen strakte sig ud over softwareudvikling — som teknisk leder, DevOps‑ingeniør, dataingeniør og SRE. Målet var at skabe et skalerbart, intuitivt GIS‑værktøj, der integrerede problemfrit med SaaS‑platformen, og som kunne udvikle sig i takt med virksomheden.</p>\n<p><strong>Handling.</strong> Det begyndte med at samarbejde med interessenter om at definere applikationens kernefunktioner med fokus på integration med den eksisterende SaaS‑platform og realtidsvisualisering. På grund af det lille team blev en modulær arkitektur designet med open source‑GIS‑biblioteker for at holde systemet let og skalerbart. CI/CD‑pipelines, automatiseret infrastrukturprovisionering og overvågning blev også implementeret. Efterhånden som teamet voksede, blev nye ingeniører mentoreret, tværfagligt samarbejde faciliteret og brugerfeedback prioriteret for at forfine applikationen iterativt.</p>\n<p><strong>Resultat.</strong> GIS‑applikationen blev en hjørnesten i SaaS‑platformen og drev en 10‑dobbelt stigning i omsætningen gennem mersalg, datalicensering og nye kunder. Dens evne til at visualisere grønne energiaktiver i realtid forbedrede kundernes driftseffektivitet, og den løbende forfining positionerede den som et primært dataaktiv. Projektets succes styrkede virksomhedens omdømme i sektoren for vedvarende energi og demonstrerede værdien af en tværfaglig tilgang i et hurtigt voksende startup‑miljø.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "DevOps", "Drift & backup", "Full stack‑udvikling", "GIS / Geospatial", "Løsningsarkitektur", "Mentoring & coaching", "Platformarkitektur", "Produkt & krav", "Teamledelse", "Teknisk ledelse", "Full stack‑produktudvikling", "GIS & geospatiale løsninger", "Platform- & løsningsarkitektur", "Produktstrategi & kravspecifikation", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/directed-full-stack-gis-map-development-overseeing-postgresql-25/", "url": "https://engineer.company/da/portfolio/directed-full-stack-gis-map-development-overseeing-postgresql-25/", "title": "Har styret full‑stack‑udvikling af GIS‑kort med ansvar for PostgreSQL, Mapbox, ReactJS og NodeJS for at levere en integreret løsning.", "summary": "Styrede full-stack GIS-kortudvikling (PostgreSQL, Mapbox, React, Node.js) — én integreret, udvidbar kerne i platformen.", "content_html": "<p><strong>Situation.</strong> På det her tidspunkt var GIS‑kortet holdt op med at være en funktion og var blevet grunden til, at kunderne overhovedet loggede ind. Problemet var, at det var vokset op i stumper. De spatiale data lå i PostgreSQL, selve kortet blev tegnet med Mapbox, og applikationen omkring det var ReactJS i frontenden med NodeJS bagved. Hver del virkede for sig. De var bare ikke bygget til at passe sammen, og sømmene begyndte at vise sig.</p>\n<p><strong>Opgave.</strong> Rollen dækkede full‑stack‑udviklingen af kortet og den tekniske retning, der fulgte med: datamodellen, renderingen, API’et og React‑frontenden. Målet var at gøre fire ting, der tilfældigvis delte et repository, til ét produkt, der var værd at stå inde for.</p>\n<p><strong>Handling.</strong> Arkitekturen blev fastlagt først, og så blev arbejdet tæt på koden i stedet for at styre på afstand. På datasiden blev den spatiale model i PostgreSQL holdt ryddelig, så forespørgslerne ikke sneglede sig af sted, efterhånden som datasættene voksede. Mapbox stod for tegningen; opgaven var at fodre den med de rigtige data på de rigtige zoomniveauer i stedet for det hele på én gang. På applikationssiden blev ReactJS- og NodeJS‑arbejdet reviewet, teamet skubbet mod fælles konventioner, og ansvar flyttet tilbage til det lag, det hørte til i, når ét begyndte at lække ind i det næste. En god del af det var uglamourøst arbejde: at fange de små inkonsistenser, før de størknede til arkitektur.</p>\n<p><strong>Resultat.</strong> Det, der kom ud, var én integreret kortapplikation, hvor data, rendering og grænseflade endelig trak samme vej. Den blev en central del af platformen og noget, teamet kunne blive ved med at bygge videre på, uden at den knækkede sammen, hver gang et lag blev tilføjet.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "API'er & integration", "Backend‑udvikling", "Databaser", "Frontend‑udvikling", "Full stack‑udvikling", "GIS / Geospatial", "Performanceoptimering", "Platformarkitektur", "PostgreSQL", "Teknisk ledelse", "Backend- & API‑udvikling", "Full stack‑produktudvikling", "GIS & geospatiale løsninger", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/saved-4-000-hours-by-mentoring-and-growing-26/", "url": "https://engineer.company/da/portfolio/saved-4-000-hours-by-mentoring-and-growing-26/", "title": "Har sparet 4.000 timer ved at mentorere og udvide et team fra 2 til 18 medlemmer, optimere workflows og fremme tværfagligt samarbejde.", "summary": "Sparede ~4.000 timer ved at udvide og mentorere et team fra 2 til 18, redesigne workflows og opbygge varig engineering-kapacitet.", "content_html": "<p><strong>Situation.</strong> Et team på to kunne ikke følge med længere. Produktet trak mere arbejde ind, end et par mennesker kunne levere, og måden, arbejdet foregik på — viden i hovederne, ingen rigtige konventioner — ville ikke overleve at blive skaleret op. At kaste flere folk efter et så løst team gør som regel bare kaosset større.</p>\n<p><strong>Opgave.</strong> Opgaven var at udvide teamet og samtidig bygge den struktur, der ville lade en større gruppe bevæge sig hurtigere i stedet for langsommere. At mentorere de nye var den ene halvdel. At rette arbejdsgangene, så ingen gik i stå og ventede på en anden, var den anden.</p>\n<p><strong>Handling.</strong> Teamet voksede fra 2 til 18 over tid, med ansættelsen og mentoreringen behandlet som det samme job: alle, der kom til, skulle kunne arbejde uden opsyn. Fælles standarder betød, at kode og proces så ens ud uanset hvem der skrev dem, og der blev lagt reelt arbejde i overleveringerne mellem specialer, for det er dér, teams stille og roligt taber deres dage. Når noget blev ved med at spænde ben, gik rettelsen til processen i stedet for symptomet.</p>\n<p><strong>Resultat.</strong> Det større, bedre mentorerede team, der kørte på arbejdsgange, der faktisk var designet, sparede i omegnen af 4.000 timer. Men tallet er ikke rigtig pointen. Det, der blev bygget, var varig engineering‑kapacitet — en gruppe, der kunne bære arbejdet, uanset om nogen enkelt person var i rummet eller ej.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Agile & Scrum", "Mentoring & coaching", "Projektledelse", "Teamledelse", "Teknisk ledelse", "Projektledelse (Agile)", "Teamopbygning & mentoring", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/optimized-forecasting-and-investment-strategies-for-11-electricity-27/", "url": "https://engineer.company/da/portfolio/optimized-forecasting-and-investment-strategies-for-11-electricity-27/", "title": "Har optimeret prognose- og investeringsstrategier for 11 eldistributionsoperatører og øget driftseffektiviteten gennem datadrevne GIS‑løsninger.", "summary": "Optimerede prognoser og investeringsstrategi for 11 eldistributionsoperatører med datadrevet GIS — troværdig, målrettet planlægning.", "content_html": "<p><strong>Situation.</strong> Eldistributionsoperatører står og falder på beslutninger om, hvor de skal forstærke nettet, og hvor de skal placere deres penge, og elleve af dem traf de beslutninger uden meget geospatial analyse under sig. De havde driftsdataene. Det, de ikke havde, var en måde at se dem på kortet, dér hvor mønstrene faktisk lever.</p>\n<p><strong>Opgave.</strong> Opgaven var at skærpe deres prognoser og investeringsstrategier med GIS — at gøre tabeller af aflæsninger til noget, der viste dem, hvor kapaciteten blev knap, hvor risikoen byggede sig op, og hvor den næste krone var bedst givet ud.</p>\n<p><strong>Handling.</strong> Deres driftsdata blev bragt sammen med geospatial modellering, så de to forstærkede hinanden. I stedet for at lave prognoser i det abstrakte kunne nettet ses rumligt og stilles konkrete spørgsmål: hvilke strækninger var på vej mod deres grænser, hvilke områder retfærdiggjorde investering først. For elleve operatører betød det at tilpasse analysen til, hvordan hver af dem faktisk drev deres net, ikke at række alle den samme skabelon og håbe, den passede.</p>\n<p><strong>Resultat.</strong> Operatørerne gik derfra med prognoser, de kunne stole på, og investeringsbeslutninger, der var målrettede frem for håbefulde. At forankre planlægningen i det, kortet viste, gjorde det hele mere effektivt — penge og opmærksomhed gik derhen, hvor dataene pegede, i stedet for derhen, hvor vanen gjorde.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Data engineering", "Dataanalyse", "GIS / Geospatial", "Produkt & krav", "Stakeholder & rapportering", "Dataanalyse & BI‑dashboards", "GIS & geospatiale løsninger", "Produktstrategi & kravspecifikation" ] }, { "id": "https://engineer.company/da/portfolio/presented-200-ui-ux-improvements-for-the-gis-28/", "url": "https://engineer.company/da/portfolio/presented-200-ui-ux-improvements-for-the-gis-28/", "title": "Har præsenteret 200 UI/UX‑forbedringer til GIS‑kortapplikationen og øget omsætningen 10 gange gennem forbedrede softwarefunktioner.", "summary": "Leverede 200 UI/UX-forbedringer til en GIS-kortapplikation og bidrog til en 10× omsætningsstigning — omhyggelig UX som kommerciel driver.", "content_html": "<p><strong>Situation.</strong> Kortgrænsefladen var blevet kraftfuld og undervejs kompliceret. Der var steder, hvor man kunne mærke kunderne ikke få den værdi, der sad lige foran dem — god funktionalitet fanget bag klodsede interaktioner. Det gab mellem, hvad produktet kunne, og hvad folk fandt let at gøre, kostede os stille og roligt.</p>\n<p><strong>Opgave.</strong> Opgaven var at finde de gab og skubbe rettelserne igennem: det brugervenlighedsarbejde, der ville gøre produktet lettere at få værdi ud af og — ikke tilfældigt — mere værd kommercielt.</p>\n<p><strong>Handling.</strong> I stedet for at gætte på, hvad der var galt, gik arbejdet i feedbacken og brugsdataene, og ud af det kom en backlog på 200 konkrete UI/UX‑forbedringer. De blev ikke behandlet som ens — rangeret efter effekt, med dem, der betød noget, argumenteret for og ført gennem teamet for at blive sendt ud som rigtige funktioner i stedet for en ønskeliste, der lå og blev forældet i et dokument.</p>\n<p><strong>Resultat.</strong> Grænsefladen blev mærkbart bedre at bruge, og produktets værdi fulgte efter: arbejdet bidrog til en tidobling af omsætningen. Det er et eksempel, der er værd at vende tilbage til, fordi det gør pointen rent — omhyggeligt UX‑arbejde er ikke kosmetik, det dukker op på fakturaen.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Dataanalyse", "Designsystemer & UI", "Frontend‑udvikling", "Produkt & krav", "Stakeholder & rapportering", "UX/UI‑design", "Produktstrategi & kravspecifikation", "UI/UX‑design & designsystemer" ] }, { "id": "https://engineer.company/da/portfolio/designed-and-managed-5-000-hours-of-map-29/", "url": "https://engineer.company/da/portfolio/designed-and-managed-5-000-hours-of-map-29/", "title": "Har designet og styret 5.000 timers kortudvikling ved hjælp af agile metoder som Scrum og Jira for effektiv projektledelse.", "summary": "Planlagde og styrede 5.000 timers kortudvikling med Scrum og Jira — synligt arbejde, afstemt med roadmappen.", "content_html": "<p><strong>Situation.</strong> Kortet blev ikke bygget i en sprint. Det var tusindvis af timers arbejde spredt over en masse mennesker og en masse måneder, og den slags indsats skrider, hvis ingen holder linjen på scope og tidsplan. Overladt til sig selv bliver den stille og roligt forsinket.</p>\n<p><strong>Opgave.</strong> Ansvaret var at designe og køre udviklingsindsatsen, så den blev leveret med vilje frem for ved held — at holde prioriteterne ærlige og fremdriften synlig for enhver, der ville kigge.</p>\n<p><strong>Handling.</strong> Det kørte på agile: ordentlig Scrum, med ceremonierne faktisk brugt frem for opført, og arbejdet sporet i Jira, så folk kunne se, hvor tingene stod, uden at skulle spørge. Et sted omkring 5.000 timers kortudvikling gik gennem den proces. Vægten lå mindre på ceremoni for dens egen skyld og mere på at holde prioriteterne rettet mod det, der betød noget, og fange skred tidligt, mens det stadig var billigt at rette.</p>\n<p><strong>Resultat.</strong> De 5.000 timer landede på en kontrolleret, synlig måde i stedet for at forsvinde ind i en sort boks. Projektledelsen gjorde det, den skal — og det, der mest går ubemærket hen, når den virker: den holdt arbejdet afstemt med målene og nogenlunde på tidsplanen, uden heltemod til sidst.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Agile & Scrum", "Produkt & krav", "Projektledelse", "Stakeholder & rapportering", "Teamledelse", "Produktstrategi & kravspecifikation", "Projektledelse (Agile)", "Teamopbygning & mentoring" ] }, { "id": "https://engineer.company/da/portfolio/wrote-50-000-words-of-comprehensive-software-documentation-30/", "url": "https://engineer.company/da/portfolio/wrote-50-000-words-of-comprehensive-software-documentation-30/", "title": "Har skrevet 50.000 ord omfattende softwaredokumentation med Markdown i GitHub, Craft og Confluence og derved sikret fastholdelse af viden og gennemsigtighed i processen.", "summary": "Skrev 50.000 ord softwaredokumentation i GitHub, Craft og Confluence — varig, gennemsigtig og reviderbar viden.", "content_html": "<p><strong>Situation.</strong> Det meste af det, der betød noget om produktet, og om hvordan teamet arbejdede, lå i folks hoveder. Det går fint lige indtil nogen ny kommer til, eller nogen forlader firmaet — og så sneglede onboardingen sig af sted, og en klump institutionel hukommelse var én opsigelse fra at være væk for altid.</p>\n<p><strong>Opgave.</strong> Målet var at få den viden ud af hovederne og ind i dokumentation, folk faktisk ville beholde og bruge: klar nok til at blive læst, struktureret nok til at blive vedligeholdt frem for droppet efter en måned.</p>\n<p><strong>Handling.</strong> En stor del af den blev skrevet i hånden, omkring 50.000 ord til sidst, i Markdown på tværs af GitHub, Craft og Confluence alt efter hvor hvert stykke hørte til. Den dækkede arkitekturen, processerne og det praktiske how‑to‑materiale — de spørgsmål, folk blev ved med at stille. Den blev holdt versionsstyret og, mindst lige så vigtigt, mulig at finde, for dokumentation, ingen kan lokalisere, kan lige så godt ikke eksistere.</p>\n<p><strong>Resultat.</strong> Videnen holdt op med at være skrøbelig. Nye folk kom hurtigere op i tempo, processen blev noget, man kunne pege på i stedet for at rekonstruere efter hukommelsen, og det hele blev reviderbart: man kunne se hvordan og hvorfor arbejdet blev gjort frem for at tage det på tro.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Dokumentation", "Mentoring & coaching", "Teamledelse", "Teknisk ledelse", "Teknisk dokumentation", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/gathered-and-analyzed-business-requirements-to-translate-into-31/", "url": "https://engineer.company/da/portfolio/gathered-and-analyzed-business-requirements-to-translate-into-31/", "title": "Har indsamlet og analyseret forretningskrav og omsat dem til konkrete funktioner og user stories i overensstemmelse med data governance‑standarder.", "summary": "Omsatte forretningskrav til klare funktioner og user stories efter data governance-standarder — mindre tvetydighed og omarbejde.", "content_html": "<p><strong>Situation.</strong> Krav plejede at ankomme som samtaler — nogen ville have noget, sådan cirka, og det faldt tilbage på udviklerne at gætte på kanterne. At gætte betyder omarbejde, og omarbejde er omtrent den dyreste måde, der findes, at bygge noget på.</p>\n<p><strong>Opgave.</strong> Rollen sad mellem forretningen og engineeringen og oversatte den ene til den anden: at gøre løse behov til arbejde, en udvikler kunne tage fat på uden at gætte, og at holde det i tråd med data governance‑standarderne undervejs.</p>\n<p><strong>Handling.</strong> Kravene blev arbejdet ud med interessenterne direkte, med de akavede spørgsmål stillet tidligt i stedet for opdaget sent, og skrevet op som funktioner og user stories, der faktisk sagde, hvad “færdig” betød. Hver enkelt blev holdt op mod governance‑reglerne, for en funktion, der er nyttig, men håndterer data forkert, er ikke rigtig færdig. Hele formålet var, at nogen kunne læse en story og bygge det rigtige første gang.</p>\n<p><strong>Resultat.</strong> Udviklingen kørte ud fra klare, aftalte funktioner i stedet for halvt forståede ønsker. Tvetydigheden faldt, og omarbejdet faldt med den, og leveringen forblev rettet mod de faktiske forretningsmål — inden for data governance‑linjerne frem for pyntet til at passe bagefter.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Agile & Scrum", "Data governance", "Dokumentation", "Produkt & krav", "Projektledelse", "Stakeholder & rapportering", "Data governance & datakvalitet", "Produktstrategi & kravspecifikation", "Projektledelse (Agile)" ] }, { "id": "https://engineer.company/da/portfolio/managed-the-execution-of-30-successful-gis-projects-32/", "url": "https://engineer.company/da/portfolio/managed-the-execution-of-30-successful-gis-projects-32/", "title": "Har styret gennemførelsen af 30 succesfulde GIS‑projekter og udvist lederskab i leveringen af innovative løsninger på tværs af feltet.", "summary": "Leverede 30 succesfulde GIS-projekter — en historik af konsistent, innovativ levering, der skabte varig kundetillid.", "content_html": "<p><strong>Situation.</strong> Mappitall tjente sine penge på at levere GIS‑projekter, og der kørte mange af dem på én gang. Virksomhedens vækst red på at få dem ud ad døren pålideligt — ikke ét flagskibsprojekt gjort strålende, men en stabil strøm af dem, der landede til tiden og hang sammen, hvilket kun sker, når koordineringen på tværs af teamet faktisk fungerer.</p>\n<p><strong>Opgave.</strong> Ansvaret var at få den portefølje leveret — at holde scope, folk og tidsplaner på linje på tværs af det hele og give teamet den retning, der skulle til for at levere.</p>\n<p><strong>Handling.</strong> Gennem de år kørte leveringen af tredive GIS‑projekter gennem den her rolle. I praksis betød det at holde scope i ro, når det ville krybe, at pege de rigtige folk mod det rigtige arbejde og at være tæt nok på hvert projekt til at fange problemer, mens de stadig var små. Når noget var ved at skride, bedre at vide det tidligt og rokere om end finde ud af det ved deadline. En stor del af jobbet var bare at holde tallerknerne i luften og kundens forventninger ærlige om, hvad der kom hvornår.</p>\n<p><strong>Resultat.</strong> Alle tredive kom i mål. Den konsistens betød mere, end noget enkelt projekt ville have gjort — en kunde, der har set dig levere tredive gange, bekymrer sig ikke om den enogtredivte, og det omdømme er en god del af, hvorfor virksomheden blev ved med at vokse.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Agile & Scrum", "GIS / Geospatial", "Projektledelse", "Stakeholder & rapportering", "Teamledelse", "GIS & geospatiale løsninger", "Projektledelse (Agile)", "Teamopbygning & mentoring" ] }, { "id": "https://engineer.company/da/portfolio/led-company-growth-from-4-to-14-employees-33/", "url": "https://engineer.company/da/portfolio/led-company-growth-from-4-to-14-employees-33/", "title": "Har ledet virksomhedens vækst fra 4 til 14 medarbejdere ved at anvende agile metoder og effektiv projektledelse.", "summary": "Ledede virksomhedens vækst fra 4 til 14 medarbejdere med agile metoder — skalerede teamet uden at miste kvalitet eller sammenhold.", "content_html": "<p><strong>Situation.</strong> Mappitall var klar til at vokse, og der er en bestemt fare i det øjeblik: man tilføjer folk hurtigere, end man tilføjer proces, og både kvaliteten og den fælles fornemmelse af, hvordan tingene gøres, begynder at flosse. Fire personer, der alle ved, hvad alle andre laver, er noget helt andet end fjorten, der ikke gør.</p>\n<p><strong>Opgave.</strong> At lede den vækst var jobbet — at få folk ind og samtidig holde leveringen disciplineret og teamet trækkende i samme retning.</p>\n<p><strong>Handling.</strong> Virksomheden gik fra fire personer til fjorten. Det var ansættelse og onboarding gjort bevidst frem for i panik, men den større brik var at få de arbejdsmåder på plads, der lod et team af den størrelse ikke snuble over sig selv — agile praksisser, rigtig projektledelse, de vaner, der holder alles arbejde synligt for alle andre. Den tiende og fjortende ansættelse skulle træde ind i noget, der allerede havde en form, ikke gætte sig til, hvordan tingene blev gjort.</p>\n<p><strong>Resultat.</strong> Teamet nåede fjorten, uden at kvaliteten faldt, eller det splintrede i folk, der ikke vidste, hvad de andre var i gang med. Det agile er det, der holdt en større gruppe produktiv — processen voksede med antallet af hoveder i stedet for at halte bagefter.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Agile & Scrum", "Mentoring & coaching", "Projektledelse", "Teamledelse", "Teknisk ledelse", "Projektledelse (Agile)", "Teamopbygning & mentoring", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/improved-team-communication-and-collaboration-by-implementing-slack-34/", "url": "https://engineer.company/da/portfolio/improved-team-communication-and-collaboration-by-implementing-slack-34/", "title": "Har forbedret teamets kommunikation og samarbejde ved at implementere Slack, Mattermost, 1Password og Jira og sparet 8.000 arbejdstimer.", "summary": "Sparede ~8.000 timer ved at indføre Slack, Mattermost, 1Password og Jira — mindre jagt på information, mere faktisk levering.", "content_html": "<p><strong>Situation.</strong> Efterhånden som teamet blev større, havde kommunikationen og værktøjerne ikke fulgt med, og det kunne mærkes. Ting blev sagt ét sted og overset af dem, der havde brug for dem, arbejde blev lavet dobbelt, fordi ingen kunne se, hvad en anden allerede havde gjort, og at koordinere noget som helst tog længere tid end selve arbejdet. Den slags friktion er usynlig fra dag til dag, men den løber op i en masse tabt tid.</p>\n<p><strong>Opgave.</strong> Målet var at rette op på, hvordan teamet kommunikerede og arbejdede sammen, og kradse den tid tilbage, der stille og roligt blev blødt til al den friktion.</p>\n<p><strong>Handling.</strong> Værktøjerne blev indført og standardiseret, og — det er den del, der faktisk betyder noget — praksis for at bruge dem blev sat, så de ikke bare blev endnu et sted at tjekke. Slack og Mattermost til kommunikation, 1Password, så delte secrets ikke blev sendt rundt på måder, ingen kunne holde styr på, Jira, så arbejdet blev tracket ét sted i stedet for at leve i folks hoveder og indbakker. Værktøjerne var den nemme del; at få alle til faktisk at bruge dem på samme måde var det egentlige arbejde.</p>\n<p><strong>Resultat.</strong> Kommunikation og samarbejde blev mærkbart bedre, og det strømlinede opsæt sparede noget i størrelsesordenen 8.000 arbejdstimer — tid, der havde gået til at jage information og lave arbejde om, som nu gik til faktisk levering.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Agile & Scrum", "Automatisering & CI/CD", "Dokumentation", "Projektledelse", "Sikkerhed", "Teamledelse", "Projektledelse (Agile)", "Sikkerhed & adgangsstyring", "Teamopbygning & mentoring" ] }, { "id": "https://engineer.company/da/portfolio/led-the-development-deployment-and-support-of-over-35/", "url": "https://engineer.company/da/portfolio/led-the-development-deployment-and-support-of-over-35/", "title": "Har ledet udvikling, idriftsættelse og support af over 30 GIS‑projekter og udvist ekspertise i PostgreSQL, Bash, Python, JavaScript, GDAL, ArcGIS, PostGIS og Mapbox.", "summary": "Ledede udvikling, drift og support af 30+ GIS-projekter med PostgreSQL, Python, GDAL, ArcGIS, PostGIS og Mapbox — hele livscyklussen.", "content_html": "<p><strong>Situation.</strong> Virksomhedens hele output var made‑to‑measure GIS — skræddersyet kortlægning og geodata‑systemer bygget til en kundes konkrete problem og så holdt kørende, når de var live. At bygge tingen er kun halvdelen; et geospatialt projekt, der ryger ud og så vælter i produktion, er ikke rigtig blevet leveret.</p>\n<p><strong>Opgave.</strong> De projekter kørte fra ende til anden gennem den her rolle — udviklingen, udrulningen og supporten, når de først var live — med den tekniske retning på tværs af en ret bred stak.</p>\n<p><strong>Handling.</strong> Leveringen kørte på mere end tredive GIS‑projekter, hands‑on på tværs af stakken hele vejen. PostgreSQL med PostGIS under til geodataene, GDAL/OGR til at flytte dem mellem formater — shapefiles, GeoJSON, GeoTIFF, KML, vector tiles — og QGIS og JOSM til selve dataarbejdet. Foran på det webkort bygget på Mapbox GL og Leaflet, nogle gange mod ArcGIS- eller HERE‑API’erne, med app‑laget i JavaScript, Python, PHP og SQL. Arbejdet spændte fra 2D- og 3D‑digital kortlægning over LiDAR‑behandling, georektifikation og vektorisering til indendørs kortlægning og navigation. Og ansvaret rakte forbi det punkt, tingen var sendt af sted — udrulningen og den løbende support i produktion var også en del af det, så problemer blev ikke givet videre; beslutningerne skulle leves med.</p>\n<p><strong>Resultat.</strong> Tredive‑plus projekter bygget, udrullet og supporteret på tværs af hele det spænd. At stå på krogen for hele livscyklussen frem for bare bygget er det, der holdt kvaliteten ærlig — man designer anderledes, når man ved, det er en selv, der får opkaldet, hvis det går i stykker.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Data engineering", "Databaser", "Drift & backup", "Full stack‑udvikling", "GIS / Geospatial", "PostgreSQL", "Python", "SQL", "Teamledelse", "Teknisk ledelse", "Webudvikling", "Backend- & API‑udvikling", "Full stack‑produktudvikling", "GIS & geospatiale løsninger", "Teknisk ledelse & rådgivning", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/contributed-to-user-interface-design-processes-ensuring-intuitive-36/", "url": "https://engineer.company/da/portfolio/contributed-to-user-interface-design-processes-ensuring-intuitive-36/", "title": "Har bidraget til designprocesser for brugergrænseflader og sikret intuitive, visuelt tiltalende og brugervenlige projektgrænseflader.", "summary": "Bidrog til UI-design med intuitive, forfinede og brugervenlige grænseflader — brugervenlighed stillet under design, ikke efter.", "content_html": "<p><strong>Situation.</strong> Grænsefladerne på tværs af projekterne var ujævne. Nogle var fine, nogle var tydeligvis bygget af ingeniører, der tænkte på datamodellen frem for den person, der skulle bruge dem, og designbeslutningerne blev ikke altid truffet med slutbrugeren i rummet. På et webkort‑produkt især er kortet den nemme del — det er kontrollerne, filtreringen og flowet rundt om det, hvor folk farer vild.</p>\n<p><strong>Opgave.</strong> At blande sig i UI‑designprocessen var en del af rollen — for at hjælpe med at gøre grænsefladerne til noget, folk faktisk fandt intuitivt og behageligt at bruge, ikke bare funktionelt.</p>\n<p><strong>Handling.</strong> Rollen var ikke designerens, men den sad med i designprocessen og bragte et engineering‑perspektiv ind — pressede på layout, på flowet gennem en opgave, på om en skærm faktisk var klar eller bare var velkendt for dem, der havde bygget den. Det her var React-, Angular- og Vue‑frontends oven på Mapbox- og Leaflet‑kort, og en stor del af brugervenligheden lå i detaljerne: hvordan man filtrerede et datasæt, hvordan man skiftede mellem etager på et indendørskort, om tingen fortalte dig, hvad den lavede. Mest af alt betød det at stille de dumme brugerspørgsmål tidligt, mens de stadig var billige at rette, i stedet for efter release, når forvirringen kom tilbage som supportsager.</p>\n<p><strong>Resultat.</strong> Grænsefladerne blev mere intuitive og mere forfinede, hvilket slutbrugerne mærkede direkte, og det løftede den samlede kvalitet af det, der blev sendt ud. At få brugervenlighedsspørgsmålene stillet under design frem for efter release er det meste af, hvad der gjorde forskellen.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Designsystemer & UI", "Frontend‑udvikling", "GIS / Geospatial", "UX/UI‑design", "Webudvikling", "UI/UX‑design & designsystemer" ] }, { "id": "https://engineer.company/da/portfolio/guided-the-execution-of-numerous-company-projects-offering-37/", "url": "https://engineer.company/da/portfolio/guided-the-execution-of-numerous-company-projects-offering-37/", "title": "Har vejledt gennemførelsen af talrige virksomhedsprojekter og ydet ekspertsupport i softwareudviklingsfaserne.", "summary": "Vejledte gennemførelsen af mange virksomhedsprojekter med ekspertsupport i de sværeste udviklingsfaser — stabil levering.", "content_html": "<p><strong>Situation.</strong> Der kørte mange projekter på samme tid, hvert midt i sin udviklingsfase, og de havde alle brug for stabil teknisk vejledning for at blive på sporet. Ladt alene driver projekter — en forkert tilgang taget tidligt bliver dyr, når først nogen bemærker det.</p>\n<p><strong>Opgave.</strong> At vejlede den gennemførelse var jobbet — at være den tekniske support, teamene kunne læne sig op ad gennem udviklingsfaserne.</p>\n<p><strong>Handling.</strong> Arbejdet holdt sig hands‑on på tværs af mange af virksomhedens projekter på én gang. Det betød at fjerne blokeringer, når folk sad fast, at kigge hårdt på en tilgang, før for meget blev bygget oven på den, og generelt at være tæt nok på til at fange et projekt på vej i den forkerte retning, mens det stadig var en kurskorrektion og ikke en genbygning. Idéen var at være tilgængelig frem for en port — at holde tingene i bevægelse, ikke at få alt til at vente på én person.</p>\n<p><strong>Resultat.</strong> Projekterne bevægede sig mere gnidningsfrit gennem deres vanskelige udviklingsfaser med en erfaren at læne sig op ad på de rigtige tidspunkter, og det stabiliserede leveringen tværs igennem virksomhedens portefølje.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Mentoring & coaching", "Projektledelse", "Teamledelse", "Teknisk ledelse", "Projektledelse (Agile)", "Teamopbygning & mentoring", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/overhauled-internal-processes-saving-8-000-hours-by-38/", "url": "https://engineer.company/da/portfolio/overhauled-internal-processes-saving-8-000-hours-by-38/", "title": "Har gennemgribende fornyet interne processer og sparet 8.000 timer ved at forbedre softwarearkitektur, systemer og planlægningseffektivitet.", "summary": "Fornyede interne processer og sparede ~8.000 timer ved at forbedre softwarearkitektur, systemer og planlægningseffektivitet.", "content_html": "<p><strong>Situation.</strong> Måden, tingene blev gjort internt på, havde samlet den sædvanlige rust — softwarearkitektur, der var vokset ved aflejring frem for design, systemer, der virkede, men ikke effektivt, planlægning, der lod folk enten vente eller være pressede. Intet af det brændte, hvilket er præcis derfor, det var blevet ladt i fred, men det kostede stille og roligt en masse tid.</p>\n<p><strong>Opgave.</strong> Målet var at forny de processer gennemgribende — at gå ud og finde spildet og tage det ud frem for at blive ved med at betale for det.</p>\n<p><strong>Handling.</strong> De interne processer blev omarbejdet på tre fronter: softwarearkitekturen, så den var noget, man kunne ræsonnere om og bygge på i stedet for at arbejde uden om; systemerne, strømlinet, så det rutineprægede arbejde holdt op med at tage længere tid, end det burde; og planlægningen, så kapaciteten faktisk blev matchet med arbejdet. Og ændringerne blev gjort til at sidde fast — forankret i, hvordan teamet opererede, frem for efterladt som et notat, alle nikkede til og glemte — for procesforbedringer, der ikke gøres permanente, forfalder bare tilbage til den gamle måde.</p>\n<p><strong>Resultat.</strong> Fornyelsen sparede omkring 8.000 timer ved at gøre arkitekturen, systemerne og planlægningen mærkbart mere effektive. Det er kapacitet, der gik direkte tilbage i arbejde med højere værdi i stedet for i overhead, ingen havde tænkt på at stille spørgsmål ved.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Infrastruktur", "Løsningsarkitektur", "Performanceoptimering", "Platformarkitektur", "Projektledelse", "Teknisk ledelse", "DevOps & CI/CD‑automatisering", "Platform- & løsningsarkitektur", "Projektledelse (Agile)", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/administered-network-infrastructure-for-over-1-000-servers-39/", "url": "https://engineer.company/da/portfolio/administered-network-infrastructure-for-over-1-000-servers-39/", "title": "Har administreret netværksinfrastruktur for over 1.000 servere og sikret optimal systemudrulning, sikkerhed og fejlfinding.", "summary": "Administrerede netværksinfrastruktur for 1.000+ servere — pålidelig udrulning, solid sikkerhed og prompt fejlfinding.", "content_html": "<p><strong>Situation.</strong> Virksomhedens drift sad oven på et stort serverestate — over tusind af dem — og et estate af den størrelse holder sig ikke pålideligt af sig selv. Udrulning, sikkerhed og den stadige strøm af ting, der går galt, kræver alle en, der kører dem med faktisk disciplin, ellers bliver det hele ustabilt, og ingen er helt sikre på hvorfor.</p>\n<p><strong>Opgave.</strong> At administrere den netværksinfrastruktur var jobbet — at holde den sikker, holde den pålidelig, holde den konsistent i en skala, hvor inkonsistens er det, der slår en ihjel.</p>\n<p><strong>Handling.</strong> Netværksinfrastrukturen kørte på tværs af mere end tusind servere. Hvordan systemer blev udrullet blev standardiseret, så en server kom op på den samme forudsigelige måde i stedet for hver at være en lille smule specialbygget; sikkerheden blev hærdet frem for at stole på, at ingen ville komme og lede; og fejlfindingen blev håndteret, når noget først gik i stykker. I den skala er standardiseringen det, der redder en — tusind snefnug er uadministrerbart, tusind af den samme ting er bare arbejde.</p>\n<p><strong>Resultat.</strong> Estatet kørte med pålidelig udrulning, solid sikkerhed og problemer, der blev håndteret prompte i stedet for at ulme. Det er den slags infrastrukturarbejde, der er usynligt, når det går godt, hvilket er pointen — det var den stabile rygrad, alt andet i virksomheden kørte på.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Drift & backup", "Infrastruktur", "Linux & servere", "Netværk & VPN", "Sikkerhed", "Systemadministration", "Netværk & VPN‑opsætning", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/established-a-technical-support-department-servicing-over-10-40/", "url": "https://engineer.company/da/portfolio/established-a-technical-support-department-servicing-over-10-40/", "title": "Har etableret en teknisk supportafdeling, der har betjent over 10.000 kunder med IT‑support og fejlfindingsløsninger.", "summary": "Etablerede en teknisk supportafdeling, der betjente 10.000+ kunder — support gik fra reaktiv til en skalerbar virksomhedsstyrke.", "content_html": "<p><strong>Situation.</strong> Kundebasen voksede og havde brug for pålidelig teknisk support, og der var bare ikke en dedikeret funktion til at give dem den i nogen egentlig skala. Support skete ad hoc, hvilket virker for en håndfuld kunder og stille og roligt falder fra hinanden, efterhånden som tallene klatrer.</p>\n<p><strong>Opgave.</strong> At rejse en egentlig teknisk supportkapacitet — en, der kunne betjene en stor og stadig voksende kundebase — var jobbet.</p>\n<p><strong>Handling.</strong> En teknisk supportafdeling blev bygget op fra ingenting. Det betød at beslutte, hvordan den faktisk skulle fungere, før der blev ansat ind i den — processen for, hvordan en henvendelse kom ind og blev løst, værktøjerne til at håndtere mængden, standarden for, hvordan god support så ud — og så bygge kapaciteten til at levere IT‑support og fejlfinding til mange kunder på én gang. At starte fra bunden var ærligt talt fordelen; den kunne designes til den skala, virksomheden var på vej mod, i stedet for at lappe på noget, der var vokset op ved et tilfælde.</p>\n<p><strong>Resultat.</strong> Afdelingen endte med at betjene over ti tusind kunder med pålidelig support og fejlfinding. Support gik fra en ting, der blev gjort reaktivt, til en ægte styrke for virksomheden — noget, der skalerede med kundebasen i stedet for at bukke under for den.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Mentoring & coaching", "Produkt & krav", "Projektledelse", "Stakeholder & rapportering", "Systemadministration", "Teamledelse", "IT‑support & helpdesk", "Projektledelse (Agile)", "Teamopbygning & mentoring" ] }, { "id": "https://engineer.company/da/portfolio/automated-ssl-tls-certificate-creation-for-100-docker-41/", "url": "https://engineer.company/da/portfolio/automated-ssl-tls-certificate-creation-for-100-docker-41/", "title": "Har automatiseret oprettelse af SSL/TLS‑certifikater for 100 Docker‑applikationer og sikret sikre forbindelser på tværs af Ubuntu Linux‑hosts.", "summary": "Automatiserede SSL/TLS-certifikater for 100 Docker-apps på Ubuntu Linux — slut med manuelle fornyelser og udløbsnedbrud.", "content_html": "<p><strong>Situation.</strong> Hundrede Docker‑applikationer havde alle brug for SSL/TLS‑certifikater, og certifikater er den slags, der er fint lige indtil de ikke er. At udstede og forny hundrede af dem i hånden er langsomt, det er kedeligt, og det er præcis den slags manuelle job, hvor én glemt fornyelse tager en app ned med en udløbsfejl på det værst tænkelige tidspunkt.</p>\n<p><strong>Opgave.</strong> Målet var oprettelse og fornyelse af certifikater automatiseret — hver app med gyldig, betroet kryptering, og ingen der skulle huske at gøre noget.</p>\n<p><strong>Handling.</strong> Et ACME‑baseret workflow håndterede hele livscyklussen for de hundrede Docker‑applikationer — oprettede certifikaterne og fornyede dem, før de udløb — og udrullede dem automatisk på tværs af Ubuntu Linux‑hostene, der kørte en blanding af Apache og Nginx. Hele målet var at tage mennesket ud af det, for mennesket er den del, der glemmer.</p>\n<p><strong>Resultat.</strong> Alle hundrede applikationer holdt gyldige certifikater og sikre forbindelser af sig selv. Det manuelle certifikatarbejde forsvandt bare, og med det hele den kategori af nedbrud, hvor noget går i stykker, ikke fordi det fejlede, men fordi et certifikat stille og roligt udløb, og ingen bemærkede det.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Containere (Docker/Kubernetes)", "DevOps", "Drift & backup", "Infrastruktur", "Linux & servere", "Sikkerhed", "Containerisering & orkestrering", "DevOps & CI/CD‑automatisering", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/streamlined-ci-cd-processes-saving-4-000-hours-42/", "url": "https://engineer.company/da/portfolio/streamlined-ci-cd-processes-saving-4-000-hours-42/", "title": "Har strømlinet CI/CD‑processer og sparet 4.000 timer ved at indføre automatisering i softwareudviklingspipelines.", "summary": "Strømlinede CI/CD og sparede ~4.000 timer — hurtigere, mere pålidelige releases, så teamet kunne udgive med tillid.", "content_html": "<p><strong>Situation.</strong> At få software ud ad døren afhang af manuelle, inkonsistente trin — nogen der huskede rækkefølgen og gjorde det en anelse forskelligt hver gang — og det bremsede releases og åd engineering‑timer, der skulle være gået til at bygge ting.</p>\n<p><strong>Opgave.</strong> Målet var at strømline CI/CD‑processen og få automatisering ind i pipelines, så releases holdt op med at være et manuelt ritual.</p>\n<p><strong>Handling.</strong> Automatiserede build-, test- og udrulningspipelines blev sat op, så vejen fra en ændring til, at den kørte i produktion, var standardiseret i stedet for improviseret. De gentagne manuelle trin — dem, der var langsomme og, værre, blev gjort forskelligt afhængigt af, hvem der gjorde dem — kom ud. Når først pipelinen gør det på samme måde hver gang, holder en hel klasse af “det virkede på min maskine” og halvt huskede udrulningstrin bare op med at ske.</p>\n<p><strong>Resultat.</strong> Automatiseringen gav omkring 4.000 timer tilbage og gjorde releases både hurtigere og mere pålidelige. Teamet kunne udgive uden at spænde ben for det — tilliden kom fra, at processen var konsistent, ikke fra at alle var forsigtige.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Drift & backup", "Teknisk ledelse", "Test & QA", "DevOps & CI/CD‑automatisering", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/streamlined-data-analysis-and-software-development-processes-saving-43/", "url": "https://engineer.company/da/portfolio/streamlined-data-analysis-and-software-development-processes-saving-43/", "title": "Har strømlinet processer for dataanalyse og softwareudvikling og sparet 4.000 timer ved at indføre CI/CD‑praksis med GitHub, GitLab, Bash og Python.", "summary": "Sparede ~4.000 timer ved at indføre CI/CD med GitHub, GitLab, Bash og Python — hurtigere dataanalyse og udvikling, mere konsistent.", "content_html": "<p><strong>Situation.</strong> Både dataanalysearbejdet og softwareudviklingen blev holdt tilbage af det samme: manuelle processer. Arbejde bevægede sig fra udvikling til levering på en langsom, inkonsistent måde, og analysesiden havde sin egen bunke gentagne trin, som nogen lavede i hånden hver gang.</p>\n<p><strong>Opgave.</strong> Målet var at strømline begge dele ved at bringe moderne automatisering og CI/CD‑praksis til workflows, der ikke havde haft dem.</p>\n<p><strong>Handling.</strong> CI/CD‑praksis blev indført, bygget på GitHub og GitLab, med Bash og Python, der lavede automatiseringsarbejdet nedenunder. De gentagne trin på tværs af både dataanalyse- og udviklingsworkflows blev automatiseret, og hvordan arbejdet bevægede sig fra udvikling og hele vejen til levering blev standardiseret, så det var det samme hver gang frem for genopfundet pr. projekt. At bringe analysesiden ind i den samme disciplinerede pipeline som udviklingsarbejdet var en stor del af det — den var blevet behandlet som en separat, mere manuel verden.</p>\n<p><strong>Resultat.</strong> De strømlinede processer sparede omkring 4.000 timer og satte fart på både dataanalysen og softwareudviklingen, og lige så nyttigt gjorde de det, der blev sendt ud, mere konsistent — færre overraskelser fra arbejde, der var blevet gjort en anelse forskelligt hver gang.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Data engineering", "Datapipelines (ETL/ELT)", "Dataanalyse", "DevOps", "Python", "Dataanalyse & BI‑dashboards", "DevOps & CI/CD‑automatisering", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/developed-a-data-analytics-reporting-system-increasing-quarterly-44/", "url": "https://engineer.company/da/portfolio/developed-a-data-analytics-reporting-system-increasing-quarterly-44/", "title": "Har udviklet et rapporteringssystem til dataanalyse og øget den kvartalsvise softwareomsætning med 400 % gennem Python‑baserede PDF‑rapporter.", "summary": "Byggede et Python-rapporteringssystem til dataanalyse, der øgede den kvartalsvise softwareomsætning 400 % med klare, rettidige rapporter.", "content_html": "<p><strong>Situation.</strong> Interessenterne fik ikke analyse i nogen rettidig, læsbar form. Dataene fandtes, men at omsætte dem til noget, man faktisk kunne træffe en beslutning ud fra, var langsomt og manuelt, så indblikket i, hvordan tingene præsterede, haltede, og de kommercielle beslutninger haltede med.</p>\n<p><strong>Opgave.</strong> At bygge et rapporteringssystem til dataanalyse — et, der omsatte rådata til klar, regelmæssig indsigt uden at nogen håndsamlede det hver gang — var jobbet.</p>\n<p><strong>Handling.</strong> Et rapporteringssystem genererede PDF‑rapporter i Python og automatiserede hele kæden: at trække dataene, køre analysen og præsentere det i et rent, konsistent format, interessenterne faktisk kunne læse. Pointen var regelmæssighed og klarhed — den samme professionelle rapport landede forudsigeligt, så tallene blev noget, folk kiggede på som en selvfølge frem for noget, de skulle gå ud og grave frem.</p>\n<p><strong>Resultat.</strong> Den rapportering er det, der drev den kvartalsvise softwareomsætning op med 400 %. At gøre analysen bedre og hurtigere var ikke en back‑office‑finesse — sæt klare, rettidige tal foran de folk, der træffer kommercielle beslutninger, og beslutningerne bliver bedre, og her viste det sig direkte på omsætningen.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Backend‑udvikling", "Data engineering", "Dataanalyse", "Produkt & krav", "Python", "Stakeholder & rapportering", "Backend- & API‑udvikling", "Dataanalyse & BI‑dashboards", "Produktstrategi & kravspecifikation" ] }, { "id": "https://engineer.company/da/portfolio/automated-data-processing-tasks-using-shell-scripting-pl-45/", "url": "https://engineer.company/da/portfolio/automated-data-processing-tasks-using-shell-scripting-pl-45/", "title": "Har automatiseret databehandlingsopgaver med shell‑scripting, PL/pgSQL, Python og Transact‑SQL og øget produktiviteten og effektiviteten.", "summary": "Automatiserede databehandling med Shell, PL/pgSQL, Python og Transact-SQL — højere produktivitet og slut med små, tilbagevendende fejl.", "content_html": "<p><strong>Situation.</strong> Der var en stabil mængde tilbagevendende databehandlingsarbejde, der blev lavet i hånden. Manuelt dataarbejde har to problemer på én gang: det æder tid, og det er inkonsistent — lav den samme opgave i hånden nok gange, og den bliver gjort en anelse forskelligt, og nogle af de forskelle er fejl.</p>\n<p><strong>Opgave.</strong> Målet var at automatisere de opgaver, både for at få tiden tilbage og for at gøre dem pålidelige.</p>\n<p><strong>Handling.</strong> Databehandlingsarbejdet blev automatiseret på tværs af de databaser og systemer, det rørte, med hvad end der passede til jobbet — shell‑scripting til limen, PL/pgSQL og Transact‑SQL nede i databaserne, Python, hvor det krævede mere, end SQL kunne give. Manuelle trin blev erstattet med jobs, der kørte på samme måde hver gang, hvilket er hele pointen: et script bliver ikke træt, springer ikke et trin over og gør det ikke anderledes en fredag eftermiddag.</p>\n<p><strong>Resultat.</strong> Produktiviteten og effektiviteten gik begge op, det manuelle arbejde kom af folks bord, og databehandlingen blev konsistent og pålidelig i stedet for en kilde til små, tilbagevendende fejl.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Data engineering", "Databaser", "Datapipelines (ETL/ELT)", "PostgreSQL", "Python", "SQL", "Databaseadministration (DBA)", "DevOps & CI/CD‑automatisering", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/enhanced-project-efficiency-saving-150-hours-per-month-46/", "url": "https://engineer.company/da/portfolio/enhanced-project-efficiency-saving-150-hours-per-month-46/", "title": "Har forbedret projekteffektiviteten og sparet 150 timer om måneden på tværs af 30 projekter ved at optimere workflows og ressourcestyring.", "summary": "Sparede ~150 timer om måneden på tværs af 30 projekter ved at optimere workflows og ressourcestyring — en tilbagevendende gevinst.", "content_html": "<p><strong>Situation.</strong> På tværs af en portefølje på omkring tredive projekter blev der tabt tid hver eneste måned til arbejdsgange, der aldrig var blevet optimeret, og ressourcestyring, der var ujævn — nogle folk underudnyttede, nogle overbelastede, arbejde planlagt forskelligt fra det ene projekt til det næste.</p>\n<p><strong>Opgave.</strong> Målet var at gøre porteføljen mere effektiv og genvinde den tid, der gik tabt måned efter måned.</p>\n<p><strong>Handling.</strong> De tredive projekter blev gennemgået, med arbejdsgangene og ressourcestyringen arbejdet på samlet — flaskehalsene taget ud, arbejdsbelastningerne balanceret, så de samme få folk ikke altid var flaskehalsen, og standardiseret, hvordan arbejdet blev planlagt og kørt, så hvert projekt ikke var sit eget særtilfælde. Tredive projekter, der hver taber lidt tid, løber op; rettelsen handlede mest om at gøre de gode vaner konsistente frem for at opfinde noget smart.</p>\n<p><strong>Resultat.</strong> Ændringerne sparede omkring 150 timer om måneden på tværs af porteføljen. Det er en tilbagevendende månedlig besparelse, ikke en engangs — tredive projekter, der kører slankere, måned ud og måned ind.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Agile & Scrum", "Automatisering & CI/CD", "Performanceoptimering", "Projektledelse", "Teamledelse", "Projektledelse (Agile)", "Teamopbygning & mentoring" ] }, { "id": "https://engineer.company/da/portfolio/managed-a-team-delivering-it-support-data-recovery-47/", "url": "https://engineer.company/da/portfolio/managed-a-team-delivering-it-support-data-recovery-47/", "title": "Har ledet et team, der leverede IT‑support, datagendannelse og hardwarereparation til over 1.000 kunder og sikret service i høj kvalitet.", "summary": "Ledede et team, der leverede IT-support, datagendannelse og hardwarereparation til 1.000+ kunder — et ry for pålidelig service.", "content_html": "<p><strong>Situation.</strong> Virksomheden var stedet, en stor base af kunder kom til, når deres IT gik i stykker — support, datagendannelse, hardwarereparation, hele spektret. At møde den slags stabile, uglamourøse efterspørgsel handler ikke om heltemod; det handler om at have et team, der kører godt dag efter dag, for arbejdet holder aldrig rigtig op med at komme.</p>\n<p><strong>Opgave.</strong> At lede det team, der leverede det hele, var jobbet, og at holde kvaliteten konsistent, om det så var en stille uge, eller alt kom på én gang.</p>\n<p><strong>Handling.</strong> Teamet kørte IT‑support, datagendannelse og hardwarereparation for over tusind kunder. En stor del af det var organiseringen nedenunder — at sikre, at arbejde blev samlet op og ikke tabt, at sætte en standard for, hvad “repareret” faktisk betød, så folk ikke fik halve reparationer tilbage, og at holde teamet effektivt, når køen var lang. Datagendannelse især er arbejde, man ikke kan være ligeglad med; det er som regel nogens billeder eller deres forretning, der ligger på det drev, og de har allerede en dårlig dag, når de når frem til dig.</p>\n<p><strong>Resultat.</strong> Teamet betjente over tusind kunder og opbyggede et ægte omdømme for pålidelig service. I den slags forretning er omdømmet alt — folk kommer tilbage, og de fortæller andre om det, netop fordi du sidste gang, noget gik i stykker, faktisk fik det ordnet.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Mentoring & coaching", "Stakeholder & rapportering", "Systemadministration", "Teamledelse", "Hardwarereparation & datagendannelse", "IT‑support & helpdesk", "Teamopbygning & mentoring" ] }, { "id": "https://engineer.company/da/portfolio/configured-and-deployed-1-000-wi-fi-routers-48/", "url": "https://engineer.company/da/portfolio/configured-and-deployed-1-000-wi-fi-routers-48/", "title": "Har konfigureret og idriftsat 1.000 Wi‑Fi‑routere og forbedret netværkstilgængeligheden og -ydeevnen for kunderne.", "summary": "Konfigurerede og idriftsatte 1.000 Wi-Fi-routere med standardopsætning — pålidelig trådløs adgang og ydeevne for kunderne.", "content_html": "<p><strong>Situation.</strong> Kunder havde brug for trådløst, der bare virkede, og det kom ned til at konfigurere og rulle et stort antal Wi‑Fi‑routere ud — og gøre det på den samme omhyggelige måde hver gang, for en router sat sjusket op er enten usikker eller langsom, og som regel finder man ud af hvilken en senere.</p>\n<p><strong>Opgave.</strong> At konfigurere og udrulle de routere for at give kunderne bedre netværksadgang og -ydeevne var jobbet.</p>\n<p><strong>Handling.</strong> Tusind Wi‑Fi‑routere blev konfigureret og udrullet. Kunsten ved det antal er at standardisere opsætningen — en konsistent, sikker, fornuftig konfiguration — frem for at tune hver enkelt fra bunden på dagen, for tusind håndlavede routere er tusind forskellige ting at supportere senere. Så de blev sat op for sikkerhed og ydeevne på samme måde hver gang og rullet pålideligt ud på tværs af kundelokationer.</p>\n<p><strong>Resultat.</strong> De tusind routere gav kunderne pålideligt trådløst — bedre adgang, bedre ydeevne — og gjorde det konsistent, fordi opsætningen var standard frem for improviseret. En router, ingen skal tænke over igen, er målet; de fleste af disse nåede dertil.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Drift & backup", "Infrastruktur", "Netværk & VPN", "Sikkerhed", "Systemadministration", "IT‑support & helpdesk", "Netværk & VPN‑opsætning" ] }, { "id": "https://engineer.company/da/portfolio/managed-4-000-computer-repairs-ensuring-rapid-and-49/", "url": "https://engineer.company/da/portfolio/managed-4-000-computer-repairs-ensuring-rapid-and-49/", "title": "Har håndteret 4.000 computerreparationer og sikret hurtig og effektiv løsning af hardware- og softwareproblemer.", "summary": "Håndterede 4.000 computerreparationer med hurtig, effektiv løsning af hardware- og softwareproblemer — pålidelig service.", "content_html": "<p><strong>Situation.</strong> Der kom en konstant strøm af computerreparationer ind — fire tusind af dem over tid — og hver eneste var en, der ventede på at få sin maskine tilbage og komme videre med sin dag. Hardwarefejl, softwarefejl, hele blandingen, og kunderne bedømte forretningen på, hvor hurtigt og hvor ordentligt de blev rettet.</p>\n<p><strong>Opgave.</strong> At håndtere de reparationer var jobbet — at holde problemer løst hurtigt og, lige så vigtigt, korrekt.</p>\n<p><strong>Handling.</strong> Fire tusind computerreparationer blev håndteret — hardware- og softwarefejlene diagnosticeret, arbejdsgangen organiseret, så maskiner bevægede sig igennem i stedet for at hobe sig op, og kvaliteten holdt, så en reparation faktisk blev gjort rigtigt frem for sendt ud igen for at fejle på ny næste uge. En reparation, der kommer tilbage, er værre end en langsom en; den koster kunden en tur til, og den koster forretningen tilliden. Så gennemløbet betød noget, men aldrig på bekostning af at rette den første gang.</p>\n<p><strong>Resultat.</strong> Alle fire tusind blev løst, og hurtigt. Folk fik deres maskiner tilbage i funktion og kom videre med tingene, og virksomhedens navn for pålidelig service holdt — hvilket i en reparationsforretning er hele forretningen.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Systemadministration", "Hardwarereparation & datagendannelse", "IT‑support & helpdesk" ] }, { "id": "https://engineer.company/da/portfolio/administered-100-bare-bone-servers-physical-networks-and-50/", "url": "https://engineer.company/da/portfolio/administered-100-bare-bone-servers-physical-networks-and-50/", "title": "Har administreret 100 barebone‑servere, fysiske netværk og IP‑telefonisystemer og sikret en robust infrastruktur til virksomhedens vækst.", "summary": "Administrerede 100 barebone-servere, fysiske netværk og IP-telefoni — det robuste fundament, virksomheden voksede på.", "content_html": "<p><strong>Situation.</strong> Under alt, hvad virksomheden lavede, lå det fysiske — servere, de faktiske netværk, IP‑telefonien — og det skulle bare køre. Det lag er usynligt, når det virker, og ekstremt synligt i det øjeblik, det ikke gør, og virksomhedens vækst hvilede på, at det holdt sig pålideligt.</p>\n<p><strong>Opgave.</strong> At administrere den infrastruktur og holde den stabil, efterhånden som virksomheden voksede, var jobbet.</p>\n<p><strong>Handling.</strong> Hundrede barebone‑servere sammen med de fysiske netværk og IP‑telefonisystemerne blev passet — opsætningen, vedligeholdelsen, fejlfindingen, når noget gik galt. Barebone‑servere betyder, at man har med hardwaren direkte at gøre, så der er en hands‑on, fysisk side af det: kablingen, kasserne, telefonsystemet, som alle bemærker i det sekund, et opkald falder. Jobbet var at holde det hele kedeligt, i den gode forstand.</p>\n<p><strong>Resultat.</strong> Serverne, netværkene og telefonien kørte pålideligt, og det pålidelige fysiske fundament er det, der lod virksomheden blive ved med at vokse, uden at grunden flyttede sig under den.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Drift & backup", "Infrastruktur", "Linux & servere", "Netværk & VPN", "Systemadministration", "Netværk & VPN‑opsætning" ] }, { "id": "https://engineer.company/da/portfolio/developed-100-web-applications-using-html-html5-css-51/", "url": "https://engineer.company/da/portfolio/developed-100-web-applications-using-html-html5-css-51/", "title": "Har udviklet 100 webapplikationer med HTML/HTML5, CSS/SCSS, Django, WordPress og Joomla og sikret en alsidig online tilstedeværelse.", "summary": "Udviklede 100 webapplikationer med HTML5, CSS/SCSS, Django, WordPress og Joomla — hver løsning matchet til kundens reelle behov.", "content_html": "<p><strong>Situation.</strong> Kunder ville have webapplikationer, og de ville have vidt forskellige ting — forskellige størrelser, forskellige budgetter, forskellige niveauer af “få mig bare online” over for “byg mig noget skræddersyet.” At møde det betød at være alsidig frem for at tvinge hver kunde ned ad den samme teknologi.</p>\n<p><strong>Opgave.</strong> At bygge webapplikationer, der gav hver kunde en alsidig, effektiv online tilstedeværelse, var jobbet.</p>\n<p><strong>Handling.</strong> Hundrede webapplikationer blev bygget, og pointen var at matche værktøjet til opgaven frem for at have en favorit. Hvor en kunde havde brug for noget skræddersyet, var det HTML/HTML5, CSS/SCSS og Django; hvor de havde brug for noget mere standard, som de også selv kunne håndtere, var WordPress eller Joomla det hurtigere, mere fornuftige svar. En del af færdigheden var at vide, hvad der var hvad — at tale en kunde fra et skræddersyet build, de ikke havde brug for, eller til et, de havde.</p>\n<p><strong>Resultat.</strong> De hundrede applikationer gav kunderne en varieret, kompetent online tilstedeværelse, hver leveret på den teknologi, der faktisk passede til den. At matche tilgangen til kunden frem for omvendt er det, der gjorde dem effektive frem for bare leveret.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Frontend‑udvikling", "Full stack‑udvikling", "Python", "Webudvikling", "Full stack‑produktudvikling", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/designed-30-websites-delivering-unique-and-flexible-solutions-52/", "url": "https://engineer.company/da/portfolio/designed-30-websites-delivering-unique-and-flexible-solutions-52/", "title": "Har designet 30 websites og leveret unikke og fleksible løsninger ved at konvertere Photoshop‑designs til HTML.", "summary": "Designede 30 websites og konverterede Photoshop-designs til HTML — distinktive, forfinede og vedligeholdelige webtilstedeværelser.", "content_html": "<p><strong>Situation.</strong> Kunder kom ind med et udseende, de ville have — ofte et færdigt visuelt design — og havde brug for et website bygget trofast ud fra det. Afstanden mellem en designfil og et fungerende site er der, hvor en masse kvalitet vindes eller tabes: det er nemt at sende noget ud, der er nogenlunde rigtigt og subtilt forkert.</p>\n<p><strong>Opgave.</strong> At designe siderne og konvertere designene til nøjagtige, fleksible implementeringer var jobbet.</p>\n<p><strong>Handling.</strong> Tredive websites blev designet og Photoshop‑designene konverteret til HTML i hånden. Trofast var standarden — mellemrummene, typografien, de detaljer, designeren faktisk mente, ikke en tilnærmelse af dem — men det var fleksibel også, for et site, der matcher mockuppen pixel for pixel og så falder fra hinanden i det øjeblik, indholdet ændrer sig, er ikke rigtig bygget godt. Så målet var markup, der forblev tro mod designet og forblev vedligeholdelig bagefter.</p>\n<p><strong>Resultat.</strong> De tredive sites matchede deres designs og forblev brugbare — distinktive, forfinede webtilstedeværelser, der ikke gik i stykker første gang, nogen redigerede dem. Trofast mod designet og stadig vedligeholdelig er den balance, der betød noget, og det er der, disse landede.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Designsystemer & UI", "Frontend‑udvikling", "UX/UI‑design", "Webudvikling", "UI/UX‑design & designsystemer", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/administered-40-websites-on-ubuntu-linux-hosting-servers-53/", "url": "https://engineer.company/da/portfolio/administered-40-websites-on-ubuntu-linux-hosting-servers-53/", "title": "Har administreret 40 websites på Ubuntu Linux‑hostingservere med Apache og Nginx og sikret høj tilgængelighed og ydeevne.", "summary": "Administrerede 40 websites på Ubuntu Linux med Apache og Nginx — høj tilgængelighed og ydeevne, hosting man ikke behøvede tænke over.", "content_html": "<p><strong>Situation.</strong> Der var en portefølje af live websites, der skulle blive oppe og blive hurtige — og hosting er endnu et af de jobs, der er usynlige, indtil et site går ned, hvorefter det er det eneste, nogen bekymrer sig om.</p>\n<p><strong>Opgave.</strong> At administrere de sites og holde dem højt tilgængelige og hurtige var jobbet.</p>\n<p><strong>Handling.</strong> Fyrre websites kørte på Ubuntu Linux‑hostingservere, på en blanding af Apache og Nginx — konfigurationen, ydeevnetuningen, den løbende vedligeholdelse for at holde dem pålidelige under reel trafik. Reel trafik er det afgørende: et site, der er fint, når ingen bruger det, og vælter, når de gør, er ikke blevet administreret, det er bare blevet ladt i fred. Så arbejdet var at holde dem sunde under faktisk belastning.</p>\n<p><strong>Resultat.</strong> Alle fyrre kørte med høj tilgængelighed og god ydeevne, hvilket gav kunderne hosting, de ikke behøvede at tænke over. Stabil og pålidelig under reel brug er hele pointen med hosting, og det er det, disse leverede.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Drift & backup", "Infrastruktur", "Linux & servere", "Performanceoptimering", "Systemadministration", "Webudvikling", "Site reliability & monitorering", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/engineered-600-pl-pgsql-based-etl-elt-pipelines-54/", "url": "https://engineer.company/da/portfolio/engineered-600-pl-pgsql-based-etl-elt-pipelines-54/", "title": "Har udviklet 600 PL/pgSQL‑baserede ETL/ELT‑pipelines for at strømline komplekse databehandlingsworkflows på tværs af flere PostgreSQL‑udviklings- og produktionsmiljøer.", "summary": "Udviklede 600 PL/pgSQL ETL/ELT-pipelines på tværs af PostgreSQL dev og prod — 35 % kortere udførelsestid og 50 %+ mere gennemløb.", "content_html": "<p><strong>Situation.</strong> Organisationen håndterede store mængder transaktions- og analysedata på tværs af flere PostgreSQL‑udviklings- og produktionsmiljøer. De eksisterende datapipelines var fragmenterede, manglede konsistens og forårsagede ydeevneflaskehalse, hvilket førte til forsinkelser i forretningskritisk rapportering og beslutningstagning.</p>\n<p><strong>Opgave.</strong> Opgaven var at designe og implementere robuste, skalerbare og effektive ETL/ELT‑pipelines med PL/pgSQL for at strømline komplekse workflows til dataindtagelse, -transformation og -indlæsning. Et nøglemål var at forbedre forespørgselsydeevnen og sikre dataintegritet på tværs af alle miljøer.</p>\n<p><strong>Handling.</strong> Over 600 PL/pgSQL‑baserede ETL/ELT‑pipelines blev udviklet til at automatisere udtræk, transformation og indlæsning af data fra forskellige kilder. Partitionerede tabeller og materialized views blev udnyttet for at optimere læseydeevnen, primary- og foreign key‑constraints anvendt for at bevare referentiel integritet, unikke og composite‑indeks designet for at fremskynde JOIN‑operationer, exception handling og transaktionskontrol indbygget for fejltolerance samt inkrementelle loads aktiveret via change data capture (CDC) og timestamp‑baserede deltaer, hvilket reducerede behandlingstiden med over 60 %. Automatiserede logging- og revisionsprocedurer blev også udviklet.</p>\n<p><strong>Resultat.</strong> Den nye ETL/ELT‑ramme forbedrede markant konsistensen, pålideligheden og ydeevnen af databehandlingsworkflows: en 35 % reduktion i den gennemsnitlige pipeline‑udførelsestid, over 50 % øget gennemløb med næsten realtids‑datatilgængelighed, over 90 % færre transformationsfejl gennem bedre constraint‑håndhævelse og validering samt forbedret vedligeholdelighed og skalerbarhed med minimal omarbejde ved fremtidige datamodelændringer.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Data engineering", "Data governance", "Databaser", "Datapipelines (ETL/ELT)", "Performanceoptimering", "PostgreSQL", "SQL", "Data governance & datakvalitet", "Databaseadministration (DBA)", "Udvikling af datapipelines (ETL/ELT)" ] }, { "id": "https://engineer.company/da/portfolio/designed-a-json-passthrough-architecture-where-postgresql-functions-58/", "url": "https://engineer.company/da/portfolio/designed-a-json-passthrough-architecture-where-postgresql-functions-58/", "title": "Har designet en JSON passthrough‑arkitektur, hvor PostgreSQL‑funktioner returnerer komplet JSON, som Go‑API'et videresender uændret, hvilket eliminerer mellemliggende unmarshalling og afkobler frontenden fra skemaændringer.", "summary": "Designede en JSON passthrough, hvor PostgreSQL-funktioner returnerer komplet JSON videresendt uændret af Go-API'et — frontend afkoblet fra skema.", "content_html": "<p><strong>Situation.</strong> Den sædvanlige måde, data kommer fra en database til en browser, er et stafetløb af transformationer. Databasen giver API’et rækker, API’et unmarshaller dem til structs, omformer dem, serialiserer dem tilbage til JSON, og først da ryger de ud. Hvert af de spring er kode, man skriver, kode, man tester, og endnu et sted, hvor API’ets idé om dataene og databasens idé om dem kan glide fra hinanden.</p>\n<p><strong>Opgave.</strong> Idéen var at springe stafetløbet over. Hvis databasen kunne returnere det færdige svar, kunne API’et bare sende det videre, og frontenden kunne afhænge direkte af databasens form i stedet for af en håndholdt kopi af den, der lå i Go.</p>\n<p><strong>Handling.</strong> Så det blev bygget som en ren passthrough. PostgreSQL‑funktionerne samler hele svaret som JSON — formningen er en SQL‑opgave, gjort der, hvor dataene i forvejen er. Go‑handleren tager det tilbage som json.RawMessage og videresender det uændret; den dekomponerer det aldrig, re‑encoder det aldrig. En lille QueryJSON‑helper gjorde det mønster til vejen med mindst modstand frem for noget, man skulle huske at gøre. Det, der faldt fra, var alt det mellemliggende maskineri, et konventionelt lagdelt API samler op — DTO’erne, mapperne, svar‑structene.</p>\n<p><strong>Resultat.</strong> Handler‑koden blev dramatisk kortere, og vigtigere endnu holdt frontenden op med at være koblet til Go. Skift, hvad en funktion returnerer, og den nye form flyder direkte igennem til klienten, uden at nogen redigerer en linje handler‑kode. Færre bevægelige dele, og en hel kategori af drift mellem lag findes simpelthen ikke her.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "API'er & integration", "Backend‑udvikling", "Databaser", "Performanceoptimering", "Platformarkitektur", "PostgreSQL", "SQL", "Backend- & API‑udvikling", "Databasedesign & datamodellering", "Platform- & løsningsarkitektur" ] }, { "id": "https://engineer.company/da/portfolio/designed-an-organization-context-switching-system-with-client-59/", "url": "https://engineer.company/da/portfolio/designed-an-organization-context-switching-system-with-client-59/", "title": "Har designet et system til at skifte organisationskontekst med client‑localStorage og server‑side cookie‑spejling, så brugere kan agere som administrerede organisationer under håndhævelse af least‑privilege‑autorisation.", "summary": "Byggede organisationskontekst-skift med localStorage og server-cookie-spejling — brugere agerer som administrerede orgs under least-privilege.", "content_html": "<p><strong>Situation.</strong> På NextMariner kan en maritim professionel administrere organisationer — virksomheder, akademier — der ikke har deres egne logins. Personen er kontoen; organisationen er noget, de agerer på vegne af. Så en bruger skal kunne bevæge sig gennem hele appen som enhver organisation, de administrerer, og skifte mellem dem frit, og den bekvemmelighed må ikke blive til et hul i autorisationen.</p>\n<p><strong>Opgave.</strong> Kontekstskift skulle være hurtigt og upåfaldende for brugeren og samtidig sikre, at den aktive kontekst aldrig af sig selv kunne give nogen adgang, de ikke var berettiget til.</p>\n<p><strong>Handling.</strong> En OrganizationContext håndterer det, med lagring på begge sider. På klienten er localStorage kilde til sandhed for, hvilken organisation man aktuelt agerer som, så skiftet er øjeblikkeligt — ingen rundtur. En server‑side cookie spejler det, så server‑renderede sider løser den samme kontekst under SSR; der er en getServerViewMode på serveren, der læser den. Det vigtige er, at intet af det bliver stolet på til adgangsbeslutninger. Autorisation genkontrolleres på serveren ved hver request. Frontend‑konteksten er der for oplevelsen — at vise dig det rigtige — og serveren er den eneste autoritet på, hvad du må.</p>\n<p><strong>Resultat.</strong> En bruger kan agere som enhver organisation, de administrerer, uden friktion, og grænsefladen holder sig i sync på både klient og server. Men fordi rettigheder verificeres server‑side hver gang, svækker intet af den bekvemmelighed sikkerheden. En, der pillede ved det, der ligger i localStorage, ændrer, hvad deres eget UI viser dem, og intet mere — serveren siger stadig nej.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "API'er & integration", "Backend‑udvikling", "Frontend‑udvikling", "Full stack‑udvikling", "Platformarkitektur", "Sikkerhed", "Backend- & API‑udvikling", "Platform- & løsningsarkitektur", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/implemented-a-catalogue-driven-deep-merge-for-stored-65/", "url": "https://engineer.company/da/portfolio/implemented-a-catalogue-driven-deep-merge-for-stored-65/", "title": "Har implementeret en katalogdrevet deep‑merge for lagrede JSON‑præferencer, hvilket forhindrer nedbrud på grund af manglende nøgler, når skemaet udvikler sig.", "summary": "Implementerede en katalogdrevet deep-merge for lagrede JSON-præferencer — forhindrer nedbrud ved manglende nøgler, når skemaet vokser.", "content_html": "<p><strong>Situation.</strong> Platformen lagrer JSON‑præferencer — notifikationsindstillinger og den slags — som en brugers gemte værdier lagt over et sæt defaults. Den oprindelige merge gjorde det på ét niveau. Problemet dukker op senere: tilføj en ny nøgle til defaults, og rækker gemt før den nøgle eksisterede, har den simpelthen ikke. Så læser noget klientkode det felt, får undefined og vælter — for præcis de brugere, der har været der længst.</p>\n<p><strong>Opgave.</strong> Det skulle være sikkert at udvikle præference‑skemaet, så det at tilføje en indstilling aldrig kunne bryde de folk, der meldte sig til, før den fandtes.</p>\n<p><strong>Handling.</strong> Enkelt‑niveau‑mergen blev erstattet med en deep‑merge drevet af defaults som et katalog. Defaults behandles som den autoritative liste over hver nøgle, der bør findes; brugerens gemte værdier merges rekursivt ovenpå, så alt i kataloget garanteret kommer ud til stede, uanset om det var i den gemte blob. Tilføj en nøgle til defaults, og den optræder i hver eksisterende rækkes effektive præferencer automatisk, nestede nøgler inkluderet. Det blev rullet ind gennem en migrering, så eksisterende data fik gavnen med det samme frem for at vente på at blive skrevet om.</p>\n<p><strong>Resultat.</strong> Præferencer kan vokse uden frygt. At tilføje en indstilling risikerer ikke længere et undefined‑felt‑crash i klienten, og frontenden holdt op med at have brug for defensive tjek spredt rundt om hvert sted, den læser en præference. Det gav også resten af platformen en pålidelig måde at udvide en hvilken som helst lagret JSON‑blob — mønsteret, ikke bare den ene rettelse.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Backend‑udvikling", "Data governance", "Databaser", "Migrering & modernisering", "PostgreSQL", "Backend- & API‑udvikling", "Data governance & datakvalitet", "Databasedesign & datamodellering" ] }, { "id": "https://engineer.company/da/portfolio/provisioned-azure-infrastructure-as-code-with-bicep-container-68/", "url": "https://engineer.company/da/portfolio/provisioned-azure-infrastructure-as-code-with-bicep-container-68/", "title": "Har provisioneret Azure‑infrastruktur som kode med Bicep — Container Apps, PostgreSQL Flexible Server, Front Door/WAF og netværk — på tværs af udviklings-, staging- og produktionsmiljøer.", "summary": "Provisionerede Azure-infrastruktur som kode med Bicep — Container Apps, PostgreSQL, Front Door/WAF — reproducerbar på tværs af miljøer.", "content_html": "<p><strong>Situation.</strong> NextMariner lever på Azure, og Azure lavet i hånden — klik gennem portalen, en indstilling justeret her og der — er en fælde. Det driver, ingen husker hvorfor noget er, som det er, og at genopbygge det efter en dårlig dag er langsomt og nervepirrende. Med mere end ét miljø at holde i takt bliver det kun værre.</p>\n<p><strong>Opgave.</strong> Læg det hele i kode, så et miljø er noget, man kan læse, reviewe og genskabe frem for en bunke manuel tilstand.</p>\n<p><strong>Handling.</strong> Estatet er defineret i Bicep. Hvert miljø — testing, staging, product — kommer ud af de samme templates: api’et og www kørende som Azure Container Apps på et managed environment, en PostgreSQL Flexible Server, Redis til caching, Front Door med en WAF‑politik ude foran og netværket under det (VNet, NSG, private DNS), med Log Analytics koblet på til diagnostik. Images hentes fra projektets Azure Container Registry. Fordi det hele er parametriseret, er det at rejse et nyt miljø eller ændre et eksisterende en pull request, ikke en supportsag til en selv.</p>\n<p><strong>Resultat.</strong> Miljøerne blev reproducerbare og reviderbare. Drift holdt op med at være et mysterium, fordi kilden til sandhed er koden, og at bringe infrastruktur op eller tilbage er et spørgsmål om at anvende templates frem for at huske, hvad der blev klikket sidst. Det er forskellen mellem infrastruktur, man ejer, og infrastruktur, der ejer én.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Cloud", "DevOps", "Drift & backup", "Infrastruktur", "Netværk & VPN", "Platformarkitektur", "Sikkerhed", "Cloud‑infrastruktur & migrering", "Infrastructure as Code", "Netværk & VPN‑opsætning", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/built-github-actions-ci-cd-pipelines-with-a-69/", "url": "https://engineer.company/da/portfolio/built-github-actions-ci-cd-pipelines-with-a-69/", "title": "Har bygget GitHub Actions CI/CD‑pipelines med et distroless produktions‑frontend‑image og promovering på tværs af flere miljøer.", "summary": "Byggede GitHub Actions CI/CD med et distroless produktions-frontend-image og promovering på tværs af miljøer — rutinemæssige releases.", "content_html": "<p><strong>Situation.</strong> Levering bør ikke afhænge af, at nogen husker trinene, og det, der ender med at køre i produktion, bør ikke være en fed general‑purpose‑container, der bærer en shell og en package manager, den aldrig vil bruge — det er bare angrebsflade, der sidder der uden grund.</p>\n<p><strong>Opgave.</strong> Gør vejen fra commit til kørende‑i‑Azure automatisk, og hold produktions‑images så små og låst ned, som hver workload tillader.</p>\n<p><strong>Handling.</strong> Pipelinen er GitHub Actions. Separate workflows håndterer kodekvalitets‑gaten, testene og de miljøspecifikke deploys, med CodeQL, dependency review og et SBOM‑trin ved siden af, så intet når et miljø uden først at passere tjekkene. Images er multi‑stage‑builds, og basen for hver del blev valgt efter dens fortjeneste frem for ét blankt valg: frontenden sendes på et distroless image (gcr.io/distroless/cc‑debian13 — ingen shell, ingen package manager), Go‑API’et på en slank Alpine og database‑imaget på postgres‑slim. Promovering flytter et build gennem miljøerne ad en defineret rute frem for i hånden.</p>\n<p><strong>Resultat.</strong> Releases holdt op med at være et omhyggeligt manuelt ritual og blev en rutinemæssig, kedelig hændelse, hvilket er præcis, hvad man vil have fra releases. Produktions‑frontenden kører på omtrent så lidt, som man kan give den, tjekkene fanger problemer, før de lander, og “deploy” er noget, pipelinen gør, frem for noget, nogen sveder sig igennem.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Cloud", "Containere (Docker/Kubernetes)", "DevOps", "Drift & backup", "Sikkerhed", "Test & QA", "Containerisering & orkestrering", "DevOps & CI/CD‑automatisering" ] }, { "id": "https://engineer.company/da/portfolio/owned-end-to-end-deployments-of-the-platform-71/", "url": "https://engineer.company/da/portfolio/owned-end-to-end-deployments-of-the-platform-71/", "title": "Har haft ansvaret for end‑to‑end‑deployments af platformen til Azure og styret releases på tværs af udviklings-, staging- og produktionsmiljøer.", "summary": "Havde ansvaret for end-to-end Azure-deployments — releases gennem dev, staging og produktion ad en defineret, gentagelig vej.", "content_html": "<p><strong>Situation.</strong> Platformen skulle nå brugerne på tværs af flere Azure‑miljøer, og deployment er den søm, hvor infrastrukturen, build‑pipelinen og applikationen alle mødes. Det er også der, hvor en lille fejl holder op med at være en bug og bliver til et nedbrud, så det er den del, man mindst af alt vil lave i hånden og halvt efter hukommelsen.</p>\n<p><strong>Opgave.</strong> Deployment blev ejet fra ende til anden, så en ændring flyttede ud til hvert miljø på den samme forudsigelige måde hver gang.</p>\n<p><strong>Handling.</strong> Releases bevæger sig ad en fast rute — udvikling, så staging, så produktion — frem for at nogen pusher direkte til et live miljø. CI/CD‑pipelinen bygger images og sender dem, og Bicep‑templates holder målinfrastrukturen ens fra det ene miljø til det næste, så et build ikke promoveres ind i et lidt anderledes sted hver gang. Konfiguration, der er forskellig pr. miljø, holdes adskilt fra secrets, hvilket betyder, at det samme byggede artefakt kan promoveres gennem miljøerne og bare samler de rette indstillinger op, hvor det lander, i stedet for at blive bygget om for hvert.</p>\n<p><strong>Resultat.</strong> Ændringer når hvert miljø forudsigeligt, ad en defineret vej, uden ad hoc‑manuelle deploys i blandingen. At release blev til et kontrolleret, gentageligt trin i stedet for et med tilbageholdt vejrtrækning, og det er en stor del af, hvad der holdt den live platform stabil, mens den stadig ændrede sig hurtigt nedenunder.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Cloud", "DevOps", "Drift & backup", "Infrastruktur", "Sikkerhed", "Cloud‑infrastruktur & migrering", "DevOps & CI/CD‑automatisering", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/delivered-full-progressive-web-app-support-installable-and-74/", "url": "https://engineer.company/da/portfolio/delivered-full-progressive-web-app-support-installable-and-74/", "title": "Har leveret fuld Progressive Web App‑understøttelse — installerbar og offline‑dygtig — med Workbox runtime‑caching via next‑pwa.", "summary": "Leverede fuld Progressive Web App-understøttelse — installerbar og offline-dygtig — med Workbox runtime-caching via next-pwa.", "content_html": "<p><strong>Situation.</strong> En stor del af NextMariners brugere er til søs. Søfarende og feltpersonale, på telefoner, ofte offline eller hængende på en dårlig forbindelse — ikke folk, der sidder ved et skrivebord på pålidelig kontor‑wifi. At bygge, som om alle havde et hurtigt, konstant netværk, ville stille og roligt have udelukket en stor del af det faktiske publikum.</p>\n<p><strong>Opgave.</strong> Appen skulle være installerbar som en native app og stadig brugbar, når netværket falder ud.</p>\n<p><strong>Handling.</strong> Den er leveret som en fuld Progressive Web App. Den installeres på hjemmeskærmen med ordentlige ikoner i de forskellige størrelser, en splash‑skærm og temafarver, så den ser ud og starter som en app frem for et bogmærke. Service workeren er sat op gennem next‑pwa‑pluginet, og Workbox runtime‑caching bruger CacheFirst til de ting, der ikke ændrer sig pr. request — skrifttyper, billeder, lyd, video, CDN‑aktiver — sammen med fornuftige HTTP cache‑control‑headere. Fordi service workers kun rigtig opfører sig over HTTPS, validerede HTTPS‑baseret test offline- og installationsadfærden på rigtige enheder i stedet for at håbe, det virkede.</p>\n<p><strong>Resultat.</strong> Brugere kan installere appen og blive ved med at bruge den offline, og genindlæsninger kommer hurtigt tilbage fra cache i stedet for over ledningen. Platformen opfører sig som en native app på det hardware, dens brugere faktisk bærer, hvilket for det her publikum ikke er en luksus — det er forskellen på, om appen er brugbar til søs eller ej.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Drift & backup", "Frontend‑udvikling", "Full stack‑udvikling", "Performanceoptimering", "Webudvikling", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/designed-an-ios-26-liquid-glass-design-system-75/", "url": "https://engineer.company/da/portfolio/designed-an-ios-26-liquid-glass-design-system-75/", "title": "Har designet et iOS 26 'Liquid Glass'-designsystem og et kanonisk komponentbibliotek håndhævet af lint‑regler for at forhindre UI‑divergens.", "summary": "Designede et iOS 26 'Liquid Glass'-designsystem og et kanonisk komponentbibliotek håndhævet af lint-regler mod UI-divergens.", "content_html": "<p><strong>Situation.</strong> Et UI uden et fælles visuelt sprog og et fast sæt komponenter driver, og det driver hurtigt. Hver ny skærm genopfinder sine egne knapper og badges og kort, hver en lille smule anderledes, og de små inkonsistenser hober sig op, indtil produktet ser usammenhængende ud, og hver ændring betyder at røre fem specialbyggede versioner af den samme ting.</p>\n<p><strong>Opgave.</strong> Designsystemet skulle være sammenhængende nok til at se bevidst ud, og håndhævbart nok til, at det ikke eroderede i det øjeblik, teamet havde travlt.</p>\n<p><strong>Handling.</strong> Det visuelle sprog og komponentsystemet blev designet som én ting. Sproget er et iOS 26 “Liquid Glass”-look — gennemsigtige paneler med backdrop‑blur, lagdelte skygger, en smule refraktion, spring‑animationer — bygget af Tailwind‑utilities. Ovenpå det sidder et kanonisk sæt komponenter, hver skærm er ment til at sammensætte af: Card, Label, Button, GlassIconButton, DirectoryGrid og resten. Det, der får det til at sidde fast, er lintingen: regler, der afviser et hjemmelavet badge eller chip og peger dig på den kanoniske komponent i stedet, så systemet holdes oppe af værktøjer frem for af, at den, der reviewer den dag, husker at bekymre sig. Og dokumentationen er parret med konkrete fejl‑til‑løsning‑noter, så vejledningen er “her er den forkerte måde og den rigtige”, ikke et abstrakt princip.</p>\n<p><strong>Resultat.</strong> UI’et forbliver konsistent og on‑brand, og de engangskomponenter, der ellers ville sprede sig, fanges, før de gør. Visuel konsistens holdt op med at være et spørgsmål om alles disciplin og blev til noget, værktøjerne holder linjen på, hvilket er den eneste version af det, der overlever en deadline.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Designsystemer & UI", "Dokumentation", "Frontend‑udvikling", "UX/UI‑design", "UI/UX‑design & designsystemer" ] }, { "id": "https://engineer.company/da/portfolio/designed-the-product-s-ui-and-ux-end-76/", "url": "https://engineer.company/da/portfolio/designed-the-product-s-ui-and-ux-end-76/", "title": "Har designet produktets UI og UX fra ende til anden — directory‑grids, dobbelte kort/tabel‑visninger, live‑kravvalidatorer og breadcrumb‑navigation.", "summary": "Designede produktets UI/UX fra ende til anden — directory-grids, kort/tabel-visninger, live-validatorer og breadcrumbs — til maritime data.", "content_html": "<p><strong>Situation.</strong> NextMariner stiller en masse forskellige entiteter foran folk — professionelle, virksomheder, skibe, jobs, anmeldelser — og grænsefladen skulle være to ting, der slås med hinanden: flot og oprigtigt brugbar. Tæt nok til at vise rigtige maritime data, men ikke så tæt, at den bliver til en mur, man preller af på.</p>\n<p><strong>Opgave.</strong> Produktets UI og UX blev ejet fra ende til anden — layoutene, interaktionsmønstrene og det mindre, som hvordan nogen bliver ført gennem en formular uden at føle sig hakket på.</p>\n<p><strong>Handling.</strong> Et par beslutninger gjorde det meste af arbejdet. Directory‑grids har en kort/tabel‑toggle, så man kan browse visuelt eller scanne en tæt tabel, og appen husker, hvilken man valgte. Formularer bruger live‑kravvalidatorer, der sidder over inputtet og viser hver regel i grønt, når den er opfyldt, og ravgult, når den ikke er, så man bliver vejledt, mens man skriver, i stedet for hakket på, efter man har sendt. Navigationen er konsistente breadcrumbs og to‑kolonne‑entitetslayouts, så sider føles som det samme produkt frem for et sæt urelaterede skærme. Og fejlfilosofien er vejledning frem for fejl — ingen røde mure, redirects i stedet for blindgyder, appen prøver at holde dig i bevægelse frem for at stoppe dig.</p>\n<p><strong>Resultat.</strong> Det, der kom ud, er en poleret, konsistent oplevelse, der gør tætte maritime data overskuelige og styrer folk gennem de komplicerede dele. Den fremstår gennemtænkt og troværdig, hvilket ikke er kosmetisk på en platform, folk bruger til deres faktiske karriere — hvis den så sjusket ud, ville de stole mindre på dataene, og de ville have ret i det.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Designsystemer & UI", "Frontend‑udvikling", "Produkt & krav", "UX/UI‑design", "Webudvikling", "Produktstrategi & kravspecifikation", "UI/UX‑design & designsystemer" ] }, { "id": "https://engineer.company/da/portfolio/hardened-the-application-with-nonce-based-csp-hsts-77/", "url": "https://engineer.company/da/portfolio/hardened-the-application-with-nonce-based-csp-hsts-77/", "title": "Har hærdet applikationen med nonce‑baseret CSP, HSTS, SameSite‑cookies, least‑privilege‑databaseroller og server‑side‑genkontrol af rettigheder.", "summary": "Hærdede appen med nonce-baseret CSP, HSTS, SameSite-cookies, least-privilege-databaseroller og server-side-genkontrol af rettigheder.", "content_html": "<p><strong>Situation.</strong> NextMariner rummer professionelle og organisatoriske data, den slags folk forventer bliver håndteret ordentligt, så en enkelt forsvarslinje var aldrig nok. Arbejdsantagelsen må være, at klienten er fjendtlig — at alt, hvad browseren håndhæver, kan slås fra af den, der holder browseren — og sikkerheden må holde alligevel.</p>\n<p><strong>Opgave.</strong> Platformen skulle hærdes på hvert lag — frontend, API, database — så sikkerhed blev håndhævet af serveren uafhængigt af, hvad grænsefladen nu tilfældigvis tillod.</p>\n<p><strong>Handling.</strong> I frontenden sætter Next.js’ proxy‑middleware — proxy.ts — en Content‑Security‑Policy med en per‑request‑nonce og strict‑dynamic, plus HSTS og SameSite‑cookies, så browseren er låst ned om, hvad den vil køre og sende. På API’et er der rate limiting, CORS, request‑størrelsesgrænser, input‑validering, før noget rører databasen, og logning af de sikkerhedsrelevante hændelser. I databasen logger API’et ind som en least‑privilege‑rolle, der kun kan EXECUTE app‑funktionerne, funktionerne kører SECURITY DEFINER, og alt er parametriseret. Og rettighederne — tier, rolle, organisation, skibs‑scoping — genkontrolleres på serveren ved hver request, hvor frontend‑gates kun behandles som UX. Gates afgør, hvad du ser; serveren afgør, hvad du må.</p>\n<p><strong>Resultat.</strong> Sikkerhed afhænger ikke af, at UI’et opfører sig. Beskyttelserne er lagdelte, så det at komme forbi én ikke får dig forbi resten, og det hele er bygget på antagelsen om, at klienten ikke kan stoles på — hvilket er den rigtige antagelse for data, folk overlader i fortrolighed.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "API'er & integration", "Backend‑udvikling", "Databaser", "Frontend‑udvikling", "Platformarkitektur", "Sikkerhed", "Backend- & API‑udvikling", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/set-a-zero-warnings-quality-bar-across-six-79/", "url": "https://engineer.company/da/portfolio/set-a-zero-warnings-quality-bar-across-six-79/", "title": "Har sat en zero‑warnings‑kvalitetsstandard på tværs af seks sprog — Go, TypeScript, SQL, Python, Shell og Markdown — håndhævet af pre‑commit‑hooks.", "summary": "Satte en zero-warnings-kvalitetsstandard på tværs af Go, TypeScript, SQL, Python, Shell og Markdown — håndhævet af pre-commit-hooks.", "content_html": "<p><strong>Situation.</strong> Advarsler, der hober sig op, er stille og roligt ætsende. Hver diagnostik, man ignorerer, sænker barren en smule, og når først buildet spytter fyrre af dem ud, læser ingen nogen af dem, og et reelt problem sidder i den liste i fuldt dagslys, fordi “advarsler” er blevet til baggrundsstøj. I et polyglot‑kodebase er der så mange flere kilder til støj til at lade det ske.</p>\n<p><strong>Opgave.</strong> Repoet skulle have én kompromisløs kvalitetsstandard på tværs af hvert sprog, så ting blev rettet i stedet for at hobe sig op.</p>\n<p><strong>Handling.</strong> En zero‑warnings‑politik gik ind, med værktøjerne som det, der håndhæver den, for en politik, der bygger på alles årvågenhed, taber til den første travle uge. Hver linter‑diagnostik er en fejl — der er intet “warn”-niveau at gemme sig i — og det er det samme på tværs af hele stakken: Go med golangci‑lint, TypeScript med ESLint, SQL med SQLFluff, Python med Ruff, shell med ShellCheck, Markdown med markdownlint. Inline‑suppressions er forbudt, så man kan ikke papre hen over en diagnostik; man er nødt til faktisk at rette tingen. Pre‑commit- og pre‑push‑hooks kører linterne og testene, så en commit, der ville introducere et problem, slet ikke bliver lavet i første omgang. Der er endda grænser for funktions- og fillængde for at holde moduler fra at brede sig ud over det punkt, hvor de er læsbare.</p>\n<p><strong>Resultat.</strong> Problemer bliver rettet ved kilden i stedet for udskudt til en backlog, ingen rydder, og kodebasen forbliver ren som standard frem for ved periodiske heltemodige indsatser. Standarden er identisk, uanset hvilket sprog man er i, og det er værktøjerne, der holder den — ikke nogens viljestyrke — hvilket er derfor, den faktisk holder.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Dokumentation", "Teknisk ledelse", "Test & QA", "DevOps & CI/CD‑automatisering", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/delivered-analysis-and-regular-progress-reports-to-the-81/", "url": "https://engineer.company/da/portfolio/delivered-analysis-and-regular-progress-reports-to-the-81/", "title": "Har leveret analyser og løbende statusrapporter til CEO'en og omsat engineering‑metrikker og leveringsstatus til beslutninger.", "summary": "Leverede analyser og løbende statusrapporter til CEO'en — omsatte engineering-metrikker og leveringsstatus til trygge beslutninger.", "content_html": "<p><strong>Situation.</strong> Efterhånden som platformen kom sammen, skulle dens fremdrift og tilstand være synlig for ledelsen i termer, de faktisk kunne gøre noget med. Rå engineering‑signaler — build‑status, leveringstempo, incidents — betyder ikke meget i sig selv for en, der træffer produkt- og investeringsvalg; de er i den forkerte højde. Nogen måtte oversætte.</p>\n<p><strong>Opgave.</strong> Den oversættelse var jobbet: at tage engineering‑virkeligheden — hvor leveringen stod, hvad systemmetrikkerne sagde — og rapportere den til CEO’en klart og regelmæssigt, så beslutninger hvilede på fakta i stedet for gætværk.</p>\n<p><strong>Handling.</strong> En fast rapporteringsrytme gik ind frem for at rapportere, når der blev spurgt, hvilket altid er en anelse for sent. Leveringsfremdriften, scope, risiciene og systemtilstanden blev fulgt og omsat til klare, beslutningsorienterede opdateringer — hvad der var på sporet, hvad der var i risiko, og hvad en given prioritet faktisk ville koste i afvejninger. Det, der skulle undgås, var at aflevere rå tal og overlade fortolkningen til nogen uden konteksten; hver rapport kom med konkrete anbefalinger, og hvor det hjalp, blev fortællingen understøttet af de underliggende analyser, så ledelsen kunne gå i dybden, hvis de ville, frem for at skulle tage det på tro.</p>\n<p><strong>Resultat.</strong> Ledelsen endte med et klart, ærligt og aktuelt billede af engineering og kunne styre produktprioriteter og investeringer med en vis tillid i stedet for at flyve i blinde. Rapportering holdt op med at være et statusritual, ingen læser, og blev til noget, beslutninger faktisk blev truffet ud fra — hvilket holdt det tekniske arbejde og forretningsretningen pegende samme vej.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Dataanalyse", "Produkt & krav", "Projektledelse", "Stakeholder & rapportering", "Teknisk ledelse", "Dataanalyse & BI‑dashboards", "Projektledelse (Agile)", "Teknisk ledelse & rådgivning" ] }, { "id": "https://engineer.company/da/portfolio/provided-round-the-clock-24-7-infrastructure-support-82/", "url": "https://engineer.company/da/portfolio/provided-round-the-clock-24-7-infrastructure-support-82/", "title": "Har leveret døgnbemandet 24/7‑infrastruktursupport til en IPTV/OTT‑streamingplatform og administreret ca. 1.000 servere samt kundeejede systemer for globale kunder i Kina, USA og Tyskland.", "summary": "Leverede 24/7-infrastruktursupport til en IPTV/OTT-streamingplatform — ~1.000 servere plus kundesystemer i Kina, USA og Tyskland.", "content_html": "<p><strong>Situation.</strong> Det her var en IPTV/OTT‑streamingplatform med kunder spredt over Kina, USA og Tyskland, hvilket betød, at der ikke var nogen stille time at lave vedligeholdelse i — nogen, et eller andet sted, kiggede altid med. Nedetid på sådan en platform er ikke en abstrakt måltal; det er nogens fjernsyn, der bare stopper, og de er ligeglade med hvorfor.</p>\n<p><strong>Opgave.</strong> At holde platformens infrastruktur tilgængelig døgnet rundt var jobbet — reelt døgnet rundt, ikke “kontortid plus en tilkaldevagt, ingen svarer på.”</p>\n<p><strong>Handling.</strong> 24/7‑support kørte for omkring tusind servere, plus et pænt antal kundeejede systemer oveni — administreret, overvåget, holdt sikret og konfigureret på tværs af hele streaming‑estatet. Fordi kunderne sad i tre vidt forskellige tidszoner, fandtes “efter arbejdstid” ikke rigtig; et problem klokken 3 om natten lokalt var primetime for nogen andre, så det blev behandlet som primetime. En stor del af arbejdet var at bemærke noget, der drev af, før det blev til et nedbrud, for på en live streamingplatform får man ikke lov at rette ting stille bagefter.</p>\n<p><strong>Resultat.</strong> Platformen forblev kontinuerligt tilgængelig for et globalt publikum, med problemer fanget og håndteret på hvilken time end de dukkede op, før de nåede en seers skærm. På en 24/7‑tjeneste er det hele jobbet — succes ser ud som ingenting, der sker, hvilket er præcis, hvad seerne ville have.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Cloud", "Drift & backup", "Infrastruktur", "Linux & servere", "Monitorering & observability", "Netværk & VPN", "Systemadministration", "IT‑support & helpdesk", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/ensured-uninterrupted-delivery-of-iptv-streaming-signals-between-83/", "url": "https://engineer.company/da/portfolio/ensured-uninterrupted-delivery-of-iptv-streaming-signals-between-83/", "title": "Har sikret uafbrudt levering af IPTV‑streamingsignaler mellem leverandører og kunder og overvåget og vedligeholdt streamingnetværket og IP‑telefonien døgnet rundt.", "summary": "Sikrede uafbrudt IPTV-streaming mellem leverandører og kunder — overvågede og vedligeholdt streamingnetværk og IP-telefoni døgnet rundt.", "content_html": "<p><strong>Situation.</strong> IPTV lever eller dør på, at signalet kommer igennem. Streamsene flyder fra leverandører, gennem platformen, til kunderne, og ethvert brud hvor som helst i den kæde er en sort skærm for nogen. IP‑telefonien sad ved siden af, med det samme krav: den skulle bare virke.</p>\n<p><strong>Opgave.</strong> At garantere, at signalleveringen og telefonien forblev uafbrudt, var jobbet.</p>\n<p><strong>Handling.</strong> Streamingnetværket og IP‑telefonien blev overvåget, fejlfundet og vedligeholdt døgnet rundt. Pointen med at holde konstant øje er, at streamingproblemer melder sig som forringelse, før de bliver til et decideret drop — en stream, der begynder at hakke, et link, der bliver ustabilt — og hvis man er opmærksom, kan man fange det ved hakket i stedet for ved den sorte skærm. Så en stor del af det var at være foran signalet frem for at reagere på klager over det.</p>\n<p><strong>Resultat.</strong> Streamsene og opkaldene forblev pålidelige på tværs af platformen, med problemer opdaget og rettet, før de blev til en tjeneste, nogen bemærkede faldt ud. At holde et signal flydende mellem leverandører og slutkunder uden et synligt hul er stille, konstant arbejde, og stille er, hvad det bør være.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Drift & backup", "Infrastruktur", "Linux & servere", "Monitorering & observability", "Netværk & VPN", "Systemadministration", "Netværk & VPN‑opsætning", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/developed-and-maintained-django-web-applications-for-the-84/", "url": "https://engineer.company/da/portfolio/developed-and-maintained-django-web-applications-for-the-84/", "title": "Har udviklet og vedligeholdt Django‑webapplikationer til IPTV‑platformen med nye funktioner og forbedret performance og stabilitet.", "summary": "Udviklede og vedligeholdt Django-webapplikationer til en IPTV-platform — nye funktioner samt bedre performance og stabilitet.", "content_html": "<p><strong>Situation.</strong> IPTV‑platformen havde webapplikationer bygget på Django omkring sig — de dele, folk faktisk klikkede på — og de skulle blive ved med at bevæge sig fremad: nye funktioner, og den performance og stabilitet, en platform, der kører døgnet rundt, kræver.</p>\n<p><strong>Opgave.</strong> At holde de applikationer funktionsrige, hurtige og stabile var opgaven.</p>\n<p><strong>Handling.</strong> Django‑webapplikationerne blev udviklet og vedligeholdt — byggede nye funktioner, rettede fejlene og pressede på performance og stabilitet, arbejdet på tværs af teamet frem for i et hjørne. På noget, der betjener kunder kontinuerligt, er stabilitetssiden ikke en luksus ved siden af funktionerne; det er den begrænsning, funktionerne må respektere. En prangende funktion, der får tingen til at vakle, er ikke meget værd, når tingen ikke har råd til at vakle.</p>\n<p><strong>Resultat.</strong> Applikationerne blev ved med at få flere funktioner og forblev samtidig pålidelige for både de interne teams og kunderne. At tilføje til noget uden at destabilisere det er den balance, der betød noget her, og det er det, arbejdet holdt sig til.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "API'er & integration", "Backend‑udvikling", "Frontend‑udvikling", "Full stack‑udvikling", "Python", "Webudvikling", "Backend- & API‑udvikling", "Full stack‑produktudvikling", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/planned-and-implemented-new-infrastructure-functionality-for-internal-85/", "url": "https://engineer.company/da/portfolio/planned-and-implemented-new-infrastructure-functionality-for-internal-85/", "title": "Har planlagt og implementeret ny infrastrukturfunktionalitet til interne og eksterne systemer og bygget løsninger, der stadig kører år efter med minimale ændringer.", "summary": "Planlagde og byggede infrastruktur til interne og eksterne systemer — robust nok til stadig at køre år efter med minimale ændringer.", "content_html": "<p><strong>Situation.</strong> Efterhånden som organisationen voksede, blev dens interne og eksterne systemer ved med at have brug for nye funktioner skruet på. Den nemme måde at gøre det på er, hvad end der er hurtigst i dag; problemet med den nemme måde er, at man er tilbage og retter det om et halvt år.</p>\n<p><strong>Opgave.</strong> At planlægge og bygge infrastrukturfunktionalitet, der faktisk ville holde, var opgaven — ikke bare virke nu, men blive ved med at virke.</p>\n<p><strong>Handling.</strong> Ny infrastrukturfunktionalitet blev planlagt og implementeret på tværs af de interne og eksterne systemer, designet til at være holdbar — den slags, man bygger én gang, ordentligt, så den bliver ved med at køre i årevis med minimal berøring frem for at kræve konstant opmærksomhed. Det er et bevidst valg hver gang: at bruge en smule mere tanke i starten, så man ikke skriver sig op til at passe på den for evigt.</p>\n<p><strong>Resultat.</strong> Systemerne forblev funktionelle og effektive længe efter, de blev bygget, og kørte i årevis med næsten ingen ændringer. Den lang levetid er det egentlige mål for infrastrukturarbejde — enhver kan lave noget, der virker i dag; at lave noget, der stadig stille og roligt virker år senere, er det sværere og mere nyttige.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Drift & backup", "Infrastruktur", "Løsningsarkitektur", "Platformarkitektur", "Systemadministration", "Platform- & løsningsarkitektur", "Site reliability & monitorering" ] }, { "id": "https://engineer.company/da/portfolio/as-one-of-the-first-hires-designed-and-86/", "url": "https://engineer.company/da/portfolio/as-one-of-the-first-hires-designed-and-86/", "title": "Har som en af de første medarbejdere designet og bygget hele kerneinfrastrukturen og de understøttende processer fra bunden for en grøn‑energi‑SaaS‑startup og lagt fundamentet for hurtig vækst.", "summary": "Designede og byggede som en af de første medarbejdere hele kerneinfrastrukturen fra bunden for en grøn-energi-SaaS-startup — grundlag for vækst.", "content_html": "<p><strong>Situation.</strong> Det her var en grøn‑energi‑SaaS‑startup med en lovende idé og reelt ingen teknisk fundament under sig endnu. At komme ind som en af de første medarbejdere betød det stadie, hvor der ikke er noget at vedligeholde, fordi der ikke findes noget — man bygger den grund, alle andre skal stå på.</p>\n<p><strong>Opgave.</strong> At bygge kerneinfrastrukturen og processerne omkring den, fra bunden, var jobbet.</p>\n<p><strong>Handling.</strong> Hele kerneinfrastrukturen og dens understøttende processer blev designet og bygget — serverne, netværkene, dataflowet, sikkerheden, driftssiden. At gøre det i en startup betyder at træffe beslutninger, der er svære at omgøre senere, så målet var ikke bare “få noget til at køre”, det var at lægge et fundament, der kunne bære vægten af hurtig vækst uden at skulle rives ud i det øjeblik, virksomheden blev større. Tidlige infrastrukturvalg bliver enten det, der lader dig skalere, eller det, du bruger et år på at gøre om; sigtet var klart den første slags.</p>\n<p><strong>Resultat.</strong> Startuppen kom ud med et solidt teknisk fundament, og det er det, der lod forretningen vokse hurtigt bagefter. At være den, der bygger den base fra ingenting, er en særlig slags ansvar — gør du det rigtigt, bemærker ingen det, gør du det forkert, bemærker alle det — og denne holdt.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Cloud", "DevOps", "Infrastruktur", "Løsningsarkitektur", "Platformarkitektur", "Sikkerhed", "Systemadministration", "Cloud‑infrastruktur & migrering", "DevOps & CI/CD‑automatisering", "Platform- & løsningsarkitektur" ] }, { "id": "https://engineer.company/da/portfolio/maintained-and-enhanced-the-legacy-hugo-static-site-87/", "url": "https://engineer.company/da/portfolio/maintained-and-enhanced-the-legacy-hugo-static-site-87/", "title": "Har vedligeholdt og forbedret det ældre Hugo‑baserede statiske website og bidraget med UI/UX‑forbedringer til det primære asset management‑produkt.", "summary": "Vedligeholdt og forbedrede et ældre Hugo-baseret website og bidrog med UI/UX-forbedringer til det centrale asset management-produkt.", "content_html": "<p><strong>Situation.</strong> Der var et ældre website bygget på Hugo — en statisk site‑generator — der kørte ved siden af virksomhedens hovedprodukt, et asset management‑system. Websitet var den gamle, etablerede ting; produktet var, hvor den reelle værdi sad.</p>\n<p><strong>Opgave.</strong> At holde websitet sundt og samtidig forbedre produktets oplevelse var opgaven.</p>\n<p><strong>Handling.</strong> Det statiske Hugo‑site blev vedligeholdt og forbedret — holdt opdateret og fungerende — mens UI/UX‑forbedringer og feedback samtidig fodredes ind i det primære asset management‑produkt. At dele opmærksomheden mellem et ældre site og flagskibsproduktet handler mest om ikke at lade den gamle ting rådne, mens man er fokuseret på den nye; begge repræsenterer virksomheden over for nogen, så begge skulle holdes anstændige.</p>\n<p><strong>Resultat.</strong> Websitet forblev opdateret i stedet for stille at ældes, og hovedproduktets brugervenlighed blev bedre gennem stabil, velinformeret forfinelse — den slags, der kommer af faktisk at bruge og tænke over tingen frem for at redesigne den i ét stort greb.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Frontend‑udvikling", "Full stack‑udvikling", "UX/UI‑design", "Webudvikling", "UI/UX‑design & designsystemer", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/designed-a-time-management-and-reporting-system-that-88/", "url": "https://engineer.company/da/portfolio/designed-a-time-management-and-reporting-system-that-88/", "title": "Har designet et tids- og rapporteringssystem, der forblev i produktion i årevis uden væsentlige ændringer.", "summary": "Designede et tids- og rapporteringssystem, der forblev i produktion i årevis uden væsentlige ændringer.", "content_html": "<p><strong>Situation.</strong> Teamet havde ikke en pålidelig måde at styre tid og rapportere fremdrift på. Hvilket som regel betyder, at det sker i en spredning af regneark og hukommelse, og at rapporteringen bliver en kamp ved slutningen af hver periode frem for noget, der bare falder ud af, hvordan folk arbejder.</p>\n<p><strong>Opgave.</strong> At bygge et system til begge dele, der faktisk ville holde, var opgaven.</p>\n<p><strong>Handling.</strong> Et tids- og rapporteringssystem blev designet op omkring den måde, teamet reelt arbejdede på, frem for at påtvinge en hyldevareproces, de ville ruter uden om. Det er hele kunsten med interne værktøjer — hvis det passer til det virkelige workflow, bruger folk det; hvis det slås med workflowet, forlader de det stille og roligt, og man er tilbage ved regneark. Så det blev bygget til at matche virkeligheden frem for et ideal.</p>\n<p><strong>Resultat.</strong> Det forblev i produktion i årevis uden nogen væsentlig ændring. Det er den kompliment, man vil have for et internt værktøj — ikke at det var imponerende, men at det bare blev ved med at virke, og ingen nogensinde behøvede at udskifte det. Noget, der overlever år med daglig brug urørt, var tydeligvis bygget til at passe.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Produkt & krav", "Projektledelse", "Teknisk ledelse", "Dataanalyse & BI‑dashboards", "Produktstrategi & kravspecifikation", "Projektledelse (Agile)" ] }, { "id": "https://engineer.company/da/portfolio/automated-team-collaboration-password-management-task-and-time-89/", "url": "https://engineer.company/da/portfolio/automated-team-collaboration-password-management-task-and-time-89/", "title": "Har automatiseret teamsamarbejde, adgangskodehåndtering, opgave- og tidsstyring og bygget et semi‑automatisk projektvisningssystem, hvilket øgede teamets produktivitet.", "summary": "Automatiserede samarbejde, adgangskode-, opgave- og tidsstyring og byggede et semi-automatisk projektvisningssystem — højere produktivitet.", "content_html": "<p><strong>Situation.</strong> En stor del af teamets driftsarbejde — at koordinere, håndtere adgangskoder, tracke opgaver og tid — blev lavet i hånden, og manuel koordinering er en stille skat: det er aldrig det, man bemærker, men det æder støt timer, der kunne gå et bedre sted hen.</p>\n<p><strong>Opgave.</strong> At automatisere det gentagne driftsarbejde var målet.</p>\n<p><strong>Handling.</strong> De dele, der egnede sig til det, blev automatiseret — teamsamarbejde, adgangskodehåndtering, opgavestyring, tidsstyring — med et semi‑automatisk projektvisningssystem oveni. Idéen på tværs af det hele var at tage rutinekoordineringen af folks bord, så den kørte af sig selv, og lade dem bruge den genvundne opmærksomhed på arbejde, der faktisk krævede et menneske. Visningssystemet var det samme instinkt anvendt på noget mere synligt: at gøre det at præsentere arbejdet mest muligt automatisk frem for en manuel pligt hver gang.</p>\n<p><strong>Resultat.</strong> Driftseffektiviteten steg, og teamet blev mere produktivt, fordi den rutinekoordinering, der før krævede konstant menneskelig opmærksomhed, nu i vid udstrækning kørte af sig selv. Den tid, der lækkede ud i pseudoarbejde, gik tilbage i det faktiske arbejde.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "DevOps", "Projektledelse", "Sikkerhed", "Teknisk ledelse", "DevOps & CI/CD‑automatisering", "Projektledelse (Agile)", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/integrated-a-company-wide-password-management-system-strengthening-90/", "url": "https://engineer.company/da/portfolio/integrated-a-company-wide-password-management-system-strengthening-90/", "title": "Har integreret et virksomhedsdækkende adgangskodehåndteringssystem, der styrkede sikkerheden og strømlinede adgangskontrollen.", "summary": "Integrerede et virksomhedsdækkende adgangskodehåndteringssystem — stærkere sikkerhed og strømlinet adgangskontrol i hele organisationen.", "content_html": "<p><strong>Situation.</strong> Adgangsoplysninger blev håndteret inkonsistent — forskellige folk lagrede og delte dem på forskellige, ad hoc‑måder — og den inkonsistens er i sig selv sikkerhedsrisikoen. Det er sjældent et dramatisk brud; det er en adgangskode i en chatbesked, et delt login, ingen roterer, den langsomme ophobning af små eksponeringer.</p>\n<p><strong>Opgave.</strong> At centralisere oplysningerne og gøre dem sikre var opgaven.</p>\n<p><strong>Handling.</strong> Et virksomhedsdækkende adgangskodehåndteringssystem gik ind, så der var én konsistent, sikker måde, oplysninger blev lagret og delt på, i stedet for hver enkelts personlige vane. Værdien af virksomhedsdækkende er præcis, at det ikke er valgfrit pr. person — en adgangskodehåndtering, kun halvdelen af teamet bruger, hjælper knap nok, for risikoen bor i den halvdel, der ikke gjorde. Så pointen var at gøre den sikre måde til standardmåden, overalt.</p>\n<p><strong>Resultat.</strong> Sikkerheden blev bedre, og adgangskontrollen blev enklere og mere konsistent i hele organisationen. Når først oplysningerne alle bor ét administreret sted, holder et helt sæt små, kedelige eksponeringer bare op med at være mulige — hvilket er det meste af, hvad sikkerhed i den virkelige verden faktisk er.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Automatisering & CI/CD", "Infrastruktur", "Sikkerhed", "Systemadministration", "Sikkerhed & adgangsstyring" ] }, { "id": "https://engineer.company/da/portfolio/drove-client-web-success-by-combining-custom-website-91/", "url": "https://engineer.company/da/portfolio/drove-client-web-success-by-combining-custom-website-91/", "title": "Har skabt succes for kunder på nettet ved at kombinere skræddersyet webudvikling og -design med SEO, indholdsstrategi og tekstforfatning.", "summary": "Skabte succes for kunder på nettet ved at kombinere skræddersyet webudvikling og -design med SEO, indholdsstrategi og tekstforfatning.", "content_html": "<p><strong>Situation.</strong> Kunder ville ikke egentlig have et website; de ville have det, et website er ment til at gøre for dem — at blive fundet, at trække kunder ind, faktisk at virke som en kanal. Et smukt site, ingen kan finde, er en fiasko, der ligner en succes.</p>\n<p><strong>Opgave.</strong> At kombinere opbygningen og vækstsiden i ét tilbud var opgaven, frem for at aflevere et site og ønske dem held og lykke.</p>\n<p><strong>Handling.</strong> De to halvdele blev sat sammen — skræddersyet webudvikling og -design på den ene side, SEO, indholdsstrategi og tekstforfatning på den anden — så en kunde fik noget, der både var velbygget og faktisk synligt. De bliver som regel behandlet som separate jobs, hvilket er, hvordan man ender med et smukt site, der rangerer ingen steder, eller et veloptimeret site, der er ubehageligt at bruge. At gøre begge dele betød, at sitet var designet fra starten til at blive fundet, ikke optimeret som en eftertanke.</p>\n<p><strong>Resultat.</strong> Kunderne endte med stærkere synlighed og mere engagement — deres sites virkede som ægte vækstkanaler frem for online brochurer. At bygge tingen og gøre den findbar i én omgang er det, der gjorde et website fra en omkostning til noget, der faktisk tjente sig ind.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Brand & marketing", "Frontend‑udvikling", "Produkt & krav", "UX/UI‑design", "Webudvikling", "Brand, marketing & SEO", "UI/UX‑design & designsystemer", "Webstedsudvikling & CMS" ] }, { "id": "https://engineer.company/da/portfolio/performed-data-recovery-across-a-wide-range-of-92/", "url": "https://engineer.company/da/portfolio/performed-data-recovery-across-a-wide-range-of-92/", "title": "Har udført datagendannelse på tværs af en bred vifte af medier — SD‑kort, harddiske, SSD'er, RAID‑arrays, eksterne drev og Mac‑systemer.", "summary": "Gendannede kritiske data på tværs af SD-kort, harddiske, SSD'er, RAID-arrays, eksterne drev og Mac — filer tilbage fra afskrevne enheder.", "content_html": "<p><strong>Situation.</strong> Folk dukkede op med lagring, der var gået i stykker eller var beskadiget, og data på den, de akut skulle have tilbage — og når først nogen bærer et dødt drev ind i en butik, er de som regel forbi bekymrede og inde i panikken. Billeder, forretningsfiler, den eneste kopi af noget, der betød noget.</p>\n<p><strong>Opgave.</strong> At gendanne deres data, på tværs af hvilke medier de end kom ind med, var jobbet.</p>\n<p><strong>Handling.</strong> Datagendannelse kørte på tværs af stort set enhver almindelig lagringstype — SD‑kort, roterende harddiske, SSD’er, RAID‑arrays, eksterne drev, Mac‑systemer. Hver af dem fejler og giver sine data fra sig forskelligt: en SSD er et andet problem end et pladedrev, et RAID‑array er et andet problem igen, og et Mac‑filsystem har sine egne særheder. Så arbejdet handlede lige så meget om at kende den rette tilgang til det specifikke medie, som det handlede om nogen enkelt teknik.</p>\n<p><strong>Resultat.</strong> Kunderne fik kritiske data tilbage fra enheder, de allerede havde afskrevet som tabt, på tværs af enhver almindelig slags lagring. Der er en specifik lettelse i nogens ansigt, når man rækker drevet tilbage med deres filer intakte — det var pointen med arbejdet, og det skete på tværs af hele viften af medier.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Drift & backup", "Systemadministration", "Hardwarereparation & datagendannelse", "IT‑support & helpdesk" ] }, { "id": "https://engineer.company/da/portfolio/diagnosed-and-repaired-laptop-hardware-screens-hinges-keyboards-93/", "url": "https://engineer.company/da/portfolio/diagnosed-and-repaired-laptop-hardware-screens-hinges-keyboards-93/", "title": "Har diagnosticeret og repareret laptop‑hardware — skærme, hængsler, tastaturer, trackpads, bundkort og strømforsyninger — og løst softwareproblemer på tværs af Linux, Windows og Mac.", "summary": "Diagnosticerede og reparerede laptop-hardware — skærme, hængsler, tastaturer, trackpads, bundkort, strøm — og løste software på Linux, Windows, Mac.", "content_html": "<p><strong>Situation.</strong> Laptops kom ind med hele viften af ting, der går galt med laptops — knuste skærme, ødelagte hængsler, døde tastaturer, svigtende trackpads, bundkortsfejl, strømproblemer — plus softwaresiden, på tværs af Linux, Windows og Mac. Kort sagt, hvad end der var galt med den, landede den på bænken.</p>\n<p><strong>Opgave.</strong> At diagnosticere og reparere dem pålideligt var jobbet — pålideligt som det afgørende ord, for en laptop‑reparation, der ikke holder, er bare et forsinket andet besøg.</p>\n<p><strong>Handling.</strong> Hardwaren blev repareret fra ende til anden — skærme, hængsler, tastaturer, trackpads, bundkort, strøm — hvilket på bundkortsniveau er oprigtigt fint, tæt arbejde, og softwareproblemerne løst på tværs af alle tre styresystemer. Diagnosen er som regel den egentlige færdighed: en laptop, der ikke vil tænde, kunne være opladeren, kortet eller batteriet, og reparationen er kun så god som gættet på, hvad der faktisk er i stykker. Så omhuen gik i at finde den rigtige fejl, før noget blev rørt.</p>\n<p><strong>Resultat.</strong> Maskinerne kom tilbage til deres ejere velfungerende og pålidelige, hardware og software begge ordnet. En reparation, der holder, er den eneste slags værd at lave, og at få diagnosen rigtig først er det, der fik disse til at holde.</p>\n", "date_published": "2026-08-17T15:08:44+02:00", "date_modified": "2026-08-17T15:08:44+02:00", "language": "da", "tags": [ "Drift & backup", "Linux & servere", "Systemadministration", "Hardwarereparation & datagendannelse", "IT‑support & helpdesk" ] }, { "id": "https://engineer.company/da/notes/solana-mobile-wallet-deeplinks/", "url": "https://engineer.company/da/notes/solana-mobile-wallet-deeplinks/", "title": "Sådan åbner man en dApp i Solana-mobilwallets", "summary": "Hvorfor browse-deeplinks til Phantom, Solflare og Backpack fejler på mobilen — og de formater, regler og rettelser, der får dem til at virke.", "content_html": "<p>En React-dApp bygget på <code>@solana/wallet-adapter-react</code> forbinder\ndesktop-wallets uden problemer, men på en telefon falder det samme flow fra\nhinanden: wallet’en skal åbne dApp’en i sin egen indbyggede browser, og de\ndeeplinks, der skulle klare det, virker bare ikke. Backpack lander på en “hent\nappen”-side; Solflare åbner appen, men aldrig sitet; alle varianter ser ud til\nat fejle. Vi skilte problemet ad, og det viste sig at være fire adskilte\nproblemer med ét fælles symptom.</p>\n<h2 id=\"de-fire-problemer\">De fire problemer</h2>\n\n<ul>\n<li><strong>Backpack-linket var forkert bygget.</strong> Det eneste dokumenterede format er\n<code>https://backpack.app/ul/v1/browse/<url>?ref=<ref></code> — et universelt link med\nmål-URL’en i stien og et påkrævet <code>ref</code>. Et gæt med eget skema som\n<code>backpack://ul/v1/browse?url=...</code> matcher ingen rute i appen, så brugeren\nender på wallet’ens installationsside.</li>\n<li><strong>Solflare skal også bruge sit universelle link:</strong>\n<code>https://solflare.com/ul/v1/browse/<url>?ref=<ref></code> — ikke det rå\n<code>solflare://</code>-skema. Et råt skema kan starte appen uden at dirigere den —\nhvilket er præcis “appen åbner, men site-fanen må åbnes med hånden”.</li>\n<li><strong>Begge parametre skal være kodet.</strong> <code>url</code> er dApp’ens fulde absolutte\nadresse, og <code>ref</code> er den kaldende origin, hver især gennem\n<code>encodeURIComponent</code>. Et ukodet <code>?</code> eller <code>&</code> i målet ødelægger\nfortolkningen, og wallet’en åbner på sin forside i stedet for browserfanen.</li>\n<li><strong>Udløsningen betyder lige så meget som linket.</strong> Universelle links skifter\nkun app ved en navigation, styresystemet stoler på — og de gør med vilje\ningenting, når de indsættes i adresselinjen, hvilket også er sådan, et helt\nkorrekt link “fejler” under test.</li>\n</ul>\n<h2 id=\"de-dokumenterede-formater\">De dokumenterede formater</h2>\n\n<ul>\n<li>Phantom: <code>https://phantom.app/ul/browse/<url>?ref=<ref></code> — uden <code>/v1</code> i\nnetop dette.</li>\n<li>Solflare: <code>https://solflare.com/ul/v1/browse/<url>?ref=<ref></code></li>\n<li>Backpack: <code>https://backpack.app/ul/v1/browse/<url>?ref=<ref></code></li>\n</ul>\n<p>Ét mønster dækker alle tre:</p>\n<pre tabindex=\"0\"><code>const WALLET_BROWSE = {\n phantom: (url, ref) =>\n `https://phantom.app/ul/browse/${url}?ref=${ref}`,\n solflare: (url, ref) =>\n `https://solflare.com/ul/v1/browse/${url}?ref=${ref}`,\n backpack: (url, ref) =>\n `https://backpack.app/ul/v1/browse/${url}?ref=${ref}`,\n};\n\nfunction walletBrowseLink(\n walletName,\n targetUrl = window.location.href,\n) {\n const build = WALLET_BROWSE[walletName.toLowerCase()];\n if (!build) return null;\n return build(\n encodeURIComponent(targetUrl),\n encodeURIComponent(window.location.origin),\n );\n}\n</code></pre><h2 id=\"udløs-linket-så-ios-og-android-accepterer-det\">Udløs linket, så iOS og Android accepterer det</h2>\n\n<ul>\n<li><strong>Render et rigtigt anker, beregnet på forhånd.</strong> Et almindeligt\n<code><a href={walletBrowseLink('phantom')}></code> er den mest pålidelige udløser på\nbegge platforme.</li>\n<li><strong>Skal det ske programmatisk</strong>, så tildel <code>window.location.href</code> synkront\ninde i tryk-handleren — ingen <code>await</code>, ingen <code>fetch</code>, ingen <code>setTimeout</code>\nførst. Efter asynkront arbejde er gestus-konteksten væk, og iOS falder\ntilbage til wallet’ens websted. Aldrig <code>window.open</code>.</li>\n<li><strong>Test aldrig ved at indsætte i adresselinjen.</strong> Universelle links udløses\nmed vilje ikke dér; test med et link, der trykkes på, eller en QR-kode, som\nkameraet scanner.</li>\n<li><strong>Pas på messenger-webviews.</strong> Åbnet i Telegrams eller Instagrams indbyggede\nbrowser bliver universelle links ofte slugt, og wallet’ens almindelige\nwebsted indlæses i stedet. User-agent-detektion er i bedste fald et gæt, så\ngiv også brugerne en synlig nødudgang: “åbn i Safari eller Chrome, og\nforbind derefter”.</li>\n</ul>\n<h2 id=\"den-større-løsning-på-android\">Den større løsning på Android</h2>\n\n<p>Håndbyggede deeplinks er iOS-historien. På Android lader Solana Mobiles Mobile\nWallet Adapter en dApp i mobilbrowseren forbinde direkte til den installerede\nwallet-app, helt uden omvejen om den indbyggede browser. Nyere versioner af\n<code>@solana/wallet-adapter-react</code> registrerer mobiladapteren automatisk, så en\nopgradering af wallet-adapter-pakkerne kan løse Android alene. Målarkitekturen:\nMobile Wallet Adapter på Android, universelle browse-links på iOS, hvor Apple\nikke tillader en tilsvarende løsning.</p>\n<h2 id=\"efterprøv-på-en-enhed\">Efterprøv på en enhed</h2>\n\n<ol>\n<li>Rigtig enhed, wallet installeret, link åbnet fra systembrowseren — ikke fra\nen messenger.</li>\n<li>Tryk på et renderet link, eller scan en QR-kode; indsæt aldrig i\nadresselinjen.</li>\n<li>Bekræft, at wallet’en åbner, og at dApp’en indlæses i dens indbyggede\nbrowserfane — anden halvdel er den, der fejler.</li>\n<li>Gentag uden wallet’en installeret: det universelle link skal falde tilbage\ntil wallet’ens websted. Ser du dén side, mens appen er installeret, er\nlinket eller udløsningen stadig forkert.</li>\n<li>Test derefter messenger-vejen, og tilføj “åbn i browser”-hjælpen, hvis den\nfejler dér.</li>\n</ol>\n<h2 id=\"kilder\">Kilder</h2>\n\n<ul>\n<li><a href=\"https://docs.phantom.com/phantom-deeplinks/deeplinks-ios-and-android\">Phantom: deeplinks på iOS og Android</a></li>\n<li><a href=\"https://docs.solflare.com/solflare/technical/deeplinks/other-methods/browse\">Solflare: Browse-deeplinket</a></li>\n<li><a href=\"https://docs.backpack.app/deeplinks/other-methods/browse\">Backpack: Browse-deeplinket</a></li>\n<li><a href=\"https://docs.solanamobile.com/mobile-wallet-adapter/mobile-apps\">Solana Mobile: Mobile Wallet Adapter</a></li>\n</ul>\n", "date_published": "2026-08-10T00:00:00Z", "date_modified": "2026-09-03T14:03:29+02:00", "language": "da" } ] }