JSON Feed Validator

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.

Feed source

{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Engineer — Portfolio and notes",
  "home_page_url": "https://engineer.company/",
  "feed_url": "https://engineer.company/feed.json",
  "description": "Engineer ApS — software development & IT consulting in Copenhagen, Denmark. A portfolio of proven engineering achievements across software, data, cloud and IT.",
  "language": "en",
  "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/"
    }
  ],
  "items": [
    {
      "id": "https://engineer.company/portfolio/built-a-multilingual-static-site-in-hugo-126/",
      "url": "https://engineer.company/portfolio/built-a-multilingual-static-site-in-hugo-126/",
      "title": "Built a multilingual static site in Hugo across 108 template files including 53 partials, publishing every page in four representations from one content tree in three languages, and again under seven focused subdomains built from that same tree.",
      "summary": "The site serves three languages and four representations off one tree, has no backend to attack and nothing to bill, and the one piece of JavaScript in it is…",
      "content_html": "<p><strong>Situation.</strong> The company needed a public site that works in three languages, proves capability rather than asserting it, costs nothing to run, and does not hand its visitors to anybody. Most of those are ordinary requirements. Together they rule out almost every content management system, because a runtime backend is a thing to secure, to patch, to pay for, and to explain on a privacy page.</p>\n<p><strong>Task.</strong> The site had to be generated entirely at build time and still behave like a modern one — searchable, installable, syndicated, printable, and readable by a screen reader in every language it ships.</p>\n<p><strong>Action.</strong> It is a static site: 108 template files, of which 53 are partials and 18 are shortcodes, three languages, no runtime backend, and one first‑party script granted as a scoped exception for a portfolio filter whose every control is hidden until it runs. Capability is added at build time rather than in the browser, which is a product decision and is written down as one. Every page is published in four representations from one content tree — HTML, a Markdown twin, a Gemini document and a Gopher menu entry — with the home page adding three syndication feeds and an installable‑application manifest on top. The taxonomies are deliberately two rather than one, and the distinction is load‑bearing: a category is a topic tag on the work, a service is something the company sells, and collapsing them would have made the catalogue a list of skills instead of a list of offerings. The same tree is then built seven more times, once per focused subdomain, by pointing the generator at a different content directory rather than by forking anything.</p>\n<p><strong>Result.</strong> The site serves three languages and four representations off one tree, has no backend to attack and nothing to bill, and the one piece of JavaScript in it is held to a contract a linter enforces. The cost is that everything interactive has to be solved at build time or not at all, which has ruled out several things that would have been easy with a server and is the reason the search index was costed and parked rather than shipped.</p>\n",
      "date_published": "2026-09-10T17:53:23+02:00",
      "date_modified": "2026-09-10T17:53:23+02:00",
      "language": "en",
      "tags": [
        "Design Systems & UI",
        "Frontend Engineering",
        "Full‑Stack Development",
        "Internationalization",
        "Performance Tuning",
        "Web Development",
        "Frontend Development",
        "Full‑Stack Product Development",
        "Internationalization & Localization",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-a-22-linter-commit-gate-127/",
      "url": "https://engineer.company/portfolio/built-a-22-linter-commit-gate-127/",
      "title": "Built a commit gate of 22 one‑line linters plus five that earn a paragraph, with no warning tier and no inline suppressions permitted, covering HTML, CSS, JavaScript, Python, YAML, Markdown, shell, links, spelling, secrets and typography.",
      "summary": "Mechanical objections are made by a machine before a commit exists, and the gate is the only reviewer this project has.",
      "content_html": "<p><strong>Situation.</strong> A contributing guide is a set of suggestions. Everyone agrees with it, and then it is Friday, the change is small, and the guide loses. On a one‑person project that is worse rather than better, because there is no reviewer at all — the only thing between a bad change and production is the person who wrote it, at the moment they are least inclined to argue with themselves.</p>\n<p><strong>Task.</strong> The standards had to be executable, so that breaking one fails a commit rather than waiting to be noticed.</p>\n<p><strong>Action.</strong> What grew is a gate of 22 linters with no warning tier: every diagnostic is an error, and the site generator itself runs with warnings promoted to failures so even a deprecation stops the build. The obvious ones are there — HTML validation, CSS, JavaScript, Python, YAML, Markdown, shell, spelling, secrets, dead links. The interesting ones are the project‑specific checks that no off‑the‑shelf tool has an opinion about: that both colour themes paint every layered surface with the same number of layers, that no photograph is displayed wider than half its source pixels, that the deployed tree contains no private path or hostname, that the prose obeys the tone rules in all three languages, that a page has a Markdown twin and a valid alternate‑protocol representation. Inline suppressions are prohibited outright — no ignore comment, no disable directive, no bypassing the hook, and no renaming a file to dodge a matcher. The rule that keeps that honest is that a pre‑existing failure is not an excuse: a check that surfaces a defect nobody introduced gets fixed in the same pass.</p>\n<p><strong>Result.</strong> Mechanical objections are made by a machine before a commit exists, and the gate is the only reviewer this project has. The cost is stated rather than hidden: committing is slow, and a badly written guard is genuinely infuriating to work around, which is why the guards themselves were later brought under a formatter and a linter of their own.</p>\n",
      "date_published": "2026-09-10T17:53:23+02:00",
      "date_modified": "2026-09-10T17:53:23+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Documentation",
        "Frontend Engineering",
        "Python",
        "Testing & QA",
        "DevOps & CI/CD Automation",
        "Product Strategy & Requirements",
        "Technical Documentation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/ran-development-as-49-written-initiatives-141/",
      "url": "https://engineer.company/portfolio/ran-development-as-49-written-initiatives-141/",
      "title": "Ran the site's development as 51 written initiatives across 54 plan documents, each carrying its open boxes, its measurements and the decisions it declined — including six of eight agent‑readiness findings refused with the reason recorded.",
      "summary": "Fifty-one decisions with their reasoning attached, roughly three-quarters closed, and the declined ones are as retrievable as the accepted ones.",
      "content_html": "<p><strong>Situation.</strong> A one‑person company has no planning meeting, no backlog grooming and nobody to disagree with a decision. What it has instead is a strong tendency to do the work that is currently interesting, and no record afterwards of why anything was chosen — which becomes a problem the first time a decision needs revisiting or defending.</p>\n<p><strong>Task.</strong> The site&rsquo;s development needed a written record per initiative that survives being reread months later, covering the ones that were declined as well as the ones that were built.</p>\n<p><strong>Action.</strong> Fifty‑one initiatives across fifty‑four numbered and indexed plan documents, each carrying the problem, the measurements taken, the decision and its open boxes. They are not summaries written afterwards. They hold the numbers that settled the argument — the scheduling measurements that shaped the check runner, the image format that was tested and declined because it came out larger, the pricing ladder confirmed by the owner, the audit that found the site proving capability across 371 pages while stating no price, no engagement shape and no minimum anywhere. Declined findings get the same treatment as accepted ones: an agent‑readiness review produced eight findings and six were refused, each with the verdict recorded, specifically so that a later automated suggestion cannot quietly reverse a written decision. The status board reads a plan&rsquo;s checkboxes rather than its prose, because prose says &ldquo;done&rdquo; and boxes say what is open. That distinction was not theoretical — a documentation linter later found six plans marked done while carrying unchecked boxes.</p>\n<p><strong>Result.</strong> Fifty‑one decisions with their reasoning attached, roughly three‑quarters closed, and the declined ones are as retrievable as the accepted ones. What it costs is that every initiative has a write‑up, which is a real tax on small work and has occasionally meant the record is longer than the change it describes.</p>\n",
      "date_published": "2026-09-10T17:53:23+02:00",
      "date_modified": "2026-09-10T17:53:23+02:00",
      "language": "en",
      "tags": [
        "Agile & Scrum",
        "Documentation",
        "Product & Requirements",
        "Project Management",
        "Stakeholder & Reporting",
        "Technical Leadership",
        "Product Strategy & Requirements",
        "Project Management (Agile)",
        "Technical Documentation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/put-the-documentation-under-its-own-linter-142/",
      "url": "https://engineer.company/portfolio/put-the-documentation-under-its-own-linter-142/",
      "title": "Put the documentation under its own linter — a per‑file line budget that only ratchets down, an index requirement and a real‑path check — which found six of nine plans marked done while carrying unchecked boxes, and fourteen commit‑gating tasks named in no document.",
      "summary": "Twenty-six documents totalling about 2,666 effective lines, each under a budget that cannot grow, with every path and symbol they name checked against the…",
      "content_html": "<p><strong>Situation.</strong> The repository governs itself with written instructions — twenty‑six documents covering the quality policy, the design system, accessibility, print, metadata, translation and git. Instructions have a specific decay pattern: they get longer, they drift from the code, they start referring to files that moved, and at some size they stop being read. An unread rule is not a rule.</p>\n<p><strong>Task.</strong> The documentation needed the same treatment as the code — a linter with limits that fail rather than advise.</p>\n<p><strong>Action.</strong> Eight policies, each mechanical. A hard ceiling on document length and a softer one that warns. A per‑file line budget that only ever ratchets downward, so a document can shrink and cannot grow back. An index requirement above a threshold, and a section index above a smaller one. A location rule about which document owns which subject. Link validity. Plan honesty — the status a plan claims must match its own checkboxes. A rule that every task gating a commit is named in some document. And a real‑path check, that every directory‑qualified path and every code symbol a document names actually exists. The first run was the argument for the whole exercise: six plans marked done while carrying unchecked boxes, fourteen tasks gating every commit and named in no document at all, three broken links, one orphaned document, and three dead paths across five documents. The budget then refused its own author — writing the summary of this work into the development document pushed it past its limit, which is how a separate document came to exist, which is the policy working exactly as intended.</p>\n<p><strong>Result.</strong> Twenty‑six documents totalling about 2,666 effective lines, each under a budget that cannot grow, with every path and symbol they name checked against the tree. The uncomfortable finding is the one worth repeating: fourteen checks were gating every single commit while appearing in no document, so the rules the machine enforced and the rules the humans read had already come apart.</p>\n",
      "date_published": "2026-09-10T17:53:23+02:00",
      "date_modified": "2026-09-10T17:53:23+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Documentation",
        "Project Management",
        "Python",
        "Technical Leadership",
        "Testing & QA",
        "Project Management (Agile)",
        "Technical Documentation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-the-companys-infrastructure-as-code-108/",
      "url": "https://engineer.company/portfolio/built-the-companys-infrastructure-as-code-108/",
      "title": "Built the company's own infrastructure as 19 Ansible playbooks and 34 roles across 12,065 lines of YAML, converging a live host to a declared state with every play idempotent.",
      "summary": "The estate is reproducible from the repository, and the parts of it that were only ever true because somebody remembered them are now assertions that fail a…",
      "content_html": "<p><strong>Situation.</strong> Engineer ApS runs its own estate — a web presence, a git forge, a database, backups, mail transport and DNS — and there was nobody to hand the operations to. A one‑person company has the same failure modes as a large one and none of the redundancy, which makes the usual answer, a person who remembers how the host was set up, the least available option there is.</p>\n<p><strong>Task.</strong> The whole estate had to be described in a repository rather than in a head, and described in a form that converges a real host rather than documenting one.</p>\n<p><strong>Action.</strong> What grew from that is 19 playbooks and 34 roles across 12,065 lines of YAML. The shape matters more than the size. Composition is data rather than flags: a host sits in a tier group whose variables declare which roles it runs, so provisioning with no arguments converges every host to its declared state. Idempotence is a contract rather than an aspiration — a converged host reports zero changes, and a play that cannot say that is not finished. Four command namespaces keep the promises apart: a gate that touches no host, reports that read one and never change it, provisioning that changes a host to match the repository, and verification that changes a host on purpose and returns a verdict.</p>\n<p><strong>Result.</strong> The estate is reproducible from the repository, and the parts of it that were only ever true because somebody remembered them are now assertions that fail a run. The cost is real: every change is slower to make than editing a file on the server would be, and a converge that half‑applies is worse than one that refuses, which is why a preflight play was later added in front of it. That trade was made deliberately and it has held.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Documentation",
        "Infrastructure",
        "Linux & Servers",
        "Platform Architecture",
        "Reliability & Backups",
        "System Administration",
        "Cloud Infrastructure & Migration",
        "DevOps & CI/CD Automation",
        "Infrastructure as Code"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/ran-the-whole-company-on-one-512mb-host-109/",
      "url": "https://engineer.company/portfolio/ran-the-whole-company-on-one-512mb-host-109/",
      "title": "Ran the whole company on one 512 MB single‑core host — a git forge, a web server serving seven domains, Tor, two alternate‑protocol servers, backups and intrusion banning — by treating 464 MB of usable memory as the binding architectural constraint.",
      "summary": "The whole company runs on a machine that costs less per month than a lunch, and the design is better for the discipline rather than merely cheaper.",
      "content_html": "<p><strong>Situation.</strong> The company&rsquo;s production host is a single‑core cloud instance with 512 MB of memory and 10 GB of disk, of which about 464 MB is usable. Everything the business runs in public sits on it: the web server terminating TLS for seven domains, the git forge, a Tor onion service, a Gemini server, a Gopher server, encrypted backups and intrusion banning. The usual reaction to that list is to buy a larger box.</p>\n<p><strong>Task.</strong> The constraint had to be treated as an architectural input rather than a problem to spend money on, because the honest question was not whether a bigger machine would work but whether the design needed one.</p>\n<p><strong>Action.</strong> Memory became the argument that settled decisions. There is no monitoring agent, no metrics pipeline and no dashboard — reporting is a pull, seven commands that read the host and render Markdown, changing nothing and running only when asked. The supervisor is systemd rather than a second process manager layered on top of it, and the container plane is Quadlet units under the same supervisor rather than a daemon with its own. Web‑panel platforms were ruled out at the design stage for the same reason. When the question came up of whether the host could carry an onion service, the answer came from a day of measured samples rather than an opinion: available memory never fell below about 310 MB of 464, swap sat at 2.6 per cent and the processor was 99.7 per cent idle.</p>\n<p><strong>Result.</strong> The whole company runs on a machine that costs less per month than a lunch, and the design is better for the discipline rather than merely cheaper. What it cost is headroom for anything careless — mail is deliberately not on this box at all — it is written as an install scaffold awaiting a host of its own, because a mail server needs headroom this machine has already spent, and that is written down rather than discovered later.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Infrastructure",
        "Linux & Servers",
        "Performance Tuning",
        "Platform Architecture",
        "Reliability & Backups",
        "Solution Architecture",
        "System Administration",
        "Cloud Infrastructure & Migration",
        "Infrastructure as Code",
        "Platform & Solution Architecture"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/found-three-ssh-brute-force-protections-that-never-worked-110/",
      "url": "https://engineer.company/portfolio/found-three-ssh-brute-force-protections-that-never-worked-110/",
      "title": "Found and closed three SSH brute‑force protections that had never worked: a ban jail watching port 22 while the daemon listened on 1986, a rate limit shadowed by a broader rule above it, and a ban action whose binary never resolved, so no ban had ever applied.",
      "summary": "Three protections that had never once fired now do, and the class of defect they belong to — a control whose failure mode is that it keeps reporting healthy —…",
      "content_html": "<p><strong>Situation.</strong> A hardening review of the production host in August 2026 asked a question that usually gets a confident answer: are the SSH brute‑force protections working. All three were configured, all three appeared in every report anyone looked at, and all three had been inert since the day the host was built.</p>\n<p><strong>Task.</strong> The controls had to be checked against what the kernel actually does with a packet, rather than against the configuration files that describe what should happen to one.</p>\n<p><strong>Action.</strong> Reading the configuration would have confirmed the wrong answer three times, so the review read the running system instead. The intrusion‑banning jail was watching port 22 while the daemon had been moved to 1986 during the initial hardening — every ban it wrote named a port nothing listened on. The firewall&rsquo;s rate limit was worse in a subtler way: the rule existed, and it sat below a broader rule that matched first. Firewall user rules are evaluated top down and the first match wins, so a broad allow above a rate limit makes the limit dead code that still prints in every status listing. The third was the quietest of them: the ban action shells out to a packet‑filter binary that the package system only recommends rather than requires, so on a host without it the jail starts, counts and decides, and then fails at the one moment it tries to ban. All three fixes were small. What came out of it was not the fix but two rules that now govern the repository: a security control gets an assertion rather than a comment, and a firewall is verified by rule position rather than by rule presence.</p>\n<p><strong>Result.</strong> Three protections that had never once fired now do, and the class of defect they belong to — a control whose failure mode is that it keeps reporting healthy — is the class the platform&rsquo;s checks are now built to catch. All three had been inert from bootstrap. All three printed healthy in every place anyone looked, which is the whole reason they lasted.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Infrastructure",
        "Linux & Servers",
        "Monitoring & Observability",
        "Networking & VPN",
        "Security",
        "System Administration",
        "Networking & VPN Setup",
        "Security & Access Management",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/hardened-ssh-with-three-stage-validation-111/",
      "url": "https://engineer.company/portfolio/hardened-ssh-with-three-stage-validation-111/",
      "title": "Hardened SSH to 24 asserted directives with three‑stage validation — the candidate file, the assembled config, then the daemon's own read‑back — after the read‑back caught the running server silently overriding two of the twenty‑four.",
      "summary": "The SSH posture is now the daemon's answer rather than the repository's claim, and the difference is not theoretical — it was already two settings wide when…",
      "content_html": "<p><strong>Situation.</strong> SSH is the only interactive way into the company&rsquo;s host, and its configuration is written by an automation role that had been running clean for months. An access report in September 2026 read the daemon&rsquo;s own resolved settings and found two of them disagreeing with what the role had been writing on every single converge.</p>\n<p><strong>Task.</strong> The hardening had to become something the daemon confirms rather than something the repository asserts, because the gap between the two had already been open for months without anyone noticing.</p>\n<p><strong>Action.</strong> The cause was configuration ordering. The operating system ships its own defaults uncommented, above where a drop‑in file lands, and for the two settings in question the first occurrence wins. The fix was a filename prefix that sorts ahead of the vendor&rsquo;s, which is a one‑character class of change and exactly the sort that stays broken because nobody thinks to look. What was built around it matters more: three stages of validation on every converge. The candidate file is syntax‑checked before it is installed, so an invalid config never reaches the host. The assembled configuration is checked after installation. Then the daemon&rsquo;s own resolved output is read back and 24 directives are asserted against it, so a setting that is written but overridden fails the run. Two directives were deliberately left out, both because the daemon no longer implements them and writing them would only look thorough.</p>\n<p><strong>Result.</strong> The SSH posture is now the daemon&rsquo;s answer rather than the repository&rsquo;s claim, and the difference is not theoretical — it was already two settings wide when the check was written. A converge that cannot see whether a control took effect has not verified it, and grepping for a directive only proves it was written.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Infrastructure",
        "Linux & Servers",
        "Security",
        "System Administration",
        "Testing & QA",
        "Infrastructure as Code",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/proved-the-intrusion-banning-path-on-every-converge-112/",
      "url": "https://engineer.company/portfolio/proved-the-intrusion-banning-path-on-every-converge-112/",
      "title": "Proved the intrusion‑banning path end to end on every hardening run by banning a reserved test address, reading the resulting kernel rule and unbanning in a guaranteed cleanup block, so a jail that stops working fails a run instead of reporting healthy.",
      "summary": "A banning path that stops working now fails a converge instead of continuing to report healthy, which is the only property that mattered.",
      "content_html": "<p><strong>Situation.</strong> The intrusion‑banning service had already been caught banning a port the SSH daemon did not listen on. Fixing the port closed that instance. It did nothing about the reason the fault survived so long, which is that a ban path has no visible failure: the service runs, the jail is listed as active, and nothing anywhere says whether a ban ever reaches the kernel.</p>\n<p><strong>Task.</strong> The ban path had to be exercised on every converge, against the live rule set, rather than inferred from the service being up.</p>\n<p><strong>Action.</strong> The converge now bans an address from the block the standards reserve for documentation, reads back the resulting rule in the kernel&rsquo;s packet filter, and unbans it in a cleanup block that runs whether the check passed or failed. The reserved range is the load‑bearing choice — the test address belongs to nobody, so a stray ban that outlives a failed run cannot lock out a real network. The kernel read‑back is the other half: the service&rsquo;s own status output would report success for a ban that produced no rule, which is the exact failure being tested for. Alongside it, the ban backend is pinned rather than left to the service&rsquo;s detection, and the log backend is set to detect because the operating system ships no traditional authentication log and the wrong choice fails silently by watching a file that never appears.</p>\n<p><strong>Result.</strong> A banning path that stops working now fails a converge instead of continuing to report healthy, which is the only property that mattered. It costs a few seconds on every run and it writes and withdraws a firewall rule against production every time, which is a real intrusion into a live system and was accepted on the grounds that the alternative had already been demonstrated to be worse.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Infrastructure",
        "Linux & Servers",
        "Reliability & Backups",
        "Security",
        "Testing & QA",
        "Security & Access Management",
        "Site Reliability & Monitoring",
        "System Administration"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/verified-firewall-rules-by-position-113/",
      "url": "https://engineer.company/portfolio/verified-firewall-rules-by-position-113/",
      "title": "Verified firewall rules by position rather than presence, reading the numbered rule list and the live packet‑filter chain, because a rule that exists is not a rule any packet reaches.",
      "summary": "The firewall posture is now checked the way a packet experiences it. The most useful outcome was not the check but what it revealed about the reporting that…",
      "content_html": "<p><strong>Situation.</strong> A firewall status listing shows a set of rules. A packet filter evaluates an ordered list and stops at the first match. Those are different things, and the difference is invisible in every tool that prints a summary — which is how the host ran for a year with a rate limit that no packet ever reached, sitting below a broader rule that matched first.</p>\n<p><strong>Task.</strong> Firewall verification had to read position rather than membership, because presence had already been demonstrated to prove nothing.</p>\n<p><strong>Action.</strong> The checks now read the numbered rule listing and the live filter chain, and assert against order. Exactly one rule exists per port and always with a protocol, because a rule without one silently widens the surface. The SSH port carries a rate limit and never an allow beside it, since an allow above a limit is precisely the shadowing that was found. The declared public surface is a short list of five ports, asserted as a whole rather than checked individually, so a port that appears without being declared fails rather than being noticed. The security report renders the rules in match order with a warning at the top of the section explaining how to read it, because the next person to open that file will otherwise read a set.</p>\n<p><strong>Result.</strong> The firewall posture is now checked the way a packet experiences it. The most useful outcome was not the check but what it revealed about the reporting that preceded it: every tool involved had been printing the rate limit for a year, correctly, and none of them had been asked the only question that mattered.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Infrastructure",
        "Linux & Servers",
        "Networking & VPN",
        "Security",
        "Testing & QA",
        "Networking & VPN Setup",
        "Security & Access Management",
        "System Administration"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-encrypted-backups-with-a-monthly-restore-drill-114/",
      "url": "https://engineer.company/portfolio/built-encrypted-backups-with-a-monthly-restore-drill-114/",
      "title": "Built encrypted off‑host backups on restic with retention pruning, an integrity check and a monthly automated restore drill, then audited the recovery position and wrote down the gaps rather than leaving them to be found during an incident.",
      "summary": "The company can lose the host and get its data back, and that sentence rests on a restore that ran last month rather than on a backup that ran last night.",
      "content_html": "<p><strong>Situation.</strong> Everything the company holds — the git repositories, the database, the site — sat on one cloud instance whose provider snapshot was the entire recovery position. A provider snapshot is a fine thing to have and a poor thing to rely on: it is in the same account as the machine it protects, it is not encrypted by anyone here, and nobody had ever restored from one.</p>\n<p><strong>Task.</strong> Backups had to be encrypted, off‑host, pruned on a retention policy, and — the part that is usually skipped — actually restored from, on a schedule, without a person remembering to do it.</p>\n<p><strong>Action.</strong> The backup runs on a systemd timer: a database dump where one exists, then an encrypted deduplicated snapshot to storage at a different provider over SFTP, then a retention prune, then an integrity check. A dead‑man&rsquo;s switch is pinged on success only, which is the distinction that makes it an alarm rather than a log — a run that fails says nothing, and saying nothing is what raises the alert. Separately, a restore drill runs monthly: it pulls a known file out of the repository and compares it, so the thing being checked is a restore rather than a backup. The passphrase is written to a file the units read rather than passed through the environment, because the supervisor processes escapes in environment values and a passphrase containing a backslash would have been silently different from the one that created the repository. Afterwards the recovery position was audited and written up, and the remaining gaps were named in the document rather than left to be discovered during an incident.</p>\n<p><strong>Result.</strong> The company can lose the host and get its data back, and that sentence rests on a restore that ran last month rather than on a backup that ran last night. The audit&rsquo;s most valuable output was the list of things still not covered, which is the part a green backup report is structurally unable to tell anyone.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Documentation",
        "Infrastructure",
        "Linux & Servers",
        "Reliability & Backups",
        "Security",
        "Backup & Disaster Recovery",
        "Infrastructure as Code",
        "Site Reliability & Monitoring",
        "System Administration"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-dead-mans-switch-monitoring-115/",
      "url": "https://engineer.company/portfolio/built-dead-mans-switch-monitoring-115/",
      "title": "Built dead‑man's‑switch monitoring that pings only while memory and disk are healthy, so a degraded host raises an alert by going silent — and caught six variable names saying \"free\" where the check correctly measured \"available\", an order of magnitude apart on a 464 MB box.",
      "summary": "The host now has an alarm whose failure mode is to fire, and the naming that would have destroyed it has been corrected.",
      "content_html": "<p><strong>Situation.</strong> A 464 MB host running a forge, a database and a web server has two realistic ways to die: it runs out of memory, or it runs out of disk. Neither announces itself. Both are entirely predictable a few hours in advance if anything is looking, and nothing was.</p>\n<p><strong>Task.</strong> The host needed an alarm that works when the host does not — which rules out anything that has to send a message at the moment of failure.</p>\n<p><strong>Action.</strong> The answer is a dead‑man&rsquo;s switch on a timer. Every fifteen minutes with jitter, a small script measures available memory and free disk and pings an external service only if both are above their thresholds. Silence is the alert. A machine that has run out of memory, lost its network or stopped booting produces exactly the same signal as one that is unhealthy, which is the correct behaviour and the reason this shape was chosen over an agent that reports a status. The measurement is available memory, not free memory, and that distinction turned into the most instructive part of the work: the script had been reading the right column all along while six variable names around it said &ldquo;free&rdquo;. On a healthy Linux box free memory is near zero because the kernel uses idle memory for cache, so a reader checking those names against a ten per cent threshold would see twelve megabytes free on a 464 MB machine, conclude the check was broken, and fix it with a one‑character change that converts a working alarm into one that breaches permanently and gets muted inside a week.</p>\n<p><strong>Result.</strong> The host now has an alarm whose failure mode is to fire, and the naming that would have destroyed it has been corrected. The switch&rsquo;s real limitation is stated in its own documentation: it proves the box is healthy, not that the site is serving, and those are different questions.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Infrastructure",
        "Linux & Servers",
        "Monitoring & Observability",
        "Performance Tuning",
        "Reliability & Backups",
        "Infrastructure as Code",
        "Site Reliability & Monitoring",
        "System Administration"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/made-ansible-check-mode-tell-the-truth-116/",
      "url": "https://engineer.company/portfolio/made-ansible-check-mode-tell-the-truth-116/",
      "title": "Made check mode tell the truth across the whole platform after finding six probes deciding on a value the host never gave, because Ansible's command module reports success under --check while skipping the command entirely.",
      "summary": "A dry run now either measures something or says it did not, and neither of those is the third option it used to have.",
      "content_html": "<p><strong>Situation.</strong> A dry run against production is supposed to be the safe way to find out what a change will do. In September 2026 a dry run failed on an assertion that was simply wrong about the host, and advised the operator to set a flag that would have relaxed a security sandbox. The dry run had not read the host. It had read a value the host never gave and drawn a conclusion from it.</p>\n<p><strong>Task.</strong> Every probe whose result feeds a decision had to be made honest under check mode, and the ones that could not be had to say so out loud instead of staying quiet.</p>\n<p><strong>Action.</strong> The cause is a property of the tool that is documented and easy to forget: the command module does not execute under check mode, and what it registers is not an empty result — it is a success with empty output. Any conditional reading that register therefore decides on a value that was never measured, and it decides in whichever direction its own logic happens to point. A sweep of every registered probe found six of them, each lying differently: one reported that the supervisor accepts every unit directive without having asked the supervisor, another reported nothing to do on a host with the firewall switched off. Each was given one of two shapes. A probe that reads pre‑existing state the run has not touched is marked to run even under check mode. A probe that cannot run is skipped, and a message names what was specifically not verified — because silence in a run log reads exactly like a pass.</p>\n<p><strong>Result.</strong> A dry run now either measures something or says it did not, and neither of those is the third option it used to have. The general rule went into the development guide in the same change: check mode must not lie, and a probe that cannot see is required to announce its blindness rather than infer a verdict from it.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Infrastructure",
        "Linux & Servers",
        "Reliability & Backups",
        "Testing & QA",
        "DevOps & CI/CD Automation",
        "Infrastructure as Code",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/added-a-preflight-play-for-secrets-117/",
      "url": "https://engineer.company/portfolio/added-a-preflight-play-for-secrets-117/",
      "title": "Added a preflight play that runs the same code as the converge against operator‑local secrets in about a second, after a half‑applied production run died on its ninth task with the swap settings already written to the live host.",
      "summary": "A secret that does not resolve now costs a two-second refusal on a laptop instead of a partly-applied change to a live server.",
      "content_html": "<p><strong>Situation.</strong> Operational secrets had just been split so that each host carries its own backup repository, passphrase and alert switch. The repository code was correct. The operator&rsquo;s encrypted vault still held the previous single‑host value, and nothing could see the mismatch, because the vault is deliberately local to the operator&rsquo;s machine and invisible to the repository&rsquo;s own quality gate.</p>\n<p><strong>Task.</strong> That class of failure needed somewhere to fail cheaply, because it had just failed expensively.</p>\n<p><strong>Action.</strong> The first converge after the change connected to the live host, ran eight tasks and refused on the ninth — correctly, but from inside a run that had already written swap settings to production. A half‑applied converge is a worse answer than a refusal, so a preflight play was written: it reaches no host at all, runs locally, gathers no facts, and executes the same two task files the real converge uses against the operator&rsquo;s vault. It answers in about a second, and provisioning runs it first. The load‑bearing decision is that it is the same code rather than a second implementation of the same rule, because a check that restates a rule in another language eventually disagrees with it and disagrees silently. Writing it surfaced a trap it nearly created: facts set during a play outlive that play, so chaining the preflight and the converge into one invocation would have handed the real role a passphrase the preflight had already resolved, and passed the check on a vault that was still wrong. That was reproduced before it was guarded, in both task files.</p>\n<p><strong>Result.</strong> A secret that does not resolve now costs a two‑second refusal on a laptop instead of a partly‑applied change to a live server. The preflight is deliberately not tagged to always run and is deliberately not serialised, and both of those are recorded as decisions rather than defaults.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Infrastructure",
        "Reliability & Backups",
        "Security",
        "Testing & QA",
        "DevOps & CI/CD Automation",
        "Infrastructure as Code",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/reconciled-a-dns-zone-declaratively-118/",
      "url": "https://engineer.company/portfolio/reconciled-a-dns-zone-declaratively-118/",
      "title": "Reconciled a 20‑record DNS zone declaratively against the Cloudflare API with separate audit and BIND‑export entry points, and turned the CDN proxy back off on privacy grounds after building it.",
      "summary": "The zone is versioned, comparable and exportable, and the one decision that went against the obvious default is written down with its reasoning so that nobody…",
      "content_html": "<p><strong>Situation.</strong> The company&rsquo;s DNS carried about twenty records across a web presence, a git subdomain, two personal redirects, mail routing through a hosted mailbox provider, a transactional sending domain and the authentication records for both. All of it lived in a provider&rsquo;s web console, which means the current state was whatever the last person to click had left behind.</p>\n<p><strong>Task.</strong> The zone had to become a declaration in the repository, with a way to compare that declaration against what the provider is actually serving.</p>\n<p><strong>Action.</strong> The zone is written as data — live records, application records, transport‑policy records and a separate list of records pending removal, which is the honest way to record a deletion that has not happened yet. One play reconciles it against the provider&rsquo;s API. Two more entry points sit behind explicit opt‑in tags: an audit that reports the difference without changing anything, and an export that writes the zone out in the standard zone‑file format so it can be read by something that is not this repository. The most interesting decision was a reversal. Proxying the two web faces through the provider&rsquo;s content network was built, worked, and was then turned back off — because it costs the one sentence the company&rsquo;s privacy page is about. With a proxy in front, another company handles every visitor&rsquo;s address and every URL they request, before we do, under their policy rather than ours. For a business whose distinguishing claim is that nobody is watching, that is a worse trade than the attacks it defends against.</p>\n<p><strong>Result.</strong> The zone is versioned, comparable and exportable, and the one decision that went against the obvious default is written down with its reasoning so that nobody re‑litigates it by accident. The audit path is the part that gets used most, because knowing the difference is more often what is wanted than closing it.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Automation & CI/CD",
        "Infrastructure",
        "Networking & VPN",
        "Python",
        "Security",
        "Infrastructure as Code",
        "Networking & VPN Setup",
        "Security & Access Management",
        "System Administration"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/cut-systemd-sandbox-exposure-across-every-unit-119/",
      "url": "https://engineer.company/portfolio/cut-systemd-sandbox-exposure-across-every-unit-119/",
      "title": "Cut systemd sandbox exposure across every unit this platform installs — a dead‑man's‑switch service from 9.6 UNSAFE to 1.5, an internet‑facing git forge from 8.3 EXPOSED to 1.5 — and added a converge‑time parser check after finding a misspelled directive silently ignored in three unit templates.",
      "summary": "Every unit runs with the privileges it needs and not the ones it does not, the numbers are measured rather than asserted, and the class of defect where a typo…",
      "content_html": "<p><strong>Situation.</strong> Thirteen unit files were installed by this repository and run on the production host with the privileges the supervisor gives by default, which is most of them. A hardening score put the dead‑man&rsquo;s‑switch service at 9.6 and called it unsafe; the internet‑facing git forge scored 8.3 and exposed. Both numbers were accurate and neither had prompted anything, because a score with no threshold attached is a number people learn to skip.</p>\n<p><strong>Task.</strong> Every unit needed a sandbox proportional to what it actually does, and the sandboxes needed a check, because the failure mode of a wrong sandbox is that the unit starts and then breaks somewhere the parser cannot see.</p>\n<p><strong>Action.</strong> Privilege restrictions went into every unit template — a privilege ceiling fixed at the unit&rsquo;s own, a system‑call filter, restricted address families, and memory that is writable or executable but never both. The measured results: the dead‑man&rsquo;s switch went from 9.6 to 1.5, the forge from 8.3 to 1.5, and both backup units from 9.6 to 2.3 and 2.5. Writing them turned up something better than the scores. One directive had been misspelled in three unit templates for as long as those roles had existed — a plausible‑looking name that does not exist, which the supervisor answers by logging that it does not recognise the key and starting the unit anyway. Grepping for a directive proves it was written; only the parser proves it took effect. So a verification role now runs every unit through the supervisor&rsquo;s own verifier during the converge, and is included by every role that installs a unit. What that still cannot see is stated plainly in the same documents: every directive capable of breaking these units breaks them at runtime, not at parse time, and a score cannot tell whether a script still reaches its last line.</p>\n<p><strong>Result.</strong> Every unit runs with the privileges it needs and not the ones it does not, the numbers are measured rather than asserted, and the class of defect where a typo becomes a silently absent control now fails a run. The limits of the measurement are recorded next to it, which is the part that keeps the next reader from over‑trusting a green band.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Containers (Docker/Kubernetes)",
        "Infrastructure",
        "Linux & Servers",
        "Security",
        "Testing & QA",
        "Containerization & Orchestration",
        "Infrastructure as Code",
        "Security & Access Management",
        "System Administration"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-seven-read-only-host-reporting-roles-120/",
      "url": "https://engineer.company/portfolio/built-seven-read-only-host-reporting-roles-120/",
      "title": "Built seven read‑only reporting roles that render a live host to Markdown — facts, access, git, metrics, traffic, security and provider inventory — under a rule that no number is printed the run did not measure.",
      "summary": "The estate can be described from the repository on demand, and the reports have found real things: the two dead security controls, an undeclared listening…",
      "content_html": "<p><strong>Situation.</strong> The host had no dashboard and was not getting one, because a metrics stack does not fit on 464 MB and would not have been worth its cost if it did. That left an honest gap: there was no way to answer questions like who has access, how much has this repository grown, what does the traffic look like, or is the hardening actually enforced.</p>\n<p><strong>Task.</strong> Those questions needed answers that could be produced on demand, cost the host nothing while nobody was asking, and could never change the thing they were describing.</p>\n<p><strong>Action.</strong> Seven read‑only roles, each rendering a live host into Markdown in the repository. A facts snapshot covering operating system, hardware, storage, network, services, packages, listening sockets and processor utilisation computed from two samples of the kernel&rsquo;s own counter. An access report covering accounts, sudo scope, key fingerprints, forge users and database roles. A git report per repository. A metrics report over the day&rsquo;s collected samples. A traffic report built from the web server&rsquo;s privacy‑masked log. A provider inventory across four provider surfaces — DNS, two cloud APIs and a dedicated‑server API. And a security report that answers whether hardening is enforced rather than configured, printing the firewall rules in match order and the live ban rule set. The governing rule across all seven is that no number is printed that the run did not measure — a report that fills a gap with a plausible figure is worse than one that leaves it blank, because only the plausible one gets believed.</p>\n<p><strong>Result.</strong> The estate can be described from the repository on demand, and the reports have found real things: the two dead security controls, an undeclared listening port, and the observation that the traffic archive is only as old as the last time somebody remembered to harvest it. That last one is written up as a limitation with a specific cost recorded — sixteen days of one site&rsquo;s history that rotated away between harvests and do not exist anywhere.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Documentation",
        "Infrastructure",
        "Linux & Servers",
        "Monitoring & Observability",
        "Stakeholder & Reporting",
        "Site Reliability & Monitoring",
        "System Administration",
        "Technical Documentation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/deployed-the-companys-own-git-forge-121/",
      "url": "https://engineer.company/portfolio/deployed-the-companys-own-git-forge-121/",
      "title": "Deployed the company's own git forge on Soft Serve, private by default with no web panel and its SSH port bound to loopback behind a jump host, and made the landing page in front of it a build artefact of the main site rather than a hand‑kept copy.",
      "summary": "The company hosts its own code on its own hardware, and the page in front of it inherits every check the main site passes rather than drifting away from it in…",
      "content_html": "<p><strong>Situation.</strong> The company&rsquo;s source code lived on a third‑party hosting service, which is a reasonable place for it and a poor fit for a business whose argument to clients is that it does not hand their data to intermediaries. Running a forge instead means running a forge: authentication, access control, storage, backups, and a public face for it.</p>\n<p><strong>Task.</strong> A canonical git server had to be stood up on the existing host, with the smallest possible attack surface and no web administration panel, and a landing page in front of it that does not rot.</p>\n<p><strong>Action.</strong> The forge is a single binary supervised by the operating system, installed from its vendor&rsquo;s package repository, deliberately chosen for being SSH‑first with no administrative web interface — the less interface, the less to defend. It is private by default: anonymous access is refused, keyless access is refused, and every declared repository is marked private rather than relying on obscurity. Its SSH listener binds only to the loopback interface on port 23231 and is reached from outside through a jump host, so the firewall&rsquo;s declared surface does not grow. A protocol multiplexer that would put HTTPS and git SSH on one public port by inspecting the first bytes of a connection is written and ready behind a master switch, and that switch is off: multi‑user git over the public port is not needed yet, and a listener nobody uses is surface. The landing page in front of it was the more interesting problem. It had been a hand‑maintained copy of the main site&rsquo;s design in its own repository, and every single difference ever found between the two turned out to be an accident rather than a decision: a type scale rendering the wordmark about nine per cent too large, a font declaration that collapsed two weights to one on any machine with the family installed, ornaments hidden below a certain width so they were absent on every phone, a weight used with no font shipped for it, and no main landmark or top‑level heading on any page. Five for five, and not one visible in a screenshot. The copy was deleted; the landing page is now built by the main site&rsquo;s own templates and delivered as an artefact.</p>\n<p><strong>Result.</strong> The company hosts its own code on its own hardware, and the page in front of it inherits every check the main site passes rather than drifting away from it in ways only a measurement can see. Divergence is still available and now has to be written down.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Infrastructure",
        "Linux & Servers",
        "System Administration",
        "Web Development",
        "DevOps & CI/CD Automation",
        "Infrastructure as Code",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/deployed-a-container-plane-on-podman-and-quadlet-122/",
      "url": "https://engineer.company/portfolio/deployed-a-container-plane-on-podman-and-quadlet-122/",
      "title": "Deployed a container plane on Podman and Quadlet under systemd rather than Docker, because Docker publishes container ports above the host firewall's own rules — and gave deploys an unprivileged user with one fixed command instead of root.",
      "summary": "Containers run under the supervisor that was already trusted, behind the firewall that was already declared, and a routine release needs no privileged access.",
      "content_html": "<p><strong>Situation.</strong> The platform needed somewhere to run application containers. The obvious choice was the industry default, and the obvious choice was wrong for this host in a specific way: it rewrites the kernel&rsquo;s packet‑filter rules and publishes container ports above the firewall rules the hardening role installs, so a container quietly becomes reachable from the internet regardless of what the firewall was told.</p>\n<p><strong>Task.</strong> A container plane had to be chosen that does not bypass the firewall, does not add a second supervisor beside the one already trusted, and does not require a person to be root to ship a release.</p>\n<p><strong>Action.</strong> The plane is a daemonless container engine driving units generated by the operating system&rsquo;s own supervisor. There is no second process manager: containers are services, they start the way services start, and they are described in the same declarative form as everything else. The containers use host networking with no published ports at all, which removes the firewall‑bypass question rather than mitigating it. Units run at the system level rather than rootless, and that is recorded as a trade — it keeps the automation simple and normal, at the cost of the stricter isolation rootless would give, and the note says which way to move if container escape ever matters more than automation simplicity. Deployment was separated from provisioning: root sets up the deployment path once, and thereafter an unprivileged user redeploys by running one fixed script through a scoped rule that permits that command and no arguments. Build the image, restart the service. No root shell, no arbitrary commands.</p>\n<p><strong>Result.</strong> Containers run under the supervisor that was already trusted, behind the firewall that was already declared, and a routine release needs no privileged access. The choice against the default is written down with its reason, which matters more than the choice — the next person will be told to use the default by every article they read.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Containers (Docker/Kubernetes)",
        "DevOps",
        "Infrastructure",
        "Linux & Servers",
        "Security",
        "Cloud Infrastructure & Migration",
        "Containerization & Orchestration",
        "DevOps & CI/CD Automation",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/provisioned-a-second-server-from-code-123/",
      "url": "https://engineer.company/portfolio/provisioned-a-second-server-from-code-123/",
      "title": "Automated the provisioning of a second server on a second cloud provider, creating the firewall before the machine so it is born behind one, with both providers' firewalls written directly against their REST APIs to avoid a third‑party collection.",
      "summary": "A second host can be created, or re-adopted, from the repository, behind a firewall that already exists, at a cost the file states.",
      "content_html": "<p><strong>Situation.</strong> A second machine was needed for an application tier, at a different provider from the first, and the first machine&rsquo;s own history was the argument for how to do it: it had been created by hand, and its firewall had been added afterwards, which leaves a window where a fresh host with a default password policy is reachable from the internet.</p>\n<p><strong>Task.</strong> The machine had to be created from the repository, and it had to be born behind its firewall rather than acquiring one shortly afterwards.</p>\n<p><strong>Action.</strong> Ordering carried the design. The firewall play runs before the server play, so the rule set exists before there is anything for it to protect; on the other provider, tagging runs before the cloud firewall because a firewall that targets tags needs the tags to exist first. Both providers&rsquo; firewalls are written directly against their REST APIs through a generic HTTP task rather than through a vendor collection, which removes a dependency and its version drift at the cost of writing the request shapes by hand. The role that creates the machine adopts an existing one rather than duplicating it if it is already there, so re‑running is safe. It is also documented, in its own file, as the one role in the repository that spends money — the instance type, the region, the specification and the monthly cost in euros are all written down, because a play that bills someone should say so where they will read it. That play is also the one that cannot be rehearsed the usual way: the generic HTTP task declares no check‑mode support, so a dry run skips every task in it. The rehearsal is done against the firewall play instead, which is free and reversible.</p>\n<p><strong>Result.</strong> A second host can be created, or re‑adopted, from the repository, behind a firewall that already exists, at a cost the file states. The one thing dry‑run cannot cover is named in the same file rather than left as a surprise.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Automation & CI/CD",
        "Cloud",
        "Infrastructure",
        "Python",
        "Security",
        "Cloud Infrastructure & Migration",
        "Infrastructure as Code",
        "Networking & VPN Setup",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/wrote-a-scope-rule-after-a-credential-leak-124/",
      "url": "https://engineer.company/portfolio/wrote-a-scope-rule-after-a-credential-leak-124/",
      "title": "Wrote a scope rule into the repository after a restructure carried another company's inventory, firewall allowances and prose into it — and kept the quarantined residue under the secret scanner rather than excluding it.",
      "summary": "The scope boundary is a written rule with a stated test, the residue is visible rather than buried, and the specific shape of credential that got through now…",
      "content_html": "<p><strong>Situation.</strong> Work done for a different company rode along through a repository restructure and stayed. What came with it was not abstract: a scratch log holding live plaintext credentials, which sat in the tree past every guard for most of the repository&rsquo;s history; a stale inventory naming that company&rsquo;s host; and a firewall task file opening ports to sixteen of its client networks. None of it ran. That is exactly why it survived — nothing that runs nowhere is ever reviewed again.</p>\n<p><strong>Task.</strong> The boundary had to become a rule with a test attached, rather than an intention, and the residue had to be dealt with in a way that did not simply hide it.</p>\n<p><strong>Action.</strong> The rule is now the opening section of the repository&rsquo;s instruction set: this repository manages one company&rsquo;s infrastructure and only that, and another party&rsquo;s host, inventory, firewall allowance, DNS zone or credential does not belong in it — not even disabled, commented out, or parked in a file no playbook imports. The practical test is written next to it: would this company still be responsible for this if the relationship ended. The residue was quarantined into a clearly‑named directory rather than deleted, so the history stays legible, and the quarantine is deliberately partial — the two structural linters skip it, and the secret scanner, the vault guard and the emoji check deliberately still read it, because those are the three that would catch the thing that got in. The leak itself produced a linter rule: a custom pattern that flags a credential passed as a command‑line flag, which is the shape the leaked one had and which the standard rule set did not match.</p>\n<p><strong>Result.</strong> The scope boundary is a written rule with a stated test, the residue is visible rather than buried, and the specific shape of credential that got through now fails a commit. The lesson recorded alongside it is the general one: the dangerous artefact is not the one that runs, it is the one that does not, because that is the one nobody reads again.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Data Governance",
        "Documentation",
        "Infrastructure",
        "Security",
        "Data Governance & Quality",
        "Security & Access Management",
        "Technical Documentation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/split-every-operational-secret-per-host-125/",
      "url": "https://engineer.company/portfolio/split-every-operational-secret-per-host-125/",
      "title": "Split the four per‑host operational secrets after establishing that two hosts sharing one dead‑man's switch alert less than two switches, not more, and that a shared backup passphrase makes two hosts one repository.",
      "summary": "Secrets are per host by construction, and the two failure modes that would have followed from sharing them are written down where the next person edits the…",
      "content_html": "<p><strong>Situation.</strong> The platform was built for one host and was about to have two. Several operational secrets had been written as single values on that assumption: one backup repository, one passphrase, one alert switch. Extending them to a second host by sharing them is the path of least resistance and it is wrong in two separate ways, both of which fail quietly.</p>\n<p><strong>Task.</strong> Each secret had to become a map keyed by host, and the reason had to be written down, because the shared version looks correct and costs nothing until the day it matters.</p>\n<p><strong>Action.</strong> Two arguments settled it, and both are recorded next to the configuration. A dead‑man&rsquo;s switch shared by two hosts alerts less than two switches, not more: either host still pinging keeps the check green while the other one is dead, so adding a host to a shared switch actively reduces the coverage of the one that was already there. And a backup repository string is only a location — the passphrase is the whole of the encryption, so two hosts sharing a passphrase are not two repositories with a common secret, they are one repository with two directories in it. Each value became a map keyed by inventory hostname, resolved per host by a shared task file that both the converge and the preflight run. The existing host was then named in all four maps with its repository and both switches left empty, which is the truthful state and keeps the two open initiatives honest about owing the operator half of the work rather than the code.</p>\n<p><strong>Result.</strong> Secrets are per host by construction, and the two failure modes that would have followed from sharing them are written down where the next person edits the file. The empty entries are the part worth keeping: they say the wiring exists and the value does not, which is a different and more useful statement than a missing key.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Infrastructure",
        "Linux & Servers",
        "Reliability & Backups",
        "Security",
        "Solution Architecture",
        "Backup & Disaster Recovery",
        "Infrastructure as Code",
        "Security & Access Management",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/cut-the-visual-quality-gate-to-ten-minutes-128/",
      "url": "https://engineer.company/portfolio/cut-the-visual-quality-gate-to-ten-minutes-128/",
      "title": "Cut the site's browser‑driven quality gate from 1,636 seconds to 615 by scheduling its checks longest‑first through a worker pool bounded to four lanes, after measuring that alphabetical order cost 320 seconds against 224.",
      "summary": "The gate went from 1,636 seconds to 615, while the checks got broader rather than thinner — the serial cost went up and the wall clock came down.",
      "content_html": "<p><strong>Situation.</strong> Eleven of the site&rsquo;s checks drive a headless browser: layout at every window shape the design draws, type scale, translated‑text expansion, contrast, accessibility rules, forced colours, mascot sizing, motion, print across six paper combinations, console errors and visual regression. Run one at a time they took 1,636 seconds, a little over twenty‑seven minutes. A gate that takes twenty‑seven minutes is a gate that gets skipped, and a skipped check is indistinguishable from a passing one.</p>\n<p><strong>Task.</strong> The wall clock had to come down far enough that running them was the default rather than a decision, without weakening any of them.</p>\n<p><strong>Action.</strong> The work was measurement first. Each check was timed individually on a twelve‑core machine: layout at 405 seconds, type at 301, translation at 282, contrast at 189, accessibility at 178, and so on down to seventeen. Two findings shaped the answer. Running all of them at once was slower than running four at a time — 265 seconds against 224 — because each check is itself a browser doing parallel work, and oversubscribing the machine costs more than the concurrency wins. And ordering by longest processing time first beat alphabetical order by nearly a third, 224 seconds against 320, which is the classic scheduling result and shows up here because the checks vary by a factor of twenty in cost. So the runner is a bounded worker pool, sized from the core count with a floor of two and a ceiling of four, fed longest‑first. Alongside it the checks were widened rather than narrowed: they now share one viewport table of twenty‑two window shapes, derived from every media query the stylesheet actually contains, which took the layout check alone from 95 seconds to 405.</p>\n<p><strong>Result.</strong> The gate went from 1,636 seconds to 615, while the checks got broader rather than thinner — the serial cost went up and the wall clock came down. The measurement is the part worth keeping: two reasonable‑sounding choices, running everything at once and running things in the order they were written, were each measurably worse than the alternative.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Frontend Engineering",
        "Performance Tuning",
        "Python",
        "Testing & QA",
        "DevOps & CI/CD Automation",
        "Frontend Development",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/replaced-27-font-sizes-with-a-six-step-scale-129/",
      "url": "https://engineer.company/portfolio/replaced-27-font-sizes-with-a-six-step-scale-129/",
      "title": "Replaced 27 rendered font sizes whose nearest neighbours were 0.6% apart with a six‑step Major Third scale, and wrote the linter that fails the twenty‑eighth.",
      "summary": "Six sizes where there were twenty-seven, with a stated ratio, a stated measure and a check that refuses the next unplanned value.",
      "content_html": "<p><strong>Situation.</strong> A measurement of the site&rsquo;s rendered text found twenty‑seven distinct font sizes in use. Several were separated by less than one per cent — five values between 0.8 and 0.85 of the body size were all live at once, which is a difference no reader can perceive and every future editor will add to. Two of the headings were not sized by the stylesheet at all and were falling through to the browser&rsquo;s own defaults, a ratio of 1.33 that matched nothing else on the page.</p>\n<p><strong>Task.</strong> The sizes had to become a scale with a stated ratio, and something had to prevent the twenty‑eighth.</p>\n<p><strong>Action.</strong> The scale is a Major Third, ratio 1.25, six named steps from fine print to display, each a token rather than a value. Headings are mapped explicitly onto steps instead of inheriting whatever the browser thinks, which is what fixed the two that had no size of their own. Only the bottom step is floored, so fine print stays legible on a phone without the whole scale being pinned. Logotypes are exempt by name rather than by accident. The linter is the part that makes it hold: it renders the site and fails on the twenty‑eighth distinct size. It has already earned its place twice — it caught a heading arriving at 1.17 of the body size, which is not a step of anything, and named the ratio in the failure message; and it caught inline code at 0.9, which is precisely the kind of value the scale exists to prevent. Alongside the scale went a measure of about seventy‑two characters, replacing an inherited fixed width that produced ninety‑one characters on a review page and a hundred and ten on the contact page.</p>\n<p><strong>Result.</strong> Six sizes where there were twenty‑seven, with a stated ratio, a stated measure and a check that refuses the next unplanned value. The constraint is real and occasionally inconvenient: a design that wants a size between two steps has to move to a step or argue for changing the scale, and that argument has been had and lost more than once.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Design Systems & UI",
        "Documentation",
        "Frontend Engineering",
        "Testing & QA",
        "UX / UI Design",
        "Brand, Marketing & SEO",
        "Frontend Development",
        "UI/UX Design & Design Systems"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/took-wcag-2-2-level-aa-across-the-whole-site-130/",
      "url": "https://engineer.company/portfolio/took-wcag-2-2-level-aa-across-the-whole-site-130/",
      "title": "Took WCAG 2.2 Level AA across 32 representative pages — one per template per language — in both colour themes, plus two AAA criteria, with a documented conformance record and a check defending each claim.",
      "summary": "Conformance is claimed at Level AA in three languages and two themes, with two AAA criteria beyond it, and every claim has a check behind it.",
      "content_html": "<p><strong>Situation.</strong> A consultancy that sells engineering judgement and ships an inaccessible website has a credibility problem before it has an accessibility one. The site also has an unusually wide surface for it: three languages, two colour themes, a full print stylesheet, a dark‑first palette, hand‑drawn annotations and an illustrated character — every one of which is a way to fail a criterion in one configuration while passing it in another.</p>\n<p><strong>Task.</strong> The site had to conform to WCAG 2.2 at Level AA across every page, every language and both themes, and the conformance had to be defended by checks rather than asserted in a document.</p>\n<p><strong>Action.</strong> A conformance record documents every criterion in scope, each row naming what satisfies it and what checks it. Two Level AAA criteria are taken beyond the target — visual presentation in full, and focus appearance, which is offered rather than claimed. The automation runs an accessibility rule engine with its best‑practice rules enabled rather than only the conformance ones, which is a thirty‑rule difference and mattered immediately: one of the extra rules failed the moment it was switched on, because the brand mark sat outside every landmark on every inner page. A second, slower check runs thirty‑two representative pages across two colour schemes and two widths, and what it asserts is unusual: focus is proven in pixels rather than in the document, by pressing the tab key for real and comparing screenshots, because identical pixels mean the user cannot tell where focus is regardless of what the markup says. It also walks the whole page looking for a keyboard trap, applies the specified text‑spacing overrides, doubles the root font size, and measures line length, justification, centring and paragraph spacing.</p>\n<p><strong>Result.</strong> Conformance is claimed at Level AA in three languages and two themes, with two AAA criteria beyond it, and every claim has a check behind it. What the site says about that on its own credits page is the honest part: no automated tool finds more than about a third of WCAG failures, and there has been no testing with real assistive technology — that limit is written where a reader will see it rather than left out of the claim.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Design Systems & UI",
        "Documentation",
        "Frontend Engineering",
        "Internationalization",
        "Testing & QA",
        "UX / UI Design",
        "Web Development",
        "Frontend Development",
        "Internationalization & Localization",
        "UI/UX Design & Design Systems",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/fixed-11-accessibility-defects-the-browser-called-healthy-131/",
      "url": "https://engineer.company/portfolio/fixed-11-accessibility-defects-the-browser-called-healthy-131/",
      "title": "Found and fixed 11 accessibility defects the browser reported as healthy — eight footer links still in tab order behind pointer‑events, a scroll timeline clipping the colophon off three pages, and a rule engine running 70 of its 105 rules.",
      "summary": "Eleven defects fixed and, more usefully, four rules that outlive them: a guard that tests one axis defends one axis, a rule set that does not contain the rule…",
      "content_html": "<p><strong>Situation.</strong> The site passed its automated accessibility rules. It also had eleven defects that those rules could not see, because each one was a property of how the page rendered rather than of what the markup said — the kind that a validator reports as healthy and a keyboard user hits within seconds.</p>\n<p><strong>Task.</strong> The defects had to be found, fixed, and written up as a ledger with the rule each one produced, so the class of defect closes rather than the instance.</p>\n<p><strong>Action.</strong> They fell into three groups. Sizing: a stylesheet keyword used as if it were relative pinned a whole region to sixteen pixels while the body ran up to twenty‑two; a subscript element was used to mean &ldquo;smaller&rdquo;; shrinking text lengthened a line until a title ran to ninety‑six characters; and a measured height cap was outgrown by the very text‑spacing override the site claims to support. Focus and clipping: eight footer links stayed in the tab order behind a property that removes pointer interaction and nothing else; an overflow rule created a scroll container nobody wanted; and a scroll‑driven animation, which is simply inactive on a page too short to scroll, permanently clipped the footer off three pages. And two defects in the guards themselves, which are the ones worth naming — every window shape the layout check opened was nine hundred pixels tall, so a landscape phone at 852 by 393 gave seven pixels between two items and no check had ever looked; and the rule engine had been running seventy of its hundred and five rules, so the rule that would have caught the missing landmark was not in the set.</p>\n<p><strong>Result.</strong> Eleven defects fixed and, more usefully, four rules that outlive them: a guard that tests one axis defends one axis, a rule set that does not contain the rule cannot enforce it, height is a dimension so test it, and prove visibility in pixels rather than in the document. When the layout check was reopened properly it failed a hundred and twenty‑eight times across three languages, including a phone‑sized window where the hero&rsquo;s closing line sat twenty‑one pixels under the fold.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Design Systems & UI",
        "Frontend Engineering",
        "Testing & QA",
        "UX / UI Design",
        "Web Development",
        "Frontend Development",
        "UI/UX Design & Design Systems",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/generated-321-achievement-pages-from-a-sqlite-export-132/",
      "url": "https://engineer.company/portfolio/generated-321-achievement-pages-from-a-sqlite-export-132/",
      "title": "Generated 495 achievement pages across three languages from a read‑only SQLite export, with the page address authored as data so that correcting a sentence no longer moved the page and broke the link.",
      "summary": "Four hundred and ninety-five pages generate from one source, correcting a statement costs nothing, and the addresses this site has ever published continue to…",
      "content_html": "<p><strong>Situation.</strong> The company&rsquo;s portfolio content is authored in a separate database — the same one that produces the CV — and the website has to publish it as pages, in three languages, without the two copies drifting apart. The naive approach, writing the pages by hand and keeping them in step, fails on the first correction.</p>\n<p><strong>Task.</strong> The website had to generate its content from the database as a build input, with page addresses that survive the sentences on them being rewritten.</p>\n<p><strong>Action.</strong> An exporter reads the database read‑only and writes one page per achievement per language — 495 pages — plus the taxonomy and services data the templates need. It uses only the standard library, so the site&rsquo;s build has no dependency on the generator&rsquo;s environment, and the committed output means the site builds standalone. The single most consequential decision in it concerns addresses. The site used to derive a page&rsquo;s URL from the leading words of its English statement, so correcting a sentence silently moved the page and broke every link to it — a site whose argument for itself is that it corrects things, charging itself a dead link every time it did. The address is now authored as data: one row per address per achievement, the first is current and every later one is a retired address the site emits as a redirect. Two other traps are recorded from the same work. A period comparison against a text column silently matched all thirty‑two rows because of how the database assigns type affinity across a comparison. And the language list is now the single axis the write loop, the data files and the queries all derive from, so adding a fourth language is one map entry rather than a search.</p>\n<p><strong>Result.</strong> Four hundred and ninety‑five pages generate from one source, correcting a statement costs nothing, and the addresses this site has ever published continue to answer. The exporter also owns exactly one presentation decision — how services group into themed sections — and that is deliberate: everything else it writes is the database&rsquo;s, and a group heading missing a language fails the export rather than rendering English over translated content.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Data Pipelines (ETL/ELT)",
        "Databases",
        "Frontend Engineering",
        "Internationalization",
        "Python",
        "SQL",
        "Web Development",
        "Brand, Marketing & SEO",
        "Data Pipeline Development (ETL/ELT)",
        "Internationalization & Localization",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/closed-the-colour-system-at-28-colours-133/",
      "url": "https://engineer.company/portfolio/closed-the-colour-system-at-28-colours-133/",
      "title": "Closed the colour system at 28 documented colours with a linter that fails a value painted but undocumented, documented but unpainted, mis‑measured, respelled as a literal, or within a perceptual distance of 0.02 of one already there.",
      "summary": "The palette is a closed, measured set that cannot quietly grow, and the two near-duplicate spellings that prompted it are gone.",
      "content_html": "<p><strong>Situation.</strong> Colour on a site with a dark default, a light theme, a print stylesheet, a forced‑colours mode and an illustrated brand does not stay a small set on its own. It had already started to drift in the way it always does: two spellings of the same colour sitting close enough that nobody could tell them apart, values re‑typed as literals beside the tokens that defined them, and documented colours that nothing painted.</p>\n<p><strong>Task.</strong> The palette had to become a closed set with a stated distinctness threshold, and something had to enforce the closure.</p>\n<p><strong>Action.</strong> Twenty‑eight colours, each documented with the contrast ratio it measures on the surface it appears on, and eleven brand values declared once as tokens. The threshold is numeric rather than editorial: two colours closer than a perceptual distance of 0.02 in a uniform colour space are one colour with two spellings. The linter fails five different ways — a value painted but undocumented, documented but unpainted, recorded with the wrong ratio, within the threshold of one already there, or a brand value re‑spelled as a literal. It found two immediately: a theme colour that existed in two spellings 0.018 apart, and a background colour standing in for a wall it sat 0.021 from. Transparency uses relative colour syntax rather than the mixing function, specifically because the guard cannot see through a mix and a palette guard that can be evaded is not a guard. The rule that goes with it is short: change a token rather than a rule, add a colour only when none fits, and never lower a documented ratio to make a design work.</p>\n<p><strong>Result.</strong> The palette is a closed, measured set that cannot quietly grow, and the two near‑duplicate spellings that prompted it are gone. It is a constraint that occasionally says no — a design wanting a slightly different blue has to take the one that exists or make the case for a twenty‑ninth colour, and that case has to include the ratio.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Design Systems & UI",
        "Documentation",
        "Frontend Engineering",
        "Testing & QA",
        "UX / UI Design",
        "Brand, Marketing & SEO",
        "Frontend Development",
        "UI/UX Design & Design Systems"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/found-the-light-theme-missing-an-overlay-layer-134/",
      "url": "https://engineer.company/portfolio/found-the-light-theme-missing-an-overlay-layer-134/",
      "title": "Found that the light theme had been missing a full‑page overlay layer since the day it was written, by asserting that both themes paint every layered surface with the same number of layers.",
      "summary": "A layer missing from one theme now fails a commit, and one that had been missing since it was written is painted.",
      "content_html": "<p><strong>Situation.</strong> The site ships two colour themes. The dark one is the default and gets looked at constantly; the light one is an override that appears only under a system preference, which means it is seen far less often and by nobody who is checking it. Theme parity is a class of defect where a surface is built once and restated once, and the restatement quietly drops a layer.</p>\n<p><strong>Task.</strong> Parity needed an assertion, because the only alternative is a person remembering to switch preference and look.</p>\n<p><strong>Action.</strong> The check is small and structural: for every layered surface, count the layers each theme paints, and fail when the counts differ. It does not compare appearances — themes are supposed to look different — it compares composition, which is the thing that should be identical. It found the defect on its first run. The site&rsquo;s full‑page cover is three layers in the dark theme, a facet pattern over a scrim over the wall, and the light theme restated it with two. The facet overlay had been absent from the light theme from the day it was written, and nothing had ever said so, because a page missing one of three background layers looks like a design decision rather than a bug. The fix was one rule; the write‑up afterwards recorded what the overlay costs in contrast, so the addition is measured rather than assumed to be free.</p>\n<p><strong>Result.</strong> A layer missing from one theme now fails a commit, and one that had been missing since it was written is painted. This is the shape of defect the whole checking approach is aimed at — nothing was broken, nothing errored, the page rendered correctly, and it had been wrong for months.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Design Systems & UI",
        "Frontend Engineering",
        "Testing & QA",
        "UX / UI Design",
        "Frontend Development",
        "UI/UX Design & Design Systems"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/made-reduced-motion-honest-135/",
      "url": "https://engineer.company/portfolio/made-reduced-motion-honest-135/",
      "title": "Made reduced‑motion honest after finding eleven selectors still animating under the preference, because a universal transition‑none rule loses on specificity to any rule carrying a class.",
      "summary": "The preference is honoured in fact rather than in intent, and eleven live animations that a blanket rule appeared to have covered are actually covered.",
      "content_html": "<p><strong>Situation.</strong> The site honoured the reduce‑motion preference with a single rule turning off every transition. It read as complete, it lints clean, and under an emulated reduce‑motion preference eleven selectors were still animating.</p>\n<p><strong>Task.</strong> The preference had to be honoured in fact, and the reason the obvious rule failed had to become something the next person cannot repeat.</p>\n<p><strong>Action.</strong> The cause is specificity. A universal rule scores at the bottom of the cascade and loses to any rule carrying a class, so a blanket transition‑none is beaten by every considered animation on the page — which is every animation worth turning off. The fix was structural rather than a patch: anything that moves now lives in one late stylesheet part, and a linter fails a transitioned transform anywhere else. The distinction the rule draws is deliberate and narrower than the obvious one: reduce motion, not colour. A colour fade is not motion and turning it off makes interfaces feel broken to people who did not ask for that, so a transition listing colour properties passes and a transition on movement does not. Motion is also required to be expressed as a transition rather than a keyframe animation, because the former is trivially cancellable and the latter is not. Two related traps came out of the same work and are recorded with measurements: a menu ornament was thirty‑one degrees through a sixty‑degree turn before it was half visible, and because the shape is periodic a half‑turn looks identical for a third of the cost; and five different properties each make an element the containing block for anything positioned relative to the viewport, which had silently shrunk a dismiss layer to a fraction of the screen.</p>\n<p><strong>Result.</strong> The preference is honoured in fact rather than in intent, and eleven live animations that a blanket rule appeared to have covered are actually covered. The general lesson is the one written at the top of that document: a rule that loses on specificity fails silently, and every report calls it something else.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Design Systems & UI",
        "Frontend Engineering",
        "Testing & QA",
        "UX / UI Design",
        "Web Development",
        "Frontend Development",
        "UI/UX Design & Design Systems",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-a-637-line-print-stylesheet-136/",
      "url": "https://engineer.company/portfolio/built-a-637-line-print-stylesheet-136/",
      "title": "Built a 637‑line print stylesheet against four documented rendering‑engine behaviours, including run‑in headings that printed white on white for any reader whose browser preferred dark.",
      "summary": "The site prints on unknown paper in both themes, and the four engine behaviours are written down with the defect each produced.",
      "content_html": "<p><strong>Situation.</strong> The site&rsquo;s case studies are the artefact somebody prints and takes into a meeting. That makes paper a real output rather than a courtesy, and paper is a different medium from a narrow screen — the reader&rsquo;s paper size is unknown, the reader&rsquo;s orientation is unknown, and the browser&rsquo;s print behaviour differs between engines in ways no screen preview reveals.</p>\n<p><strong>Task.</strong> The site had to print correctly on unknown paper, in both colour themes, without a separate document being maintained beside the web one.</p>\n<p><strong>Action.</strong> It is one 637‑line stylesheet with a single page rule and one print block. The page rule sets a margin and deliberately sets no paper size, so whatever the reader chooses in the dialog comes through and everything else adapts to it. The root font size is pinned in points so every relative measurement has a physical anchor, and one measure of about seventy‑two characters is applied to the four top‑level blocks — which is the sheet winning over the design, since a landscape page would otherwise run a line to about a hundred and ten characters. Four engine behaviours are documented as traps, each having caused a real defect. Viewport units resolve to the page box in one engine and to the window in another, so a page printed from a wide window has a third of every line cropped off in the second. Fixed‑position elements paint on every sheet, so the decorative ones are dropped and two of them are repurposed as a letterhead and a colophon. The palette defaults to white text because the default theme is dark, which printed the four section run‑in labels white on white — the words were simply absent. And paged media cannot split a flex or grid box, which produced a blank first sheet on an achievement page. A six‑part check covers it, testing six paper and orientation combinations, both colour‑scheme preferences, and the real page count against what the content height implies.</p>\n<p><strong>Result.</strong> The site prints on unknown paper in both themes, and the four engine behaviours are written down with the defect each produced. The known limits are recorded rather than hidden: no page numbers or running heads, one engine ignores widow and orphan control, and drop caps are not universal. A stray comment terminator once made the CSS linter swallow a whole rule and pass clean twice — only the print check noticed.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Design Systems & UI",
        "Documentation",
        "Frontend Engineering",
        "UX / UI Design",
        "Web Development",
        "Frontend Development",
        "UI/UX Design & Design Systems",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/mirrored-the-site-to-gemini-and-gopher-137/",
      "url": "https://engineer.company/portfolio/mirrored-the-site-to-gemini-and-gopher-137/",
      "title": "Mirrored the entire site as 777 Gemini documents and 777 Gopher documents off the same deployed tree, at zero bytes of change to the HTML.",
      "summary": "The site is readable over four protocols from one build, at 777 documents each and zero HTML change, and the alternate representations cost about twelve per…",
      "content_html": "<p><strong>Situation.</strong> The site is static, has no tracking and no runtime backend, and its argument is that a document does not need a megabyte of JavaScript to be read. That argument is easy to make and hard to demonstrate. Two small internet protocols demonstrate it directly, because neither of them can carry a script at all.</p>\n<p><strong>Task.</strong> The whole site had to be published over Gemini and Gopher from the same content, without a second content tree and without changing the HTML.</p>\n<p><strong>Action.</strong> Both are output formats of the same build rather than a separate pipeline. The site generator was given custom media types and output formats, and every page, section and taxonomy term gained two more representations alongside its HTML and its Markdown twin. The result is 777 Gemini documents and 777 Gopher menus produced from the same source, deployed to the same tree, and served by two small daemons on the same host. Two properties made it worth doing rather than a curiosity. Both formats are marked as non‑alternative representations, so nothing about the HTML changed — not one byte, and that was measured rather than assumed. And the Gopher format has no way to put a link inside a sentence, since a menu line is tab‑separated fields, so the prose is hard‑wrapped at sixty‑eight columns during the build; that constraint sharpened the writing in a way the HTML never demanded. A Tor onion mirror sits alongside them, and it is the only public face the platform could add without opening a firewall port, because the daemon dials out and nothing dials in.</p>\n<p><strong>Result.</strong> The site is readable over four protocols from one build, at 777 documents each and zero HTML change, and the alternate representations cost about twelve per cent of the HTML&rsquo;s own weight on disk. Only one of the four is measured for traffic, and that is stated in the platform&rsquo;s own statistics document rather than being quietly ignored.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Frontend Engineering",
        "Infrastructure",
        "Internationalization",
        "Web Development",
        "DevOps & CI/CD Automation",
        "Internationalization & Localization",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/replaced-963-structured-data-blocks-with-one-graph-138/",
      "url": "https://engineer.company/portfolio/replaced-963-structured-data-blocks-with-one-graph-138/",
      "title": "Replaced 963 anonymous structured‑data blocks that restated the company 1,671 times with one linked graph of 16 types minted from stable origin identifiers.",
      "summary": "One graph with stable identity replaces 963 anonymous restatements, and the pages carry less rather than more.",
      "content_html": "<p><strong>Situation.</strong> The site emitted structured data the way most sites do: a block per page, each one describing the organisation again from scratch. Across the site that came to 963 blocks restating the same company 1,671 times, anonymously — no stable identifier anywhere, so nothing consuming it could tell that the organisation on one page was the organisation on another.</p>\n<p><strong>Task.</strong> The structured data had to become one graph with stable identity, rather than a pile of blocks that happen to contain the same words.</p>\n<p><strong>Action.</strong> Identifiers are now minted from the site&rsquo;s origin — one for the organisation, one for the site, one for the person — and every block references them rather than restating their contents. The identifiers are origin‑wide rather than per language, because the company is the same company in Danish. Sixteen types are in play, covering the service catalogue and its offers, the reviews and the work they describe, the collections and their breadcrumbs. Two decisions kept the weight down. List pages publish their items by URL only rather than inlining them, which was measured: naming them on the portfolio index would have meant 107 full statements and a 52 per cent increase in the compressed page. And the check that guards it does not merely validate syntax — it asserts that every block parses, that every identifier resolves, and that every URL the graph names was actually built, so a graph pointing at a page that does not exist fails the gate.</p>\n<p><strong>Result.</strong> One graph with stable identity replaces 963 anonymous restatements, and the pages carry less rather than more. The measurement that started the work is worth keeping in mind: the site had been emitting the same organisation description 1,671 times without anything being able to join them up.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Automation & CI/CD",
        "Brand & Marketing",
        "Data Governance",
        "Frontend Engineering",
        "Web Development",
        "Brand, Marketing & SEO",
        "Data Governance & Quality",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/cut-the-stylesheet-bundle-from-87kb-to-31kb-139/",
      "url": "https://engineer.company/portfolio/cut-the-stylesheet-bundle-from-87kb-to-31kb-139/",
      "title": "Cut the stylesheet bundle from 87 KB to 31 KB by taking a base64 font out of it, and dropped 756 KB across 22 files that were published on every deploy and referenced by nothing.",
      "summary": "The bundle is 31 KB instead of 87, 756 KB of dead assets stopped being deployed, and each decision has a measurement attached — including the one that went the…",
      "content_html": "<p><strong>Situation.</strong> The site&rsquo;s stylesheet bundle was 87 KB, which for a document site with no framework is most of a page weight spent before any content arrives. The site also published a directory of icons on every deploy, generated once and referenced by nothing since.</p>\n<p><strong>Task.</strong> The weight had to come down without the design changing, and it had to come down for a reason that could be pointed at rather than by general tidying.</p>\n<p><strong>Action.</strong> Measurement first. Two thirds of the largest stylesheet was one base64‑encoded font — about 56 KB of text encoding about 42 KB of font — inlined for a first‑paint benefit that a preloaded external file gets anyway, and paid for on every page whether the weight was needed or not. Taking it out took that file from 84 KB to 28 and the whole bundle from 87 to 31. The three font weights are preloaded instead, and the third joined the list only when field data showed it closing the longest critical chain rather than because three sounded complete. Separately, an audit of what the deploy actually published found twenty‑one generated icons and a duplicate configuration file, 756 KB, shipped every time and referenced from nowhere; and the site&rsquo;s icon file itself came down from 145 KB to 15. A modern image format was evaluated for the brand mark, measured, and declined — it came out larger, and the selection mechanism picks by order rather than by size, so a modern browser would have taken the heavier file everywhere. Two checks now hold the line: no photograph may be displayed wider than half its source pixels, and every illustration must be published at a size its own pixels support at both standard and high density.</p>\n<p><strong>Result.</strong> The bundle is 31 KB instead of 87, 756 KB of dead assets stopped being deployed, and each decision has a measurement attached — including the one that went the other way, which is the more useful record of the two.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Design Systems & UI",
        "Frontend Engineering",
        "Performance Tuning",
        "Web Development",
        "Brand, Marketing & SEO",
        "Frontend Development",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/fixed-a-sitemap-with-one-shared-timestamp-140/",
      "url": "https://engineer.company/portfolio/fixed-a-sitemap-with-one-shared-timestamp-140/",
      "title": "Fixed a sitemap where 172 of 176 URLs shared one modification timestamp, by taking the date from git history after establishing that the export rewrites every file on every run.",
      "summary": "A re-sync after a month of content edits now touches eight files of 186 instead of all of them, and the sitemap says something true.",
      "content_html": "<p><strong>Situation.</strong> The site&rsquo;s sitemap told search engines that 172 of its 176 pages had last changed at the same instant. That is not a subtle inaccuracy — a modification date is a promise to a crawler that a page&rsquo;s content changed, and a site making that promise 172 times simultaneously is either telling the truth about a full rewrite or telling nobody anything useful.</p>\n<p><strong>Task.</strong> The dates had to describe the content rather than the file, without inventing precision the repository does not have.</p>\n<p><strong>Action.</strong> The cause was that the date came from the file&rsquo;s modification time and the content export rewrites every file on every run, so a single sync stamped the whole corpus. The fix moves the source to the version history, with a fallback chain that tries an explicit field first, then the commit history, then the file. The trap in that fix is worth recording: the literal field name has to appear in the list or the generator never reads it, so a configuration that looks like it prefers an authored date but omits the name silently ignores every authored date. Two other date decisions came out of the same work. The database now carries when each record was written and last revised, kept deliberately separate from when the work happened — those are years apart and conflating them would date a page written this year to a decade ago. And rows in that file are optional and nothing is derived, because the repository&rsquo;s own history begins after the content did: a missing date leaves the field empty rather than recording a migration, on the principle that a precise wrong number is worse than an absent one, since only the wrong one gets believed.</p>\n<p><strong>Result.</strong> A re‑sync after a month of content edits now touches eight files of 186 instead of all of them, and the sitemap says something true. The general rule went into the metadata document alongside it, because the same trap applies to every field that has a fallback chain.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Brand & Marketing",
        "Python",
        "Testing & QA",
        "Web Development",
        "Brand, Marketing & SEO",
        "DevOps & CI/CD Automation",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/brought-the-quality-gate-code-under-a-linter-143/",
      "url": "https://engineer.company/portfolio/brought-the-quality-gate-code-under-a-linter-143/",
      "title": "Brought 10,242 lines of quality‑gate JavaScript under a formatter and a linter after establishing it was the largest body of code in the repository and the only one nothing read, fixing 13 findings and suppressing none.",
      "summary": "The largest and most load-bearing code in the repository is now formatted, linted and type-checked, with thirteen findings fixed and zero suppressions.",
      "content_html": "<p><strong>Situation.</strong> The site&rsquo;s quality gate is thirty‑two scripts totalling 10,242 lines of JavaScript. It was, by a wide margin, the largest body of code in the repository, and it was the only body of code nothing read — no formatter, no linter, no type checking. The programs enforcing every rule in the project were the only programs subject to none of them.</p>\n<p><strong>Task.</strong> The checkers had to be held to the standard they exist to enforce, and the formatter had to be fitted to them rather than the other way around.</p>\n<p><strong>Action.</strong> The formatter came first, and the indentation width was the interesting decision. The repository&rsquo;s default is four spaces; at four spaces the formatter would have rewritten 2,173 lines of the largest checker. An override to two spaces for those files reduced that to 38 lines of genuine drift. The principle recorded with it is that the formatter is fitted to the code, not the code to the formatter — reformatting two thousand lines to satisfy a preference destroys the ability to read the history of the file. Then the linter, which produced thirteen real findings, all fixed and none suppressed. The Python checkers got the same treatment through a type checker configured at a middle strictness with thirty individual strict‑tier rules enabled on top, chosen by measurement: at that setting the tree is silent, and of the thirty candidates twenty‑nine were already silent and one fired — and that one was fixed rather than exempted. The type checker found two real defects the linter had passed clean, both about a value&rsquo;s shape rather than its syntax.</p>\n<p><strong>Result.</strong> The largest and most load‑bearing code in the repository is now formatted, linted and type‑checked, with thirteen findings fixed and zero suppressions. The reason it matters more than the line count suggests is stated in the plan: a defect in the site&rsquo;s stylesheet shows up as a page that looks wrong, and a defect in a checker shows up as a check that passes when it should not.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Documentation",
        "Frontend Engineering",
        "Python",
        "Testing & QA",
        "DevOps & CI/CD Automation",
        "Frontend Development",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-a-database-driven-cv-and-portfolio-generator-144/",
      "url": "https://engineer.company/portfolio/built-a-database-driven-cv-and-portfolio-generator-144/",
      "title": "Built a database‑driven CV, references, portfolio and cover‑letter generator in Python — 41 modules, 10,580 lines — rendering six output formats from one 23‑table SQLite source assembled by a 19‑step idempotent pipeline.",
      "summary": "Six formats, three languages and five themes come out of one database, and a corrected sentence is corrected once.",
      "content_html": "<p><strong>Situation.</strong> A CV, a reference sheet, a portfolio and a cover letter are the same facts arranged four ways, and keeping them as four documents means every correction is made four times and eventually is not. Three languages multiplies that by three. The failure mode is not that a document is wrong; it is that two documents disagree and nothing says which one is current.</p>\n<p><strong>Task.</strong> One source of facts had to produce every document, in every language, in every format, with the arrangement decided by code rather than by whoever last edited a file.</p>\n<p><strong>Action.</strong> The source is a SQLite database of twenty‑three tables — achievements, titles, companies, regions, categories, services, profiles, references, summaries — assembled by a nineteen‑step pipeline that runs from an empty file to a complete database in one command. The steps are ordered and each is written to be safe to repeat, so the pipeline can be run against an existing database without duplicating a row. Forty‑one Python modules totalling 10,580 lines render six output formats from it: HTML, PDF in two variants, plain text, Markdown and JSON, through six Jinja templates and eight stylesheets. Runtime dependencies were held to exactly two, a template engine and a PDF renderer, on the argument that a document generator which cannot be installed in five years has not preserved anything. Targeting is a first‑class concept rather than a manual edit: a profile selects which achievements appear and in what order, so a document aimed at one kind of reader is a query rather than a copy.</p>\n<p><strong>Result.</strong> Six formats, three languages and five themes come out of one database, and a corrected sentence is corrected once. The cost is that the system is now the only way to produce a document — there is no longer a file to open and edit in a hurry, and adding a language means adding it everywhere before anything builds at all.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Backend Engineering",
        "Databases",
        "Full‑Stack Development",
        "Internationalization",
        "Python",
        "Solution Architecture",
        "SQL",
        "Backend & API Development",
        "Database Design & Modeling",
        "Full‑Stack Product Development",
        "Internationalization & Localization",
        "Platform & Solution Architecture"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/held-the-generator-to-964-tests-and-a-coverage-floor-145/",
      "url": "https://engineer.company/portfolio/held-the-generator-to-964-tests-and-a-coverage-floor-145/",
      "title": "Held the generator to 981 test cases at a 92% branch‑coverage floor with warnings treated as failures, and asserted idempotence by running the whole build pipeline twice from an empty file and requiring the second pass to change nothing.",
      "summary": "The pipeline can be re-run against a live database without fear, which is what makes incremental content work possible at all.",
      "content_html": "<p><strong>Situation.</strong> A generator that assembles a database from scratch has a particular kind of bug: it works the first time and corrupts the second. Steps that insert without checking, steps that depend on the order of a previous step&rsquo;s output, steps that are safe alone and not together. None of that shows up in a test that starts from nothing and runs once.</p>\n<p><strong>Task.</strong> The build had to be proved repeatable rather than merely working, and the test suite had to be large enough and strict enough that a regression could not pass through it quietly.</p>\n<p><strong>Action.</strong> The idempotence test is the blunt one and the most useful: build the entire database from an empty file, snapshot it, run the whole nineteen‑step pipeline again over the result, and require the second pass to change nothing. Row counts, contents and identifiers all have to match. Around it sit 383 test functions &ndash; 981 cases once the parameterised ones expand &ndash; across forty‑five files and 7,944 lines, covering the pipeline, the renderers, the content loaders, the targeting logic and the checkers. Branch coverage carries a floor of ninety‑two percent enforced in the build rather than reported in a summary, and the suite currently measures about 95 percent, so the floor has headroom without being decorative. Warnings are configured as failures, which is the setting that matters most in practice — a deprecation notice that prints for two years is a deprecation notice nobody reads, and the run that turns it into a red test is the run that gets it fixed.</p>\n<p><strong>Result.</strong> The pipeline can be re‑run against a live database without fear, which is what makes incremental content work possible at all. The floor is a floor, not a target, and it is worth saying that ninety‑two percent branch coverage still leaves branches nothing has ever taken — the number bounds the risk, it does not remove it, and two of the defects found later in the project were in code the coverage report showed as covered.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Databases",
        "DevOps",
        "Python",
        "Reliability & Backups",
        "Testing & QA",
        "Backend & API Development",
        "DevOps & CI/CD Automation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/established-pdf-ua-1-conformance-across-nine-documents-146/",
      "url": "https://engineer.company/portfolio/established-pdf-ua-1-conformance-across-nine-documents-146/",
      "title": "Established PDF/UA‑1 conformance across nine documents at 106 of 106 rules, and found the archival variant failing one rule of 146 — a near‑miss that reads as a pass to anyone not running the validator.",
      "summary": "Accessibility conformance is proven at full marks and the archival claim is stated honestly as a near-miss with the specific gap named.",
      "content_html": "<p><strong>Situation.</strong> A PDF that looks correct on screen tells you nothing about whether a screen reader can read it, whether its headings form a structure, whether its language is declared, or whether it will still open in twenty years. Accessible‑PDF and archival‑PDF conformance are both machine‑checkable standards, and a document that has never been run through the validator is a document making an unverified claim.</p>\n<p><strong>Task.</strong> The generated documents had to be measured against both standards, with the result recorded as a number rather than as an intention.</p>\n<p><strong>Action.</strong> Nine documents — the CV in its variants, the reference sheet, the portfolio and the cover letter, across languages — were run through an independent validator for the accessibility profile and the archival profile separately. The accessibility profile passes at 106 of 106 rules, which required tagged structure, declared document language, alternative text on every non‑decorative graphic, a real title in the metadata, and an explicit reading order rather than the one the layout happens to produce. The archival profile is the more interesting result: it fails exactly one rule of 146. That is the shape of result that is easy to misreport. One hundred and forty‑five passes reads like conformance in a summary, and it is not conformance; it is a document that will be rejected by a system enforcing the standard. The failing rule is recorded with what it is and why it has not been closed, rather than rounded away.</p>\n<p><strong>Result.</strong> Accessibility conformance is proven at full marks and the archival claim is stated honestly as a near‑miss with the specific gap named. The limit worth being direct about is that both numbers come from one validator; another implementation may disagree, and a rule that passes is only evidence that this checker had nothing to say about it.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Documentation",
        "Python",
        "Testing & QA",
        "UX / UI Design",
        "Web Development",
        "Technical Documentation",
        "UI/UX Design & Design Systems",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/enabled-every-python-linter-rule-as-an-error-147/",
      "url": "https://engineer.company/portfolio/enabled-every-python-linter-rule-as-an-error-147/",
      "title": "Selected every rule the Python linter has as an error, working through 1,815 findings to reach zero, with each of the few exemptions carrying a written reason and two of them backed by a checker instead of a comment.",
      "summary": "The linter runs at full strength with zero findings, and every deviation is documented at the line where it is taken.",
      "content_html": "<p><strong>Situation.</strong> Most projects pick a comfortable subset of their linter&rsquo;s rules, and the subset is chosen by whichever rules were quiet on the day it was configured. That makes the configuration a record of the code&rsquo;s existing habits rather than a standard the code is held to, and every rule left off is a class of defect nobody will ever be told about.</p>\n<p><strong>Task.</strong> The default had to be inverted — every rule the tool implements enabled as an error — and the resulting backlog worked to zero rather than negotiated down by turning rules back off.</p>\n<p><strong>Action.</strong> Selecting the complete rule set produced 1,815 findings on first run. They were worked through by category rather than by file, because the categories tell you something: unused arguments and shadowed builtins are noise, but the security category, the mutable‑default category and the exception‑handling category each pointed at real behaviour. Genuine incompatibilities exist — a formatter and a linter can disagree about the same line, and a few rules contradict the project&rsquo;s own deliberate choices — and each of the small number of exemptions carries a written reason at the point of exemption saying what the rule wanted and why this code does otherwise. Two of them go further and are backed by a check rather than a comment, so the exemption cannot quietly widen: the rule is off, and a test asserts the specific property the rule would have enforced. Type checking runs in strict mode alongside it, which is a separate and harder standard, and it is the one that caught defects the linter could not see because they are about what a value is rather than how it is written.</p>\n<p><strong>Result.</strong> The linter runs at full strength with zero findings, and every deviation is documented at the line where it is taken. The cost is real and worth naming: the strictest setting produces findings that are genuinely not worth acting on, and someone has to make that judgement 1,815 times rather than once.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Documentation",
        "Python",
        "Testing & QA",
        "Backend & API Development",
        "DevOps & CI/CD Automation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/vendored-a-qr-encoder-in-522-lines-148/",
      "url": "https://engineer.company/portfolio/vendored-a-qr-encoder-in-522-lines-148/",
      "title": "Vendored a QR encoder — Reed‑Solomon over GF(256), fixed module layout, eight mask patterns — in 522 lines rather than take a third runtime dependency, and verified it by reading the finished matrix back with an independently written decoder.",
      "summary": "The runtime dependency count stayed at two, and the encoder is the only vendored component in the project.",
      "content_html": "<p><strong>Situation.</strong> The documents needed a QR code linking to the online version. Every available library does this, and taking one would have added a third runtime dependency to a project that had deliberately held itself to two — a template engine and a PDF renderer — on the argument that a document generator should still install years from now.</p>\n<p><strong>Task.</strong> Either accept the dependency or implement the format, and the implementation had to be verified as correct rather than merely producing something square and black.</p>\n<p><strong>Action.</strong> The encoder is 522 lines and implements the parts of the specification the use actually needs: byte‑mode encoding, Reed‑Solomon error correction over the Galois field of 256 elements with the generator polynomial built at the required degree, the fixed module layout with its finder patterns, timing patterns and alignment patterns, format and version information, and all eight data mask patterns evaluated against the specification&rsquo;s four penalty rules so the lowest‑scoring mask is chosen rather than a fixed one. The verification is the part that made it defensible. Testing an encoder against its own logic proves nothing, so the finished module matrix is read back by a decoder written independently from the specification&rsquo;s reading order, and the test asserts that the decoded string equals the input. That turns &ldquo;it produces a plausible image&rdquo; into &ldquo;it produces a code that decodes to the right URL&rdquo;, and it is checked on every run rather than once by eye with a phone.</p>\n<p><strong>Result.</strong> The runtime dependency count stayed at two, and the encoder is the only vendored component in the project. It should be said plainly that this is not a general‑purpose implementation — it supports the modes and versions the documents use and nothing else, and the honest justification is the dependency budget rather than any claim that the result is better than a mature library.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Documentation",
        "Performance Tuning",
        "Python",
        "Testing & QA",
        "Backend & API Development",
        "Platform & Solution Architecture"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/wrote-tests-for-the-checkers-themselves-149/",
      "url": "https://engineer.company/portfolio/wrote-tests-for-the-checkers-themselves-149/",
      "title": "Wrote tests for the checkers themselves after establishing that a checker fed only clean input will one day report clean because it read nothing — planting a misspelling to confirm the spell‑check finds it, and taking an id range from the database rather than from a number in the test.",
      "summary": "Every checker in the project now has a test that proves it fails on bad input, and the range assertions read from the source of truth.",
      "content_html": "<p><strong>Situation.</strong> The project runs a set of custom checkers over its own content — a spell check, an identifier‑range check, a voice check, a figure‑consistency check. Each of them had run green for months. A checker that has only ever seen clean input and only ever reported clean is indistinguishable from a checker that reads nothing at all, and there was no test in the suite that could tell the two apart.</p>\n<p><strong>Task.</strong> The checkers had to be made to prove they can fail, and the fixtures they check against had to stop being hand‑maintained copies of the thing they describe.</p>\n<p><strong>Action.</strong> The pattern applied throughout is to plant the defect the checker exists to find and require the checker to find it. The spell check is fed a deliberately misspelled word and the test fails if the run comes back clean. The voice check is fed prose in the first person and must reject it. The buzzword check is fed a banned word. Each of these is a small test and each one closed a real blind spot, because two of the checkers turned out to be reading a narrower set of files than their documentation claimed and had been silently skipping content. The second change is about where a test gets its expectations: the identifier‑range check previously compared against a number written in the test file, which meant every content addition required editing a test, and an editor who updated the number without looking had disabled the check. It now derives the range from the database, so the check describes the data rather than a stale memory of it.</p>\n<p><strong>Result.</strong> Every checker in the project now has a test that proves it fails on bad input, and the range assertions read from the source of truth. The uncomfortable part is what this exposed: a green check had been meaningless in at least two places for an unknown length of time, and there is no way to find out retroactively what passed through.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Data Governance",
        "Databases",
        "Python",
        "Testing & QA",
        "Data Governance & Quality",
        "DevOps & CI/CD Automation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/found-the-commit-hooks-and-the-gate-disagreeing-150/",
      "url": "https://engineer.company/portfolio/found-the-commit-hooks-and-the-gate-disagreeing-150/",
      "title": "Found the commit hooks and the quality gate running different checks while a document promised they were the same, by comparing the two lists in a test — the five that only ever ran by hand were the ones reading the CV prose.",
      "summary": "The hook and the gate provably run the same checks, and the prose checks now run on every commit.",
      "content_html": "<p><strong>Situation.</strong> The project had a commit hook that runs checks before a commit is accepted, and a full quality gate run on demand. A document stated that the hook runs the gate, so nothing could reach history without passing everything. Both lists were maintained by hand, in two different files, and nothing compared them.</p>\n<p><strong>Task.</strong> The claim had to be turned into an assertion, which meant enumerating both sets programmatically and failing when they diverge.</p>\n<p><strong>Action.</strong> The test reads the hook configuration and the gate&rsquo;s task definitions, resolves each to the set of checks it actually invokes, and compares. They did not match. Five checks existed only in the gate and never ran on commit, and the five were not random — they were the ones that read the CV prose itself: the voice check that keeps reviews impersonal, the buzzword check, the figure‑consistency check across languages, the notation check, and the spell check over content. In other words, every check protecting code ran automatically and every check protecting the writing ran only when someone remembered. Given that the writing is the entire product, the exposure was inverted from where anyone would have guessed. The fix was to bring the five into the hook, which required making two of them fast enough to survive a pre‑commit budget, and then to keep the comparison test in place so the two lists cannot drift apart again. The document that had been describing an intention now describes something enforced.</p>\n<p><strong>Result.</strong> The hook and the gate provably run the same checks, and the prose checks now run on every commit. The trade‑off is the commit hook&rsquo;s runtime, which grew and will keep growing as content grows, and there is a point at which a slow hook gets bypassed — so this fix has a shelf life measured in how long the checks stay fast.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Documentation",
        "Python",
        "Testing & QA",
        "DevOps & CI/CD Automation",
        "Technical Documentation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/rehearsed-the-forge-side-ci-hook-151/",
      "url": "https://engineer.company/portfolio/rehearsed-the-forge-side-ci-hook-151/",
      "title": "Rehearsed the forge‑side CI hook and found two defects unreachable by reading the file: a fallback that put an unresolvable argument on the hook's input, and git's own environment variable following the gate into the checkout and turning 19 tests red.",
      "summary": "The hook works on both paths and both defects were found before any real push met them. The limit is that a rehearsal is still a simulation: it proves the hook…",
      "content_html": "<p><strong>Situation.</strong> The self‑hosted git forge accepts pushes and can run a hook on the server side to reject work that does not pass the gate. A server‑side hook is the one piece of automation that cannot be tested by running it locally: it executes in a bare repository, with no working tree, under an environment the forge sets, reading the pushed references from its standard input. Reading the script and concluding it is correct is a guess.</p>\n<p><strong>Task.</strong> The hook had to be rehearsed against a real bare repository and a real push before being trusted, rather than deployed and discovered.</p>\n<p><strong>Action.</strong> The rehearsal creates a bare repository, installs the hook, clones it, commits, pushes, and asserts on both the accept and the reject path. It found two defects, neither of which was visible in the file. The first was a fallback in the argument handling: when the hook could not determine a reference it substituted a placeholder that was not a resolvable object, and because the value arrives on the hook&rsquo;s standard input rather than as a parameter, the failure surfaced as an unrelated error much further down. The second is the one worth remembering. Git exports an environment variable naming the repository directory when it invokes a hook, and that variable is inherited by everything the hook runs. The gate checks out the pushed revision and runs the test suite inside it, and the test suite inherited the pointer to the bare repository — so nineteen tests that touch git resolved against the wrong repository and went red, on code that was correct. The environment has to be cleared at the boundary, and that boundary is invisible unless the thing is actually run.</p>\n<p><strong>Result.</strong> The hook works on both paths and both defects were found before any real push met them. The limit is that a rehearsal is still a simulation: it proves the hook survives one push shape, and the forge in production sees pushes this rehearsal does not construct, including forced updates and branch deletions.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Infrastructure",
        "Linux & Servers",
        "Python",
        "Testing & QA",
        "DevOps & CI/CD Automation",
        "Infrastructure as Code",
        "System Administration"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/moved-every-user-facing-string-into-a-content-tree-152/",
      "url": "https://engineer.company/portfolio/moved-every-user-facing-string-into-a-content-tree-152/",
      "title": "Moved every user‑facing string out of Python into a content tree of 874 files across three languages, after finding dead translations nobody could see were dead and a check silently grading a third of the achievements.",
      "summary": "The prose is now editable without touching code and comparable across languages by counting. The cost is that adding a language is now unambiguous rather than…",
      "content_html": "<p><strong>Situation.</strong> Every user‑facing string in the generator — every achievement, every review, every section heading, every summary, in three languages — lived inside Python source. That makes editing a sentence a code change, makes reviewing a translation a diff against source, and makes it impossible to tell at a glance whether a language is complete. It also hides the failure mode that matters: a translation can be present, wrong, and unreachable at the same time.</p>\n<p><strong>Task.</strong> The strings had to move out of code into a content tree that can be counted, compared across languages, and checked without running the renderer.</p>\n<p><strong>Action.</strong> The result is 874 files under a content directory, organised by kind and then by language: achievements, reviews, summaries, titles, companies, regions, categories, services, profiles, references and cover‑letter fragments. Tab‑separated files where the unit is a line with an identifier, individual files where the unit is a paragraph. The loader reads them at build time and the database is assembled from them, so the tree is the source and the database is derived. Two findings came directly out of being able to count. Some translated strings no longer had any English counterpart — dead entries that nothing rendered and nobody could have noticed while they were embedded in code, because an unreferenced dictionary key looks exactly like a referenced one. And a content check that was supposed to grade every achievement was reading only the subset it could resolve, grading thirty‑seven of ninety‑three and reporting success. Making the corpus a directory made both of these visible as a mismatch in file counts.</p>\n<p><strong>Result.</strong> The prose is now editable without touching code and comparable across languages by counting. The cost is that adding a language is now unambiguous rather than gradual — the tree makes an incomplete language obvious, which is the point, but it also means partial translations cannot be shipped quietly while they are being finished.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Data Governance",
        "Documentation",
        "Internationalization",
        "Migrations & Modernization",
        "Python",
        "Backend & API Development",
        "Data Governance & Quality",
        "Database Migration & Modernization",
        "Internationalization & Localization"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-a-cross-language-figure-and-notation-check-153/",
      "url": "https://engineer.company/portfolio/built-a-cross-language-figure-and-notation-check-153/",
      "title": "Built a cross‑language content check that fails when a translation drops a figure the English states, and when a language uses notation it does not use — finding two Danish descriptions missing a metric and sixteen Ukrainian spans quoting in the English style.",
      "summary": "Eighteen real defects closed and the class closed with them, since every future translation is compared to its English source before it can be committed.",
      "content_html": "<p><strong>Situation.</strong> The most damaging kind of translation error in a CV is not an awkward phrase. It is a number that disappears. An English sentence claiming a fifty‑fold improvement, translated into a sentence that says &ldquo;significantly&rdquo;, is a claim quietly withdrawn in one market and kept in another — and no spell check, grammar check or human reading of the target language alone will ever notice, because the translated sentence is perfectly good prose.</p>\n<p><strong>Task.</strong> A check was needed that reads the languages against each other rather than each one on its own, on the two things that must survive translation: the figures, and the notation each language uses to write them.</p>\n<p><strong>Action.</strong> The figure check extracts every number, percentage, multiplier and unit from the English string and requires each one to appear in every translation of that string, with the multiplier forms mapped per language rather than matched literally — the English fifty‑times form corresponds to a specific Danish phrasing and a specific Ukrainian phrasing, and the check knows the mapping instead of demanding the digits alone. The notation check is the mirror of it: each language has conventions it must use and conventions it must not, including decimal separators, thousands grouping and quotation marks. Ukrainian uses low‑nine and high‑six quotation marks; English double quotes in a Ukrainian sentence are as wrong as a missing figure, and far easier to introduce by copying. The first full run found two Danish descriptions where a metric present in English had been dropped, and sixteen Ukrainian spans quoting in the English style.</p>\n<p><strong>Result.</strong> Eighteen real defects closed and the class closed with them, since every future translation is compared to its English source before it can be committed. What the check cannot do is judge meaning: it proves the number survived and the punctuation is native, and a translation that keeps every figure while getting the claim backwards passes it cleanly.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Data Governance",
        "Documentation",
        "Internationalization",
        "Python",
        "Testing & QA",
        "Data Governance & Quality",
        "Internationalization & Localization",
        "Technical Documentation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-a-native-macos-messaging-client-in-swift-6-154/",
      "url": "https://engineer.company/portfolio/built-a-native-macos-messaging-client-in-swift-6-154/",
      "title": "Built a native macOS messaging client in Swift 6 and SwiftUI — 11,141 lines across 53 files — over the C interface of a Rust core linked as a static archive from a pinned revision.",
      "summary": "A native client that starts as a Mac application, uses the platform's own materials and controls, and carries no embedded browser.",
      "content_html": "<p><strong>Situation.</strong> The messaging protocol had a well‑tested core written in Rust and clients on several platforms, but the desktop experience on macOS was a cross‑platform shell — it did not look like a Mac application, did not behave like one, and carried a runtime that a native application does not need. The core exposes its capability through a C interface, which means any language that can call C can build on it, and Swift can.</p>\n<p><strong>Task.</strong> A native client had to be built directly on that C interface, in the platform&rsquo;s current language and current interface framework, with the core linked in rather than shipped alongside.</p>\n<p><strong>Action.</strong> The application is 11,141 lines of Swift across fifty‑three files in the app target, built on SwiftUI with the core linked as a static archive compiled from a pinned upstream revision. Pinning the revision rather than tracking a branch is the decision that makes the build reproducible: the C interface is the contract, and a moving core changes the contract without changing a line of Swift. The layering keeps the unsafe surface small — a thin wrapper owns every pointer and every string that crosses the boundary, models above it are ordinary Swift values, and the interface layer never sees a raw pointer. Everything the C interface returns has an ownership rule, and getting one wrong produces either a leak or a crash with no compiler warning in between, so the wrapper is where the entire project&rsquo;s memory discipline lives. The build is driven by a task runner with fifty‑six targets covering the core compile, the Swift build, the test suites, the linters and the packaging.</p>\n<p><strong>Result.</strong> A native client that starts as a Mac application, uses the platform&rsquo;s own materials and controls, and carries no embedded browser. What has not been done should be stated: this is not a shipped release. There is no notarised distribution, no update channel, and one language in the interface, so it is a working application rather than a product anyone else is running.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Design Systems & UI",
        "Frontend Engineering",
        "Full‑Stack Development",
        "Solution Architecture",
        "UX / UI Design",
        "Frontend Development",
        "Full‑Stack Product Development",
        "Platform & Solution Architecture",
        "UI/UX Design & Design Systems"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/stopped-an-application-filling-memory-at-41mb-a-second-155/",
      "url": "https://engineer.company/portfolio/stopped-an-application-filling-memory-at-41mb-a-second-155/",
      "title": "Stopped an application filling memory at 41 MB a second — a recorded 111 GB of compressed pages on a 36 GB machine — by bounding every event stream, subscribing by event type and putting a rate budget on logging, taking 610,996 log lines down to 1,411.",
      "summary": "Memory stays flat under sustained load and the same session that produced 610,996 log lines produces 1,411.",
      "content_html": "<p><strong>Situation.</strong> The application filled memory until the operating system killed it. The recorded incident reached 111 GB of compressed pages on a 36 GB machine, climbing at roughly 41 MB a second, and the log file for a single short session held 610,996 lines. A machine in that state is not slow, it is unusable — the kill arrives after the swap has already made everything else on the desktop stop responding.</p>\n<p><strong>Task.</strong> The growth had to be found rather than guessed at, and every unbounded path had to be given a limit, because one bounded queue next to three unbounded ones is not a fix.</p>\n<p><strong>Action.</strong> There were three multiplying causes and the multiplication is why it was so fast. The event stream from the core was consumed without any bound, so events arrived faster than the interface could apply them and the backlog was retained rather than dropped. Every subscriber received every event and filtered afterwards, so the cost of one event was multiplied by the number of listeners, and each listener&rsquo;s filtering allocated. And logging was unbudgeted, so each event produced log lines — which is the compounding term, because the volume of logging was proportional to the volume of the thing going wrong. The fix addressed all three: bounded buffers with an explicit policy for what happens when they fill, subscription by event type so a listener is only woken for events it wants, and a rate budget on logging that collapses repeats rather than writing each one. A regression test drives a high event rate and asserts the memory ceiling holds, so the bound is a property of the build rather than a comment.</p>\n<p><strong>Result.</strong> Memory stays flat under sustained load and the same session that produced 610,996 log lines produces 1,411. The honest note is that the rate budget on logging discards information: when something goes wrong quickly now, the record of it is deliberately incomplete, and that is a trade made knowingly against the alternative of a machine that stops.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Frontend Engineering",
        "Monitoring & Observability",
        "Performance Tuning",
        "Reliability & Backups",
        "Solution Architecture",
        "Testing & QA",
        "Frontend Development",
        "Platform & Solution Architecture",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/adopted-swift-6-strict-concurrency-over-a-c-event-loop-156/",
      "url": "https://engineer.company/portfolio/adopted-swift-6-strict-concurrency-over-a-c-event-loop-156/",
      "title": "Adopted Swift 6 complete strict concurrency with no actors, bridging a blocking C event loop to the main actor through one producer, one consumer and one ordering — after establishing that a task per event loses the ordering the interface depends on.",
      "summary": "The application compiles under complete strict concurrency with no suppressions, and event ordering is a structural property rather than a hope.",
      "content_html": "<p><strong>Situation.</strong> Swift 6&rsquo;s complete strict concurrency checking turns data races into compile errors instead of intermittent crashes. Adopting it against a C library is where it gets difficult: the core&rsquo;s event loop is a blocking call that must run off the main thread forever, and the values it hands back are pointers with no concurrency guarantees at all. The compiler cannot reason about any of it and will refuse everything until the boundary is described explicitly.</p>\n<p><strong>Task.</strong> Complete checking had to be enabled with no escape hatches, which meant designing the crossing from a blocking C loop to the main actor rather than annotating around it.</p>\n<p><strong>Action.</strong> The obvious approach is an actor per subsystem, and it was rejected on measurement rather than taste. Spawning a task per incoming event lets the runtime schedule them in any order, and the core&rsquo;s event stream is ordered — a message‑changed event that overtakes the message‑created event it refers to produces an interface showing an edit to something that does not exist yet. Actor reentrancy makes this worse, not better, because an actor can suspend mid‑method and process another call. What replaced it is deliberately plain: one producer thread owning the blocking loop, one consumer, one queue between them, and a single hop onto the main actor at the end. Ordering is preserved because there is exactly one path and nothing overtakes anything. The unsafe types crossing that boundary are wrapped in types whose thread‑safety is asserted at the wrapper rather than assumed, and the assertion is documented with why it holds — the pointer is owned by one thread and copied before it is handed over.</p>\n<p><strong>Result.</strong> The application compiles under complete strict concurrency with no suppressions, and event ordering is a structural property rather than a hope. The cost is that the design is less parallel than it could be: everything funnels through one consumer, and if that consumer ever becomes a bottleneck the fix will require re‑deriving which events can be reordered safely, which is exactly the analysis this avoided.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Frontend Engineering",
        "Performance Tuning",
        "Reliability & Backups",
        "Solution Architecture",
        "Testing & QA",
        "Backend & API Development",
        "Platform & Solution Architecture",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/wrote-a-parser-that-verifies-every-ffi-call-site-157/",
      "url": "https://engineer.company/portfolio/wrote-a-parser-that-verifies-every-ffi-call-site-157/",
      "title": "Wrote a parser that reads the real 7,308‑line C header and verifies every call site, every enum constant and that every pointer‑owning class is final, after a hand‑written placeholder header let calls to three removed functions compile, link and crash.",
      "summary": "The class of defect that produced the original crash cannot recur, because a stale reference is now a build failure rather than a runtime one.",
      "content_html": "<p><strong>Situation.</strong> Early in the project the C interface was represented by a hand‑written header describing the functions the application expected. That header compiled, the application linked, and calls to three functions that no longer existed in the core reached the point of being called and crashed. The compiler and the linker had both been satisfied by a description of the library rather than the library, and the gap only appeared at runtime.</p>\n<p><strong>Task.</strong> The real header — 7,308 lines and 268 declarations — had to become the authority, and every use of it in the Swift code had to be verified against it automatically rather than by review.</p>\n<p><strong>Action.</strong> The check is a parser that reads the actual upstream header and builds the set of functions, enum constants and types it declares, then reads the Swift source and resolves every call site and every constant reference against that set. A call to a function the header does not declare fails the build. A reference to an enum constant that has been renamed fails the build. The current count is 132 of the 268 declarations referenced, and knowing which 136 are unused is itself useful, because it says exactly how much of the core the client has not reached. The parser also enforces a rule the compiler cannot: every Swift class that owns a pointer into the core must be final. A non‑final pointer‑owning class can be subclassed, and a subclass that overrides deinitialisation or adds its own lifetime changes when the pointer is freed — a use‑after‑free with no unsafe keyword anywhere near it. The rule is checked by name across the whole tree.</p>\n<p><strong>Result.</strong> The class of defect that produced the original crash cannot recur, because a stale reference is now a build failure rather than a runtime one. The limitation is that the parser understands the header&rsquo;s declarations and not its semantics: it proves a function exists with a matching name, and a function whose meaning or ownership rule changed upstream while keeping its signature passes without comment.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Automation & CI/CD",
        "Backend Engineering",
        "Documentation",
        "Security",
        "Testing & QA",
        "Backend & API Development",
        "DevOps & CI/CD Automation",
        "Technical Documentation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/took-the-test-suite-under-nine-seconds-158/",
      "url": "https://engineer.company/portfolio/took-the-test-suite-under-nine-seconds-158/",
      "title": "Took the test suite from six tests over a sixty‑second limit to 135 passing in 8.9 seconds by profiling the main thread and removing the two calls it sat inside for 3,989 samples out of 4,017.",
      "summary": "The suite went from six tests over sixty seconds -- 74 seconds of wall clock -- to 135 tests passing in 8.9, which puts it inside the window where it runs on…",
      "content_html": "<p><strong>Situation.</strong> The test suite had a sixty‑second time limit and six tests were over it. A test suite that takes over a minute stops being run before every change, and a suite that is not run before every change is a report on the past. The instinct in this situation is to raise the limit, and raising the limit is how a suite gets to ten minutes.</p>\n<p><strong>Task.</strong> The time had to be found rather than budgeted for, which meant profiling the suite instead of reasoning about which tests looked expensive.</p>\n<p><strong>Action.</strong> The profile was taken on the main thread while the suite ran, and the result contradicted the guess. The main thread sat inside two calls for 3,989 samples out of 4,017 &ndash; so the slow tests were not the ones doing the most work, and almost every test passed through the same two places. The first was a fixed wait used to let asynchronous work settle before asserting — a sleep, effectively, paid by every test that touched the event path whether or not the work had already finished. It was replaced by waiting on the actual condition with a timeout, so a test that is ready in five milliseconds takes five milliseconds and only a genuinely stuck test pays the full wait. The second was per‑test setup that rebuilt an expensive fixture each time, where the fixture was read‑only and could be built once for the suite. Neither was in a test anyone would have nominated as slow; both were in the shared path, which is why the whole suite was uniformly slow rather than a few tests being outliers.</p>\n<p><strong>Result.</strong> The suite went from six tests over sixty seconds &ndash; 74 seconds of wall clock &ndash; to 135 tests passing in 8.9, which puts it inside the window where it runs on every save. The caveat is that the shared read‑only fixture is now a coupling point — a test that mutates it will produce a failure in a different test, and the suite&rsquo;s speed depends on a discipline the compiler does not enforce.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Performance Tuning",
        "Testing & QA",
        "DevOps & CI/CD Automation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/named-all-seventeen-interface-icons-for-screen-readers-159/",
      "url": "https://engineer.company/portfolio/named-all-seventeen-interface-icons-for-screen-readers-159/",
      "title": "Named every icon‑only control in the interface for screen readers after finding the send button announced as \"arrow up circle, button\", and wrote the linter that requires the label within eight lines of the icon.",
      "summary": "All seventeen controls announce their function, and an eighteenth cannot be added without one. The rule's imprecision is real and stated where it is defined:…",
      "content_html": "<p><strong>Situation.</strong> The interface used system symbols for its controls, and a symbol with no accessibility label is read out by the screen reader as its own internal name. The send button announced as &ldquo;arrow up circle, button&rdquo;. So did every other icon‑only control in the application, in its own way — seventeen of them describing their own artwork instead of their function.</p>\n<p><strong>Task.</strong> Every icon‑only control needed a label describing what it does, and the fix had to come with a check, because the next icon added would otherwise reintroduce the defect immediately.</p>\n<p><strong>Action.</strong> Each of the seventeen was given a label naming the action rather than the shape, and where the control&rsquo;s meaning depends on state the label follows the state instead of being fixed. The check is the part worth describing, because a general &ldquo;is this accessible&rdquo; rule does not exist for this. The linter looks for the construction that creates an icon‑only control and then requires an accessibility label within eight lines of it. Eight lines is a deliberately crude heuristic and it was chosen by measurement: it is long enough to span every legitimate way the codebase writes one of these controls with its modifiers, and short enough that a label attached to a different view further down does not accidentally satisfy it. A precise rule here would need to understand SwiftUI&rsquo;s modifier chains as a tree, and the cheap proximity rule catches the actual mistake — which is not a mislabelled control, it is an unlabelled one.</p>\n<p><strong>Result.</strong> All seventeen controls announce their function, and an eighteenth cannot be added without one. The rule&rsquo;s imprecision is real and stated where it is defined: it can be satisfied by a label on a neighbouring view, so it proves a label exists nearby rather than proving the label is correct, and correctness still needs someone to listen to it.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Design Systems & UI",
        "Documentation",
        "Frontend Engineering",
        "Testing & QA",
        "UX / UI Design",
        "Frontend Development",
        "UI/UX Design & Design Systems"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-a-48-token-design-system-160/",
      "url": "https://engineer.company/portfolio/built-a-48-token-design-system-160/",
      "title": "Built a 48‑token design system on the platform's own glass material and wrote the linter that rejects a magic number, a hardcoded font size, or an animation that ignores the reduce‑motion preference.",
      "summary": "Forty-eight tokens describe the whole interface, the appearance follows the system, and none of the three erosions can be committed.",
      "content_html": "<p><strong>Situation.</strong> An interface built by adding views accumulates values: a corner radius here, a fourteen‑point font there, a two‑tenths‑of‑a‑second animation somewhere else. Individually each is reasonable and collectively they are a design that cannot be changed, because there is no such thing as &ldquo;the corner radius&rdquo; to change — there are forty of them, slightly different, spread across fifty files.</p>\n<p><strong>Task.</strong> The visual vocabulary had to be reduced to a named set, expressed against the platform&rsquo;s own material rather than reinvented, and defended by a check so it stays reduced.</p>\n<p><strong>Action.</strong> The system is forty‑eight tokens in six groups: spacing, typography, colour, radius, elevation and motion. Colour and material are built on the platform&rsquo;s semantic colours and its glass material rather than fixed values, which is what makes the interface follow the system appearance, the accent colour and the contrast settings without any code that watches for them. Typography maps to the platform&rsquo;s text styles so it scales with the user&rsquo;s size preference instead of pinning a point value. The linter rejects three things: a numeric literal where a spacing or radius token belongs, a hardcoded font size anywhere, and an animation declared without honouring the reduce‑motion preference. The third is the one that would otherwise erode fastest, because an animation is added in a moment of polish and the preference check is the part that gets skipped — so the rule makes the animation itself impossible to write without it, rather than asking anyone to remember.</p>\n<p><strong>Result.</strong> Forty‑eight tokens describe the whole interface, the appearance follows the system, and none of the three erosions can be committed. The limit is that a token system constrains consistency and not quality — everything now matches, and matching is not the same as being well designed, which is a judgement no linter in this project makes.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Design Systems & UI",
        "Frontend Engineering",
        "Testing & QA",
        "UX / UI Design",
        "Brand, Marketing & SEO",
        "Frontend Development",
        "UI/UX Design & Design Systems"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/reached-the-unused-half-of-the-messaging-core-161/",
      "url": "https://engineer.company/portfolio/reached-the-unused-half-of-the-messaging-core-161/",
      "title": "Reached the half of the messaging core the application had never used — backup transfer, disappearing messages, message editing and resending, verified invitations, proxies and encryption policy — driving every test against the real library with no mocks.",
      "summary": "The previously unreached half of the core is driven by tests against the real library, and the wrapper carries the highest floor in the project.",
      "content_html": "<p><strong>Situation.</strong> The client used roughly half of what the messaging core offers. The unused half was not obscure — backup transfer between devices, disappearing messages, message editing and resending, verified invitation links, proxy configuration and the encryption policy for a chat. Each is a feature a user would expect and each was an untested region of the C interface, which is the more dangerous fact, because an untested region of a C interface is where the ownership mistakes live.</p>\n<p><strong>Task.</strong> The unreached capability had to be driven and covered, and the tests had to run against the real library rather than a stand‑in.</p>\n<p><strong>Action.</strong> The rule adopted was no mocks for the core. A mock of a C interface encodes the developer&rsquo;s belief about what the library does, and every defect worth finding here is a place where that belief is wrong — so a passing mock‑based test is evidence about the mock. Instead the tests create real accounts in temporary directories, drive the real library, and assert on what it actually returns, with each suite cleaning up its own state. That is what made the coverage meaningful: exercising backup transfer meant handling a real transfer&rsquo;s state machine and its failure paths, and exercising verified invitations meant constructing the real link format and having the library parse it. Coverage floors were set per layer rather than as one number, at eighty‑four percent for the core wrapper, seventy‑five for utilities and sixty‑five for models, on the reasoning that the layer touching raw pointers should be held highest and a model that is mostly stored properties should not be padded with tests to hit an average.</p>\n<p><strong>Result.</strong> The previously unreached half of the core is driven by tests against the real library, and the wrapper carries the highest floor in the project. The cost is speed and determinism: real‑library tests are slower than mocks and they can fail for environmental reasons, which is the price of them being able to fail for real ones.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Automation & CI/CD",
        "Backend Engineering",
        "Reliability & Backups",
        "Security",
        "Testing & QA",
        "Backend & API Development",
        "DevOps & CI/CD Automation",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/rewrote-the-error-messages-against-a-tone-standard-162/",
      "url": "https://engineer.company/portfolio/rewrote-the-error-messages-against-a-tone-standard-162/",
      "title": "Rewrote the application's error messages against a written tone standard after a refused sign‑in blamed the user for mistyping when the provider actually required an app‑specific password, and covered it with a test that names the provider.",
      "summary": "The error surface follows a stated standard and the case that prompted it is covered by a test that fails on the old text.",
      "content_html": "<p><strong>Situation.</strong> A sign‑in against a major mail provider failed, and the application told the user their password was incorrect. It was not incorrect. That provider requires an application‑specific password for third‑party clients and rejects the account password regardless of how carefully it is typed. The message sent the user to retype something that could never work, and the actual instruction — go and generate a different kind of password — appeared nowhere.</p>\n<p><strong>Task.</strong> The error messages had to be rewritten against a written standard rather than patched one at a time, since this one was the visible instance of a habit running through all of them.</p>\n<p><strong>Action.</strong> The standard has three requirements: say what happened, never imply the user did something wrong when the cause is elsewhere, and give the next action when one exists. Applied across the error surface, most messages violated at least one — several were the underlying library&rsquo;s error string passed through, which describes a condition to a programmer rather than a situation to a person. The provider case was rewritten to name the provider, state that it requires an app‑specific password for other clients, and say where to create one. The test is what stops it regressing, and it is deliberately specific: it drives a sign‑in failure against that provider and asserts the message contains the provider&rsquo;s name and the phrase describing the required credential type. A test asserting only that some error appeared would pass on the original wrong message, so the assertion is on the content, which is the only part that was ever broken.</p>\n<p><strong>Result.</strong> The error surface follows a stated standard and the case that prompted it is covered by a test that fails on the old text. What remains unresolved is scale: the standard is enforced by review and by one test on one message, and the other providers with their own particular requirements have no equivalent test, so the class is documented rather than closed.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Documentation",
        "Frontend Engineering",
        "Product & Requirements",
        "Security",
        "Testing & QA",
        "UX / UI Design",
        "Product Strategy & Requirements",
        "Technical Documentation",
        "UI/UX Design & Design Systems"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/audited-644-rust-crates-for-licence-compatibility-163/",
      "url": "https://engineer.company/portfolio/audited-644-rust-crates-for-licence-compatibility-163/",
      "title": "Audited 644 Rust crates for licence compatibility on every build, and proved the check fires by rewriting one crate's licence and by moving the pinned core revision without regenerating.",
      "summary": "The distribution position is checked on every build, and the check is known to work because it was made to fail rather than because it has never spoken.",
      "content_html": "<p><strong>Situation.</strong> Linking a Rust core into a shipped application means shipping everything that core depends on. The dependency graph is 644 crates. Each carries a licence, some carry more than one, and a single copyleft crate arriving three levels down through a routine version bump changes what the application as a whole may be distributed under — silently, in a lock file nobody reads line by line.</p>\n<p><strong>Task.</strong> The licence position had to be verified on every build rather than reviewed once, with an explicit allow list, so that a change in the graph is a build failure and not a discovery made later by someone else.</p>\n<p><strong>Action.</strong> The audit resolves the full transitive graph and checks each crate&rsquo;s licence expression against a list of terms the project accepts, evaluating the boolean expressions properly — a crate offering a choice of two licences is acceptable if either is on the list, and a crate requiring both is only acceptable if both are. Anything unmatched fails the build rather than warning, and adding a term to the allow list is a deliberate edit with a reason. A check that has never failed is indistinguishable from one that cannot fail, so it was made to fail on purpose, twice. Once by rewriting a crate&rsquo;s licence expression to something the list does not accept, which is the shape of a crate changing its terms upstream between versions &ndash; the case no human review catches, because the crate itself is not new. Once by moving the pinned core revision without regenerating the audit, which is the shape of a core bump quietly bringing a new dependency with it. Both provocations failed the build as intended, and the check was left in place rather than widened.</p>\n<p><strong>Result.</strong> The distribution position is checked on every build, and the check is known to work because it was made to fail rather than because it has never spoken. Before it existed, the bundle, the licence file and the project&rsquo;s own README all made a claim about 644 crates that nothing had verified. The limit is precise and should not be overstated: the check reads the declared licence metadata, and metadata can be wrong or incomplete. It proves nothing about a crate that misdeclares itself, and it is not legal advice.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Data Governance",
        "DevOps",
        "Documentation",
        "Security",
        "Data Governance & Quality",
        "DevOps & CI/CD Automation",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-the-visual-identity-from-two-drawings-164/",
      "url": "https://engineer.company/portfolio/built-the-visual-identity-from-two-drawings-164/",
      "title": "Built the company's icon and favicon sets from two hand‑made drawings, with every derived asset regenerated by script and a check that fails when a derived file was committed before its source.",
      "summary": "Two drawings produce every published icon, regeneration is one command, and a stale asset fails the build.",
      "content_html": "<p><strong>Situation.</strong> A small company&rsquo;s visual identity is usually bought, generated, or assembled from stock, and the result is an identity that belongs to nobody. The alternative problem is worse: hand‑made artwork that exists only as exported files, where the source drawing is lost, the exports are edited directly, and within a year the versions in use disagree with each other and no original remains to settle it.</p>\n<p><strong>Task.</strong> The identity had to be drawn rather than sourced, and the pipeline from drawing to published asset had to be reproducible, so that every file on the site is derivable rather than kept.</p>\n<p><strong>Action.</strong> Two hand‑made drawings sit at the root of the generated artwork — the company mark and the cog that became the favicon — and every icon the site publishes is produced from them. The mark is a raster drawing; the cog is a vector. From these, scripts produce every derived form: the favicon set in its required sizes, the touch icons, and the launch images at each device size. The generation is a task anyone can run, so the question &ldquo;where did this file come from&rdquo; has an answer that is a command. The staleness check is the piece that makes it hold: it compares the commit date of every derived asset against its source and fails when a derived file was committed first, which catches the exact failure this design exists to prevent — someone edits the source, forgets to regenerate, and the published site keeps showing artwork that no longer matches the original. The rule that derived files are never edited by hand is stated where the sources live.</p>\n<p><strong>Result.</strong> Two drawings produce every published icon, regeneration is one command, and a stale asset fails the build. The trade‑off is a hard dependency on the toolchain: the derived files are committed so the site builds anywhere, but changing the identity requires the generation tooling to still work, and a source format that stops being readable takes the whole identity with it.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Brand & Marketing",
        "Design Systems & UI",
        "Testing & QA",
        "UX / UI Design",
        "Web Development",
        "Brand, Marketing & SEO",
        "UI/UX Design & Design Systems",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/held-five-repositories-to-one-history-standard-165/",
      "url": "https://engineer.company/portfolio/held-five-repositories-to-one-history-standard-165/",
      "title": "Held four repositories to one history standard — conventional, emoji‑free, no attribution trailers, enforced by a commit‑message hook — alongside 46 instruction documents that govern how the work is done.",
      "summary": "Five repositories share one history format and one instruction structure, both enforced by hooks rather than by discipline.",
      "content_html": "<p><strong>Situation.</strong> Five repositories built over eighteen months by one person is the situation where process is easiest to skip, because there is nobody to coordinate with and the cost of an unreadable history is paid entirely by a future self who has not complained yet. It is also the situation where an inconsistent history is most likely, since each repository can drift into its own habits with nothing pulling them together.</p>\n<p><strong>Task.</strong> One standard for history and one standard for instructions had to apply across every repository the work is authored in, and it had to be enforced mechanically rather than remembered.</p>\n<p><strong>Action.</strong> The commit standard is a conventional prefix naming the kind of change and a scope, a subject under a fixed length, no emojis, and no attribution trailers of any sort — the last of these because a trailer crediting a tool is not a fact about the change, and history is for facts about changes. A commit‑message hook rejects anything that does not conform, in every repository that carries work, so the standard is a property of the repository rather than of whoever is committing. The distribution on the largest repository shows what the work actually was: 156 feature commits, 133 fixes, 128 documentation, 81 chores, 26 refactors, 20 style, 5 performance and 3 test. Documentation is close enough to fixes to be worth noticing, and that is a consequence of the second half of this — 46 instruction documents across the four, each covering one domain, each written as rules rather than description, all reachable from a single entry document per repository so there is one place to start. A documentation linter enforces per‑file size budgets and index membership, so the set stays navigable rather than growing into an archive.</p>\n<p><strong>Result.</strong> Five repositories share one history format and one instruction structure, both enforced by hooks rather than by discipline. What this does not do is make the history good: the format is checked and the content is not, so a conforming subject line that describes nothing passes exactly as well as one that explains the change.</p>\n",
      "date_published": "2026-09-10T14:54:29+02:00",
      "date_modified": "2026-09-10T14:54:29+02:00",
      "language": "en",
      "tags": [
        "Agile & Scrum",
        "Automation & CI/CD",
        "DevOps",
        "Documentation",
        "Project Management",
        "Technical Leadership",
        "DevOps & CI/CD Automation",
        "Project Management (Agile)",
        "Technical Documentation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/increased-brand-popularity-by-200x-through-successful-brand-1/",
      "url": "https://engineer.company/portfolio/increased-brand-popularity-by-200x-through-successful-brand-1/",
      "title": "Increased brand popularity by 200x through successful brand development across offline and online platforms.",
      "summary": "Grew brand reach roughly 200x across offline and online platforms, turning an unknown newcomer into a recognised name.",
      "content_html": "<p><strong>Situation.</strong> Engineer ApS was a brand‑new consultancy with real technical depth and almost nobody who&rsquo;d heard of it. That&rsquo;s a specific kind of frustrating: the skill is there, the work would be good, but none of it matters if the clients and partners who&rsquo;d want it don&rsquo;t know you exist. A young firm has to be seen before it can win anything.</p>\n<p><strong>Task.</strong> The task was to build a brand people would actually recognise — across both the offline and online sides — and to turn the firm&rsquo;s engineering credibility into visible market presence, rather than leaving it as a well‑kept secret.</p>\n<p><strong>Action.</strong> The brand was built deliberately and kept consistent. It started with a clear identity — a voice, a visual language, and a portfolio that led with concrete engineering outcomes instead of the vague &ldquo;we deliver value&rdquo; language everyone else uses. Then it ran across the channels that matter for this kind of firm — the website, LinkedIn, GitHub, in‑person events — with every touchpoint saying the same thing rather than each drifting off on its own. The throughline was leading with real case studies and real results, so the credibility was something you could see evidence for, not just a claim.</p>\n<p><strong>Result.</strong> Brand reach grew around 200‑fold across the offline and online platforms — an unknown newcomer turned into something people actually recognised. And it wasn&rsquo;t vanity reach; the visibility started producing a steady flow of inbound conversations and opportunities that simply hadn&rsquo;t been there before.</p>\n",
      "date_published": "2026-09-10T09:22:24+02:00",
      "date_modified": "2026-09-10T09:22:24+02:00",
      "language": "en",
      "tags": [
        "Brand & Marketing",
        "Product & Requirements",
        "Stakeholder & Reporting",
        "Brand, Marketing & SEO",
        "Product Strategy & Requirements"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/drove-brand-engagement-and-loyalty-by-100-through-2/",
      "url": "https://engineer.company/portfolio/drove-brand-engagement-and-loyalty-by-100-through-2/",
      "title": "Drove brand engagement and loyalty by 100% through market trend analysis and consumer behavior insights.",
      "summary": "Doubled brand engagement and loyalty (+100%) using market-trend analysis and consumer-behaviour insight to win a returning audience.",
      "content_html": "<p><strong>Situation.</strong> As the visibility climbed, Engineer ApS was reaching more people — but the engagement was thin. People noticed and moved on; the early interest wasn&rsquo;t turning into relationships that lasted. Reach without engagement is just noise, and the brand was making noise more than connections.</p>\n<p><strong>Task.</strong> The aim was to deepen the engagement and the loyalty, and to do it by actually looking at what the market and the audience were responding to rather than trusting gut instinct.</p>\n<p><strong>Action.</strong> So the brand became data‑informed instead of intuition‑led. That meant looking at the market trends and how the audience actually behaved across the channels — not what anyone assumed they&rsquo;d like, but what they demonstrably engaged with. It surfaced the topics and formats that drew real attention, and the content and outreach got steered toward those. The important part was tightening the loop: watch what landed, publish more of that shape next time, and let each cycle be a bit better‑aimed than the last.</p>\n<p><strong>Result.</strong> Engagement and loyalty doubled — a 100% improvement — with an audience that came back and engaged rather than glancing once and leaving, and noticeably stronger relationships with prospects and partners. The brand stopped broadcasting into the void and started building something that returned.</p>\n",
      "date_published": "2026-09-10T09:22:24+02:00",
      "date_modified": "2026-09-10T09:22:24+02:00",
      "language": "en",
      "tags": [
        "Brand & Marketing",
        "Data Analytics",
        "Product & Requirements",
        "Stakeholder & Reporting",
        "Brand, Marketing & SEO",
        "Data Analytics & BI Dashboards",
        "Product Strategy & Requirements"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/designed-a-comprehensive-infrastructure-framework-for-dtu-impacting-3/",
      "url": "https://engineer.company/portfolio/designed-a-comprehensive-infrastructure-framework-for-dtu-impacting-3/",
      "title": "Designed a comprehensive infrastructure framework for DTU impacting 14 departments, featuring flexible modules, unified data pipelines, and structured support strategies for long‑term adoption.",
      "summary": "Designed a comprehensive infrastructure framework for DTU spanning 14 departments — modular, with unified data pipelines and support strategies.",
      "content_html": "<p><strong>Situation.</strong> At a research institute comprising 14 diverse research groups, each group worked with varying data sources, formats, scales, and software tools. The technical expertise and available IT resources varied widely across the groups. While a few had managed to create and deploy custom IT solutions, many struggled with the complexity of their data infrastructure needs, diverting valuable time and focus from their core research work.</p>\n<p><strong>Task.</strong> The task was to devise a solution that would allow researchers to focus on their scientific work rather than IT challenges. The goal was to design and implement a scalable, institute‑wide data infrastructure that could accommodate the broad and differing requirements of the majority of research groups.</p>\n<p><strong>Action.</strong> A robust, forward‑thinking infrastructure plan was developed that balanced flexibility and standardization. The plan outlined key components such as modular architecture, integration pathways for diverse data sources, user‑friendly interfaces tailored to varying technical skill levels, and scalable storage and processing solutions. It also included strategies for onboarding, support, and governance to ensure adoption and sustainability.</p>\n<p><strong>Result.</strong> The resulting infrastructure plan was both technically sound and strategically aligned with the institute’s research goals. It unified the vision for data management across the organization, provided a clear path to reducing IT burden on researchers, and laid the foundation for a shared, efficient, and future‑ready research data environment. The plan was well received for its inclusivity, clarity, and adaptability, setting a strong direction for the institute&rsquo;s data infrastructure transformation.</p>\n",
      "date_published": "2026-09-10T09:22:24+02:00",
      "date_modified": "2026-09-10T09:22:24+02:00",
      "language": "en",
      "tags": [
        "Data Engineering",
        "Data Governance",
        "Data Pipelines (ETL/ELT)",
        "Documentation",
        "Infrastructure",
        "Platform Architecture",
        "Solution Architecture",
        "Stakeholder & Reporting",
        "Technical Leadership",
        "Data Governance & Quality",
        "Data Pipeline Development (ETL/ELT)",
        "Platform & Solution Architecture",
        "Technical Documentation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/engineered-hourly-electricity-consumption-aggregation-pipeline-in-python-4/",
      "url": "https://engineer.company/portfolio/engineered-hourly-electricity-consumption-aggregation-pipeline-in-python-4/",
      "title": "Engineered hourly electricity consumption aggregation pipeline in Python / SQL / Bash + Jq, achieving 180ms for 30‑day datasets across heterogeneous JSONL sources.",
      "summary": "Built a Python/SQL/Bash electricity-consumption pipeline aggregating 30-day datasets in 180ms across heterogeneous JSONL sources.",
      "content_html": "<p><strong>Situation.</strong> The company processes large volumes of heterogeneous electricity data coming from multiple data sources, each with its own reporting frequency — ranging from hourly intervals to 15‑minute intervals, and in some cases, irregular timestamps. This variability poses a challenge when trying to create a coherent, comparable dataset. To support accurate energy analytics, this data needs to be normalized into consistent hourly consumption values, aggregated per zone.</p>\n<p><strong>Task.</strong> The objective was to take raw event data from a historical dataset (in JSONL format) and convert it into hourly‑aligned electricity consumption figures, structured as one row per hour and per zone. Specifically, the task involved:</p>\n<ul>\n<li>Aligning timestamped data to strict hourly intervals,</li>\n<li>Aggregating total electricity production within each interval by summing mix values,</li>\n<li>Incorporating cross‑border exchanges by adding imports and subtracting exports,</li>\n<li>Outputting the final values in a structured, scalable format suitable for further analysis.</li>\n<li>The solution also needed to be efficient enough to scale for large time windows (30+ days) and across multiple countries/zones.</li>\n</ul>\n<p><strong>Action.</strong> To approach this task, three different variants of the solution were implemented using Python, JQ (for command‑line JSON processing), and SQL, each optimized for different contexts:</p>\n<p>Python was chosen for its flexibility, ease of data manipulation, and ability to handle in‑memory transformations efficiently. Using pure Python functions, a pipeline was built that:</p>\n<ul>\n<li>Parsed the JSONL files into structured data frames</li>\n<li>Resampled the time series data to hourly intervals</li>\n<li>Aggregated production and calculated net electricity consumption (production + imports − exports)</li>\n<li>Exported the results as CSV or loaded them into a lightweight SQLite database for inspection.</li>\n</ul>\n<p>JQ was used to create a quick, minimal‑dependency solution for command‑line environments, built as a JQ filter chain.</p>\n<p>In PostgreSQL the JSONL data was imported, normalized tables created, and a series of SQL queries written.</p>\n<p>To evaluate performance, each solution was benchmarked with both 1‑day and 30‑day historical datasets.</p>\n<p><strong>Result.</strong> The Python implementation emerged as the fastest and most scalable solution, completing:</p>\n<ul>\n<li>1‑day data processing in just 34ms</li>\n<li>30‑day dataset in 180ms</li>\n</ul>\n<p>This confirmed its suitability for handling larger timeframes while maintaining sub‑second performance. It also offered clear, maintainable code that could be easily extended to multi‑zone processing or integrated into an ETL pipeline.</p>\n<p>Overall, the multi‑tool approach demonstrated flexibility in tool usage, strong performance optimization, and robust handling of time‑alignment and aggregation challenges in real‑world electricity data pipelines.</p>\n",
      "date_published": "2026-09-10T09:22:24+02:00",
      "date_modified": "2026-09-10T09:22:24+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Data Analytics",
        "Data Engineering",
        "Data Pipelines (ETL/ELT)",
        "Databases",
        "Performance Tuning",
        "PostgreSQL",
        "Python",
        "SQL",
        "Data Analytics & BI Dashboards",
        "Data Pipeline Development (ETL/ELT)",
        "Database Performance Tuning"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/architected-developed-implemented-supported-infrastructure-data-processing-and-55/",
      "url": "https://engineer.company/portfolio/architected-developed-implemented-supported-infrastructure-data-processing-and-55/",
      "title": "Architected, developed, implemented, supported infrastructure, data processing, and the map application for 2 years non‑stop without any weekends, holidays, or vacations, 10–14 hours a day.",
      "summary": "Architected, built and ran the infrastructure, data processing and map app for two years non-stop — the dependable backbone of the product.",
      "content_html": "<p><strong>Situation.</strong> An early‑stage green‑energy startup depended on a single platform to track, monitor, and optimize renewable energy assets, yet had neither a dedicated infrastructure team nor an established engineering organization to build and operate it. The entire technical foundation — cloud infrastructure, data‑processing pipelines, and the customer‑facing GIS map application — had to be created and kept running continuously, in a market where any downtime or data gap directly eroded customer trust and revenue.</p>\n<p><strong>Task.</strong> The task was to single‑handedly architect, build, and operate the whole system end to end, spanning platform and data engineering, DevOps, and site reliability. Beyond writing the software, this meant owning production: provisioning and hardening infrastructure, designing the data‑processing layer that fed the map, and guaranteeing the application stayed available around the clock for a growing customer base — all within the constraints and relentless pace of a fast‑moving startup.</p>\n<p><strong>Action.</strong> For two years the infrastructure, data pipelines, and map application were designed, implemented, and supported without interruption — no weekends, holidays, or vacations, often ten to fourteen hours a day. A pragmatic, modular architecture was chosen to keep a one‑person operation maintainable, with automated provisioning, monitoring, and alerting so issues could be detected and resolved quickly. Data processing was continuously tuned for reliability and performance, releases were shipped incrementally, and every layer — from servers to the user‑facing map — was personally maintained and improved in response to real customer usage.</p>\n<p><strong>Result.</strong> The platform stayed continuously available and evolved from a fragile early prototype into the dependable backbone of the product, sustaining the company through its critical growth phase on the strength of a single engineer&rsquo;s ownership. This hands‑on stewardship kept infrastructure, data, and the map application reliable enough to support upsells, data licensing, and new‑customer acquisition, and demonstrated a rare degree of commitment, breadth, and end‑to‑end accountability across the full stack.</p>\n",
      "date_published": "2026-09-10T09:22:24+02:00",
      "date_modified": "2026-09-10T09:22:24+02:00",
      "language": "en",
      "tags": [
        "Data Engineering",
        "Data Pipelines (ETL/ELT)",
        "Full‑Stack Development",
        "GIS / Geospatial",
        "Infrastructure",
        "Platform Architecture",
        "Reliability & Backups",
        "Data Pipeline Development (ETL/ELT)",
        "Full‑Stack Product Development",
        "GIS & Geospatial Solutions",
        "Platform & Solution Architecture",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/optimized-budget-costs-10-times-with-zero-loss-56/",
      "url": "https://engineer.company/portfolio/optimized-budget-costs-10-times-with-zero-loss-56/",
      "title": "Optimized budget costs 10 times with zero loss in productivity for the Saudi Arabia company by reimagining the overall infrastructure, eliminating unnecessary services, and relocating from the AWS cloud.",
      "summary": "Cut infrastructure budget 10x with zero productivity loss for a Saudi company by re-imagining the stack and moving off AWS.",
      "content_html": "<p><strong>Situation.</strong> A company operating out of Saudi Arabia was carrying badly bloated infrastructure costs. Their AWS setup had been over‑provisioned and had collected services they no longer used, so the cloud bill had drifted completely out of proportion to what the business actually needed. It&rsquo;s a common story — nobody sets out to overspend, it just accretes when no one&rsquo;s watching the meter.</p>\n<p><strong>Task.</strong> The engagement was to cut the costs substantially without losing any productivity, which meant rethinking the infrastructure properly rather than trimming round the edges — edge‑trimming rarely moves a bill that&rsquo;s structurally too big.</p>\n<p><strong>Action.</strong> So the work went end to end. First came an audit of what was actually being used — which is where the duplicated and unnecessary services show themselves — and those got cut. Then whatever was left was right‑sized to match real demand instead of the worst‑case guesses the original setup had been built on. And the big move was relocating the workloads off AWS entirely, onto a more cost‑effective hosting arrangement — done carefully, in stages, so the running business never felt the migration happening underneath it.</p>\n<p><strong>Result.</strong> The budget costs fell roughly ten‑fold, with zero loss in productivity — the same capability at a fraction of what they&rsquo;d been paying. It freed up a real amount of money that had quietly been leaking into an oversized cloud bill month after month, which for the business was money straight back to the bottom line.</p>\n",
      "date_published": "2026-09-10T09:22:24+02:00",
      "date_modified": "2026-09-10T09:22:24+02:00",
      "language": "en",
      "tags": [
        "Cloud",
        "DevOps",
        "Infrastructure",
        "Migrations & Modernization",
        "Platform Architecture",
        "Solution Architecture",
        "Cloud Infrastructure & Migration",
        "DevOps & CI/CD Automation",
        "Platform & Solution Architecture"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/architected-a-layered-maritime-platform-separating-a-next-57/",
      "url": "https://engineer.company/portfolio/architected-a-layered-maritime-platform-separating-a-next-57/",
      "title": "Architected a layered maritime platform separating a Next.js PWA frontend, a Go (Huma/Fiber) API, and a PostgreSQL function layer, keeping all business logic in the database.",
      "summary": "Architected a layered maritime platform — Next.js PWA, a Go (Huma/Fiber) API and a PostgreSQL function layer holding all business logic.",
      "content_html": "<p><strong>Situation.</strong> NextMariner was going to be a maritime platform — professional networking, company reviews, sponsored education — and it was a young product, which is a polite way of saying the requirements were going to move around a lot. The thing to avoid was an architecture where changing a business rule meant touching the frontend, the API and the database all at once. On a small codebase that grows fast, that kind of coupling is what turns a two‑line change into an afternoon.</p>\n<p><strong>Task.</strong> As the architect, the call to make up front was where each kind of logic lived, with boundaries obvious enough to hold up under pressure instead of blurring the first time someone was in a hurry.</p>\n<p><strong>Action.</strong> It settled into three layers, one job each. The Next.js frontend does presentation and interactivity and nothing else. The Go API — Huma over Fiber — is deliberately thin: it routes, validates the request, applies the security filtering and orchestrates, but it holds no business logic at all. The business logic all lives in PostgreSQL functions, which assemble the full result and hand it back for the API to forward. So when a rule changes, it changes in one layer, in SQL, and the other two don&rsquo;t need to know. The boundaries were written down and then enforced in review, because a convention nobody polices stops being one.</p>\n<p><strong>Result.</strong> The thing stayed easy to hold in your head. Business logic sits in one place you can actually audit, the API is a boring adapter in the good sense, and the frontend doesn&rsquo;t care when the schema shifts underneath it. That separation is what let the product keep bolting on features without the architecture quietly rotting.</p>\n",
      "date_published": "2026-08-17T20:37:52+02:00",
      "date_modified": "2026-08-17T20:37:52+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Backend Engineering",
        "Databases",
        "Frontend Engineering",
        "Full‑Stack Development",
        "Platform Architecture",
        "PostgreSQL",
        "Solution Architecture",
        "Technical Leadership",
        "Backend & API Development",
        "Database Design & Modeling",
        "Frontend Development",
        "Full‑Stack Product Development",
        "Platform & Solution Architecture"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/adopted-uuid-v7-time-ordered-identifiers-postgresql-18-64/",
      "url": "https://engineer.company/portfolio/adopted-uuid-v7-time-ordered-identifiers-postgresql-18-64/",
      "title": "Adopted UUID v7 time‑ordered identifiers (PostgreSQL 18) as entity keys to reduce B‑tree index fragmentation and speed up queries.",
      "summary": "Adopted UUID v7 time-ordered keys (PostgreSQL 18) to cut B-tree index fragmentation and speed inserts and range queries across every table.",
      "content_html": "<p><strong>Situation.</strong> Every entity needs a unique id, and the reflex choice is a random UUID. The trouble is that random ids land all over a B‑tree index. Inserts scatter, the index fragments, and as tables grow both writes and range scans pay for it. On a platform meant to keep growing, that&rsquo;s a slow leak you&rsquo;d rather not build in.</p>\n<p><strong>Task.</strong> Keep the global uniqueness of a UUID but drop the fragmentation that comes with the randomness.</p>\n<p><strong>Action.</strong> The standard became UUID v7, which is time‑ordered — the leading bits are a timestamp, so new rows sort into the index instead of peppering it. PostgreSQL 18 has this natively as uuidv7(), wrapped in a small uuid_generate_v7() function so the same call behaves cleanly on Azure&rsquo;s Flexible Server, then made the default for entity primary keys across the schema. Nothing exotic about it; it&rsquo;s the kind of decision that&rsquo;s cheap if you make it early and a pain to retrofit later.</p>\n<p><strong>Result.</strong> Ids stayed globally unique, the index stopped fragmenting the way random UUIDs cause, and time‑ordered inserts and range queries got quicker — evenly, across every table, without anyone having to think about it again. As a bonus, every entity ends up with a key you can sort by time for free.</p>\n",
      "date_published": "2026-08-17T20:37:52+02:00",
      "date_modified": "2026-08-17T20:37:52+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Data Engineering",
        "Databases",
        "Performance Tuning",
        "PostgreSQL",
        "Database Design & Modeling",
        "Database Performance Tuning"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/migrated-the-http-api-from-fiber-to-huma-60/",
      "url": "https://engineer.company/portfolio/migrated-the-http-api-from-fiber-to-huma-60/",
      "title": "Migrated the HTTP API from Fiber to Huma v2 — 649 paths and 760 operations — reaching and holding 100% parity between the routes the server registers and the OpenAPI description it publishes.",
      "summary": "Migrated the HTTP API from Fiber to Huma v2 — 649 paths, 760 operations — at 100% parity between registered routes and the published OpenAPI description.",
      "content_html": "<p><strong>Situation.</strong> The API started life on Fiber, with request validation written out by hand, endpoint by endpoint. That&rsquo;s fine when there are a handful of endpoints. It stops being fine as the surface grows: the hand‑rolled validation turns into a maintenance tax, and small inconsistencies creep in because every endpoint&rsquo;s checks are their own little snowflake. And there was no single description of the API&rsquo;s shape anywhere.</p>\n<p><strong>Task.</strong> The goal was validation coming from the types instead of from hand‑written checks, and an actual contract describing the API — without stopping to do a big‑bang rewrite.</p>\n<p><strong>Action.</strong> The HTTP layer moved onto Huma v2, sitting on top of Fiber, so the existing runtime stayed. Each endpoint gets input and output structs, and Huma generates the request validation and response modelling from those types. An OpenAPI description comes out of it for free, which means the documentation tracks the code instead of rotting in a wiki. Everything new got written against Huma and the existing routes migrated over, with exactly two endpoints left on raw Fiber — the WebSocket ones, where you genuinely want the socket and Huma&rsquo;s request/response model doesn&rsquo;t fit. What that has grown into is 649 paths carrying 760 operations, and a check in CI that compares the routes the server actually registers against the ones the OpenAPI description advertises. Parity is 100%, and it stays there because a route that isn&rsquo;t described fails the build.</p>\n<p><strong>Result.</strong> New endpoints get validation and current documentation without anyone doing extra work for it, and a whole class of request‑handling bugs — the &ldquo;oh, we forgot to check that field here&rdquo; kind — went away. At 760 operations the description is the only practical way anyone reads the API, so the guarantee that it is complete matters more than it did at fifty. The typed contract made the API both safer to change and easier to hand to someone else, because the types tell you what an endpoint expects.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Backend Engineering",
        "Documentation",
        "Migrations & Modernization",
        "Platform Architecture",
        "Testing & QA",
        "Backend & API Development",
        "Platform & Solution Architecture",
        "Technical Documentation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-a-request-schema-validation-contract-with-automated-61/",
      "url": "https://engineer.company/portfolio/built-a-request-schema-validation-contract-with-automated-61/",
      "title": "Generated the API contract outward from the database — OpenAPI, a 44,076‑line typed TypeScript client, 61 mock handlers and the limits the UI enforces — with a guard at every hop that fails on drift.",
      "summary": "Generated the API contract from the database outward — OpenAPI, a typed TypeScript client, mock handlers and UI limits — with a guard at every hop.",
      "content_html": "<p><strong>Situation.</strong> Frontend and backend move at their own pace, and their assumptions about a request payload can drift apart without anyone noticing. The way you usually find out is a 422 in the browser — after the mismatch has already shipped, which is the most expensive moment to learn about it. Writing the two sides by hand from the same document does not fix it; it just moves the drift to whoever forgot to reread the document.</p>\n<p><strong>Task.</strong> The two sides had to be generated from one source rather than agreed between two, with every step of the generation checked instead of trusted.</p>\n<p><strong>Action.</strong> The chain starts at the database and runs outward. The schema and its functions define the shapes; the Go types define the API; Huma emits the OpenAPI description from those; a typed TypeScript client — 44,076 lines of it — is generated from that description; 61 mock handlers are generated alongside it so the frontend&rsquo;s own tests run against the real contract rather than a hand‑written fixture; and the limits the UI enforces on a form come from the same place instead of being retyped into a validator. Every hop has a guard. A contract check in CI compares what the frontend sends against what the API expects and fails the build on divergence, with a schema probe underneath it that checks the real shapes rather than a description of them. It deliberately covers where drift likes to hide: optional body fields, where &ldquo;missing&rdquo; and &ldquo;null&rdquo; get confused, and query‑parameter enums, where the two sides can quietly disagree on the allowed values.</p>\n<p><strong>Result.</strong> A field cannot change on one side only — it fails at the first hop that notices, in a build, minutes after the change. That took out a recurring and genuinely annoying class of bug, the kind invisible in code review that only shows up at runtime. The cost is a generation step in the middle of everything: regenerating is a chore, and the chain is only as trustworthy as its least‑guarded link, which is why each hop got one.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Automation & CI/CD",
        "Backend Engineering",
        "DevOps",
        "Reliability & Backups",
        "Testing & QA",
        "Backend & API Development",
        "DevOps & CI/CD Automation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-email-as-a-platform-capability-with-failover-62/",
      "url": "https://engineer.company/portfolio/built-email-as-a-platform-capability-with-failover-62/",
      "title": "Built email as a platform capability — three providers with failover, delivery webhooks, send and delivery logging, templating and campaigns — behind a startup check that will not boot without one.",
      "summary": "Built email as a platform capability: three providers with failover, delivery webhooks, send and delivery logging, templating and campaign broadcasts.",
      "content_html": "<p><strong>Situation.</strong> Email carries a lot of weight on NextMariner — verification, notifications, digests, campaigns, the things a user actually waits for. And a mail provider is exactly the kind of dependency that fails quietly: the config looks fine, the app boots, and you only find out something&rsquo;s broken when a real person never gets the message they were promised. That&rsquo;s the worst way to learn about it. One provider makes it worse, because the failure is total and someone else&rsquo;s to fix.</p>\n<p><strong>Task.</strong> Email had to be treated as a capability the platform owns rather than a client library it calls — able to survive a provider outage, able to say what happened to a given message, and loud at startup in the environments where silence is dangerous.</p>\n<p><strong>Action.</strong> Three providers sit behind one interface — SendGrid as primary, with SMTP2GO and Azure Communication Services behind it — and failover between them is automatic rather than a configuration change made under pressure. Delivery is not assumed: inbound webhooks report what each provider did with a message, and both sides are recorded, in a send log and a delivery event table, so &ldquo;did this person get their verification mail&rdquo; is a query rather than a guess. Templating keeps the message bodies out of the code, and a separate broadcast schema — 5 tables and 24 functions — carries campaigns to segments of users, which is a different problem from transactional mail and was built as one. In front of all of it, a startup check sends a real message through the stack, behind a flag: in development it logs a warning and carries on, because nobody wants their laptop refusing to start over an expired sandbox key, and in staging and production a failure is fatal and the process exits rather than deploy a build that cannot send mail. The send path itself goes through an SSRF‑protected client with a 30‑second timeout, and the async delivery path has retries and backoff so a momentary blip doesn&rsquo;t drop a message.</p>\n<p><strong>Result.</strong> A whole category of silent failure moved from &ldquo;a user notices days later&rdquo; to &ldquo;the deploy stops&rdquo;, and a provider having a bad afternoon became a degraded path rather than an outage. The cost is three integrations to keep working instead of one, and delivery logs that grow and have to be pruned — both accepted, because email is the channel the platform cannot route around.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Backend Engineering",
        "Cloud",
        "Reliability & Backups",
        "Security",
        "Backend & API Development",
        "Security & Access Management",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/designed-a-postgresql-function-first-data-layer-across-63/",
      "url": "https://engineer.company/portfolio/designed-a-postgresql-function-first-data-layer-across-63/",
      "title": "Designed a PostgreSQL function‑first data layer — 1,275 stored functions across 34 schemas — so every read and write goes through a function the database can grant, rather than through a table.",
      "summary": "Designed a PostgreSQL function-first data layer — 1,275 stored functions across 34 schemas — with the access boundary enforced by the database itself.",
      "content_html": "<p><strong>Situation.</strong> Business logic has a way of leaking. A bit ends up in the API, a bit in some SQL a handler runs inline, and before long the same rule is written two or three slightly different ways and there&rsquo;s nowhere you can point to and say &ldquo;this is what the system does with its data.&rdquo; That&rsquo;s how bugs and security holes get in.</p>\n<p><strong>Task.</strong> The goal was one home for all of it: every read and write going through the database, the API a thin adapter that doesn&rsquo;t know the business rules, and the whole thing lockable down tight.</p>\n<p><strong>Action.</strong> The data layer is function‑first. The schema is split by domain — identity, organization, review, message, notification and 29 more, 34 in all — and every operation the app can do is one of 1,275 PostgreSQL functions it calls; there&rsquo;s no direct table access from Go at all. Then the database enforces it. The role the API logs in as, mariner, has EXECUTE on the app functions and USAGE on the schemas and nothing else — no SELECT, no INSERT, no way to touch a table directly — which comes to roughly 4,000 explicit grants rather than a blanket one. The functions run SECURITY DEFINER, owned by a separate non‑login function_owner role with a pinned search_path, and the superuser account stays reserved for migrations and cron, well away from the running app.</p>\n<p><strong>Result.</strong> The logic lives in one place you can actually audit, the API stays thin and boring in the good way, and the access boundary is enforced by Postgres itself rather than by everyone remembering the rules. If the API were somehow compromised, it still couldn&rsquo;t do anything the functions don&rsquo;t allow. At 1,275 functions the discipline costs something real — a new field is a migration and a function change, not a line in a query — and that friction is the price of the boundary holding.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Data Governance",
        "Databases",
        "Platform Architecture",
        "PostgreSQL",
        "Security",
        "SQL",
        "Backend & API Development",
        "Data Governance & Quality",
        "Database Design & Modeling",
        "Platform & Solution Architecture"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-a-python-vessel-data-scraper-marinetraffic-maritime-66/",
      "url": "https://engineer.company/portfolio/built-a-python-vessel-data-scraper-marinetraffic-maritime-66/",
      "title": "Built the Python vessel‑data scrapers (MarineTraffic, Maritime‑Database) and a repeatable import that seeds the platform's reference data — 184,197 rows, including 698 companies and 56,149 vessels.",
      "summary": "Built Python vessel-data scrapers and seeded 184,197 rows of maritime reference data — 698 companies and 56,149 vessels — through a repeatable pipeline.",
      "content_html": "<p><strong>Situation.</strong> A maritime networking‑and‑reviews site is dead on arrival if it&rsquo;s empty. Nobody joins a directory with no companies in it. So before any of the social features mattered, the platform needed a real body of maritime companies and vessels already sitting there, ready to be found.</p>\n<p><strong>Task.</strong> Go and get that data — real companies and ships, at enough scale to feel populated — and get it into the database in a way that could be rerun, not a one‑off scrape nobody could reproduce.</p>\n<p><strong>Action.</strong> The scrapers are written in Python. One drives MarineTraffic with Playwright; another pulls from Maritime‑Database over async httpx; there&rsquo;s a ClassNK fetcher in there too. They write out CSVs, and an import step cleans and normalises those and loads them into the Postgres schema through a single task, so seeding the database is one command rather than an afternoon of manual work. What went in came to 184,197 rows: 698 companies, 56,149 vessels and 33,074 cities, with a later refresh replacing 74,794 vessel rows.</p>\n<p><strong>Result.</strong> The platform launched with a populated directory instead of empty tables, and a base of reference data the networking, jobs and review features could all build on top of. Because the pipeline is repeatable, refreshing or extending it later is just running it again — which is how the vessel refresh happened without anyone rebuilding the tooling. Scraped data ages, and keeping it current is an ongoing cost rather than a solved problem.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Backend Engineering",
        "Data Engineering",
        "Data Pipelines (ETL/ELT)",
        "Databases",
        "Python",
        "Backend & API Development",
        "Data Pipeline Development (ETL/ELT)"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/modeled-the-maritime-domain-professionals-companies-ships-jobs-67/",
      "url": "https://engineer.company/portfolio/modeled-the-maritime-domain-professionals-companies-ships-jobs-67/",
      "title": "Modeled the maritime domain into 348 normalized tables across 34 PostgreSQL schemas — professionals, companies, ships, jobs, reviews and the rest — with SMALLINT lookups and UUID v7 keys.",
      "summary": "Modeled the maritime domain into 348 normalized tables across 34 PostgreSQL schemas, with SMALLINT lookups, UUID v7 keys and one naming convention.",
      "content_html": "<p><strong>Situation.</strong> The heart of NextMariner is a dense maritime domain — professionals, companies, ships, jobs, reviews — and these things reference each other constantly. A professional sails on ships, works for companies, leaves reviews; a company owns ships and posts jobs. Nearly every feature is a query across that web, so how well the data is modelled decides how well most of the app performs and how sane it is to extend.</p>\n<p><strong>Task.</strong> That domain had to be modelled so it stayed fast and kept its integrity, and so that adding the next entity type didn&rsquo;t mean fighting the schema.</p>\n<p><strong>Action.</strong> It&rsquo;s laid out as normalized PostgreSQL schemas organised by domain — 348 tables across 34 of them by now. The many small, stable enumerations — statuses, types, categories — became SMALLINT lookup tables, which keeps the rows compact and the joins cheap instead of storing text codes everywhere. Entities get UUID v7 primary keys, so they&rsquo;re globally unique but still time‑ordered in the index. One naming convention runs throughout — plural table names inside singular‑named schemas — applied without exception, which as a bonus sidesteps a lot of reserved‑word collisions. And the relationships are held up by real foreign keys and constraints, so integrity is the database&rsquo;s job, not something the application has to remember to do.</p>\n<p><strong>Result.</strong> What came out is a data model that&rsquo;s consistent and quick, and predictable to work in because the same rules hold everywhere — there aren&rsquo;t special cases to memorise. Adding a feature usually means extending the schema along the existing grain rather than working against it, which is the only reason 34 schemas is a structure rather than a sprawl. It&rsquo;s the floor the rest of the platform stands on.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Data Engineering",
        "Data Governance",
        "Databases",
        "Performance Tuning",
        "Platform Architecture",
        "PostgreSQL",
        "SQL",
        "Data Governance & Quality",
        "Database Design & Modeling",
        "Platform & Solution Architecture"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/authored-578-go-task-automation-targets-70/",
      "url": "https://engineer.company/portfolio/authored-578-go-task-automation-targets-70/",
      "title": "Authored 578 go‑task automation targets spanning native, Docker and HTTPS dev modes, linting, testing, database and deployment.",
      "summary": "Authored 578 go-task automation targets across native, Docker and HTTPS modes — linting, testing, database and deployment behind one toolchain.",
      "content_html": "<p><strong>Situation.</strong> NextMariner is a polyglot monorepo — Go, TypeScript, SQL, Python, shell — and every one of those brings its own way to build, test, lint and run. Left alone, that means everyone carrying a mental cheat‑sheet of tool‑specific commands, and newcomers spending their first day just working out how to make things go.</p>\n<p><strong>Task.</strong> Give the whole project one front door: a single, consistent way to run anything, whatever language it happens to be written in.</p>\n<p><strong>Action.</strong> That&rsquo;s built out with go‑task — a Taskfile layer that has grown to 578 named targets, 50 in the root file and 528 in namespaced files beneath it. There are the development modes (native, Docker, an HTTPS variant for testing PWA and mobile), the code‑quality side (lint, format, test, fix across all the languages), database management, and the environment‑specific build and deploy tasks. There&rsquo;s even a low‑memory mode for machines that can&rsquo;t spare the RAM to build the frontend the usual way. The point was never to have a lot of tasks; it was that you never have to know the underlying command.</p>\n<p><strong>Result.</strong> Anyone can run task &ndash;list and see the whole toolchain laid out, and run any part of it the same way regardless of what&rsquo;s under the hood. Onboarding got shorter, and the small, stupid mistakes — wrong flag, wrong directory, half‑remembered command — mostly went away. The number is also a warning: 578 targets is past what anyone can hold in their head, so the naming and the namespacing are what keep it usable rather than the count being something to be proud of.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Documentation",
        "Technical Leadership",
        "Testing & QA",
        "DevOps & CI/CD Automation",
        "Technical Documentation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/configured-database-backup-retention-as-infrastructure-as-code-72/",
      "url": "https://engineer.company/portfolio/configured-database-backup-retention-as-infrastructure-as-code-72/",
      "title": "Configured database backup retention as infrastructure‑as‑code, then audited the recovery position and documented the restore procedure — naming the remaining gaps rather than leaving them to be found during an incident.",
      "summary": "Implemented database backups and an infrastructure-as-code disaster-recovery strategy for fast, reproducible recovery.",
      "content_html": "<p><strong>Situation.</strong> A product that lives on its data can&rsquo;t afford to lose any, and &ldquo;there are backups somewhere&rdquo; is a hope, not a recovery plan. The only backup worth having is one you know restores, into an environment you know you can rebuild. NextMariner had neither half written down.</p>\n<p><strong>Task.</strong> Get the recoverable position defined instead of assumed — the data, the environment around it, and an honest account of how far that actually reaches today.</p>\n<p><strong>Action.</strong> Backup retention is configured in the Bicep beside the database it protects, so point‑in‑time restore is a property of the template rather than a setting someone once clicked in a portal. The environment around it is defined as infrastructure‑as‑code too, which is the quiet half people forget: restoring a database into an environment you&rsquo;d have to rebuild by hand from memory isn&rsquo;t really recovery. Then the position was audited and written up — the restore procedure, the drill that would measure it, and the gaps still open: geo‑redundancy is switched off, and the recovery‑time objective is proposed rather than measured, because no drill has been run yet.</p>\n<p><strong>Result.</strong> Recovery stopped being a vague reassurance and became a documented position with its gaps named. That reads less impressively than &ldquo;disaster recovery: done&rdquo;, and it is worth considerably more — whoever touches it next knows what is covered, what isn&rsquo;t, and exactly which drill closes the difference. A named gap is one you can close; an unnamed one is discovered during an incident.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Cloud",
        "Databases",
        "DevOps",
        "Infrastructure",
        "PostgreSQL",
        "Reliability & Backups",
        "Backup & Disaster Recovery",
        "Database Administration (DBA)",
        "Infrastructure as Code"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-the-next-js-16-frontend-with-deliberate-73/",
      "url": "https://engineer.company/portfolio/built-the-next-js-16-frontend-with-deliberate-73/",
      "title": "Built the Next.js 16 frontend with deliberate SSR, SSG and CSR strategies, and a reusable prefetched‑server‑page factory that removes the N+1 fetch cascade from each authenticated page moved onto it.",
      "summary": "Built the Next.js 16 frontend with SSR/SSG/CSR and a server-shell prefetch-and-hydrate pattern that eliminated N+1 fetches.",
      "content_html": "<p><strong>Situation.</strong> The authenticated dashboard needed to feel quick and stay genuinely interactive, and those two goals pull against each other if you&rsquo;re naïve about it. Fetch everything on the client and the first load drags, and worse, you get the N+1 pattern where every component wakes up and fires its own request, so a single page turns into a cascade of round‑trips.</p>\n<p><strong>Task.</strong> Each part of the app needed rendering the way that actually suited it, without giving up the client‑side interactivity where it mattered.</p>\n<p><strong>Action.</strong> The Next.js 16 frontend uses the right mode per surface instead of one blanket choice. Marketing and public pages are statically generated — they don&rsquo;t change per user, so there&rsquo;s no reason to render them on every request. The genuinely interactive parts stay client‑rendered. And where the N+1 cascade actually bites — the authenticated dashboard and the big directory listings — the fix was factored rather than hand‑rolled: createPrefetchedServerPage fetches the page&rsquo;s data on the server and hands it to the client already populated, so the components come up with their data instead of each going off to ask for it. A cache‑invalidation strategy is documented per query, so data stays fresh without the app re‑fetching things it already has.</p>\n<p><strong>Result.</strong> The pages moved onto the factory come up in one round‑trip instead of a storm of them, and because it is a factory rather than a pattern people copy by hand, the next page moved over inherits the behaviour for free. The rest of the authenticated app is still client‑rendered and still queued behind it — a migration with a working mechanism and an obvious next page, rather than a finished sweep.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Frontend Engineering",
        "Full‑Stack Development",
        "Performance Tuning",
        "Platform Architecture",
        "Web Development",
        "Frontend Development",
        "Full‑Stack Product Development",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/established-an-english-danish-internationalization-system-with-linter-78/",
      "url": "https://engineer.company/portfolio/established-an-english-danish-internationalization-system-with-linter-78/",
      "title": "Established an English/Danish internationalization system carrying 12,027 messages per locale across 458 namespace files, with linter‑enforced vocabulary and a 400‑line budget per file.",
      "summary": "Established an English/Danish i18n system carrying 12,027 messages per locale across 458 files, with linter-enforced vocabulary and a size budget.",
      "content_html": "<p><strong>Situation.</strong> NextMariner runs in English and Danish, and translation systems have a way of sprawling into a mess. The files grow without limit, keys leak — present in one locale, missing in the other — and the language‑specific conventions get applied unevenly, so one locale ends up reading like it was translated by a committee that didn&rsquo;t talk to each other. For a professional product that&rsquo;s not a small blemish; it reads as carelessness.</p>\n<p><strong>Task.</strong> The internationalization setup had to scale — keeping both languages consistent and correct, and the files something a person could still maintain a year in.</p>\n<p><strong>Action.</strong> It&rsquo;s built on next‑intl with rules the tooling actually enforces. The Danish side has a locked vocabulary and style — literal æøå, the informal &ldquo;du&rdquo;, the right imperative accents, and semantic mappings for compound nouns so they&rsquo;re translated by meaning rather than word‑for‑word — and a linter holds it to that. There&rsquo;s a hard 400‑line budget per namespace file, with a split‑and‑merge approach so a big area divides into nested files that merge back cleanly instead of one file growing forever; that budget is why 12,027 messages per locale live in 458 files rather than a handful of enormous ones. A linter check compares keys across locales so nothing leaks or goes missing. And stored overrides are merged catalogue‑driven rather than with fragile top‑level fallbacks — the same deep‑merge idea used for preferences.</p>\n<p><strong>Result.</strong> Both locales stay correct and consistent at 24,054 messages between them, and the files stay maintainable as the string count climbs. Language quality became something the tooling guarantees on every commit rather than something that quietly degrades each time someone adds a string in a hurry. The budget is the load‑bearing part: without a limit per file, 458 files would have been twelve, and nobody would open them.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Documentation",
        "Frontend Engineering",
        "Full‑Stack Development",
        "Internationalization",
        "Frontend Development",
        "Internationalization & Localization"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/set-the-platform-s-founding-decisions-in-november-2025-80/",
      "url": "https://engineer.company/portfolio/set-the-platform-s-founding-decisions-in-november-2025-80/",
      "title": "Set the platform's founding decisions in the weeks after the repository opened in November 2025 — the layering, database‑first data access and the zero‑warnings bar — and they still hold nine months on.",
      "summary": "Set the platform's founding decisions in November 2025 — the layering, database-first data access and the zero-warnings bar — and they still hold.",
      "content_html": "<p><strong>Situation.</strong> The repository opened on 25 November 2025 with nothing in it. What gets decided in the first few weeks of a project like that is disproportionate: the layering, where the business logic is allowed to live, what the quality bar is. Those choices are cheap to make on day three and close to impossible to reverse by month six, by which point everything written since assumes them.</p>\n<p><strong>Task.</strong> The founding decisions had to be made deliberately and early, and made in a form that could survive being handed to other people and to a much larger codebase than existed at the time.</p>\n<p><strong>Action.</strong> Three decisions did most of the work. The stack was layered so each part has one job — a Next.js frontend, a Go API that validates and forwards, a PostgreSQL function layer that owns the business rules — rather than logic being placed wherever it was convenient that afternoon. Data access was put behind stored functions from the start, which is the choice everything else in the database record follows from; retrofitting it later would have meant rewriting every handler. And a zero‑warnings bar went in before there was much code to hold to it, because a standard introduced at commit 5,000 is a cleanup project, whereas the same standard at commit 50 is just how the repository works. None of the three were the easy option at the time, and all three cost momentum in the first month.</p>\n<p><strong>Result.</strong> Nine months and several thousand commits later, all three still hold: the layers have not blurred, no handler talks to a table directly, and the build still has no warnings in it. That is the check worth applying to a founding decision — not whether it sounded right, but whether it survived contact with the volume of work that came after, which is the point at which convenient choices usually get quietly abandoned.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Databases",
        "DevOps",
        "Frontend Engineering",
        "Full‑Stack Development",
        "Platform Architecture",
        "Security",
        "Solution Architecture",
        "Team Leadership",
        "Technical Leadership",
        "Backend & API Development",
        "Frontend Development",
        "Full‑Stack Product Development",
        "Platform & Solution Architecture",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-a-layered-automated-test-suite-across-four-layers-94/",
      "url": "https://engineer.company/portfolio/built-a-layered-automated-test-suite-across-four-layers-94/",
      "title": "Built a layered automated test suite — 981 Go tests, 543 frontend and browser specs, 494 SQL behavioural tests — with mutation testing, property‑based tests and an accessibility gate.",
      "summary": "Built a layered automated test suite — 981 Go tests, 543 frontend specs and 494 SQL behavioural tests — with mutation, property and accessibility gates.",
      "content_html": "<p><strong>Situation.</strong> A platform that keeps its business logic in the database has a testing problem most projects do not. The logic is not in the language the test framework is good at — it is in SQL, behind function boundaries, and SQL is exactly the sort of code that goes untested because testing it is awkward. Add a Go API and a Next.js frontend on top of that, and &ldquo;the important parts are covered&rdquo; quietly turns into &ldquo;the parts that were easy to cover are covered.&rdquo;</p>\n<p><strong>Task.</strong> Each layer needed its behaviour checked where that behaviour actually lives, rather than everything being checked from the outside through a browser.</p>\n<p><strong>Action.</strong> Four layers got four kinds of test. The Go API carries 981 test functions across 310 files. The frontend carries 543 Vitest and Playwright specs, including 48 end‑to‑end files and 30 browser specs. The database carries 494 behavioural test files — 116,876 lines of SQL asserting through 6,654 raised exceptions — so a stored function is tested in the database instead of through three layers of application above it. Above those sit the tests that test the tests: Stryker mutation testing deliberately breaks a line and fails when nothing notices, and fast‑check generates inputs nobody thought to write down. An axe‑core gate asserts zero WCAG 2.0 and 2.1 A and AA violations, which makes accessibility a build failure rather than an audit finding months later. Coverage thresholds only ever move upward. The whole suite runs as 10 jobs in a 612‑line workflow.</p>\n<p><strong>Result.</strong> Changing something structural stopped being frightening, which is the only thing that keeps a codebase this size from calcifying. The honest cost is time — the suite is slow, it taxes every change, and at this size it needs maintenance of its own. What it buys is the ability to keep moving quickly, and that is worth more than the minutes it takes.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Backend Engineering",
        "DevOps",
        "Frontend Engineering",
        "PostgreSQL",
        "Reliability & Backups",
        "Testing & QA",
        "Backend & API Development",
        "DevOps & CI/CD Automation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-the-repository-s-guard-engine-of-268-checks-95/",
      "url": "https://engineer.company/portfolio/built-the-repository-s-guard-engine-of-268-checks-95/",
      "title": "Built the repository's guard engine — 268 registered commit checks, 277 lint rules and 15 custom ESLint rules — plus 146 tests of the guards themselves, so the build holds the standard, not review.",
      "summary": "Built a guard engine of 268 registered commit checks, 277 lint rules and 15 custom ESLint rules, with 146 tests of the guards themselves.",
      "content_html": "<p><strong>Situation.</strong> Standards written down in a contributing guide are suggestions. Everyone agrees with them, and then it is Friday, the change is small, and the guide loses. The zero‑warnings bar was only ever going to hold if something other than goodwill was holding it — and the linters that ship with each language stop well short of the project‑specific rules that actually matter, the ones about how this codebase in particular is meant to work.</p>\n<p><strong>Task.</strong> The rules the project cared about had to be executable, so that breaking one failed a commit rather than waiting for a reviewer with the time and the memory to catch it.</p>\n<p><strong>Action.</strong> What grew out of that is a guard engine. There are 274 check scripts, 268 of them registered into the commit hooks, alongside 277 JavaScript linters and 96 shell and 17 Python validators covering the things off‑the‑shelf tooling has no opinion about — that a migration can be rolled back, that a translation key exists in both locales, that a registered route appears in the OpenAPI description, that nobody quietly added an inline suppression. Fifteen custom ESLint rules hold the house patterns in TypeScript. One rule is worth naming on its own: any commit prefixed fix: has to carry a test that fails without it, so a bug that is fixed stays fixed. And because a broken guard is worse than no guard at all — it passes everything and nobody finds out — the guards have 146 tests of their own.</p>\n<p><strong>Result.</strong> Review time moved off mechanics and onto design, because the mechanical objections were already made by a machine before the branch was pushed. The trade‑off is real and worth stating: committing is slow, and a badly written guard is genuinely infuriating to work around. Those 146 tests exist because that happened.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Documentation",
        "Technical Leadership",
        "Testing & QA",
        "DevOps & CI/CD Automation",
        "Technical Documentation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-the-payments-and-entitlements-layer-96/",
      "url": "https://engineer.company/portfolio/built-the-payments-and-entitlements-layer-96/",
      "title": "Built the payments and entitlements layer — Stripe alongside Apple and Google in‑app purchase — gating the directory, search and export through an 11‑table access model checked on the server.",
      "summary": "Built the payments and entitlements layer — Stripe with Apple and Google in-app purchase — gating directory, search and export from an access model.",
      "content_html": "<p><strong>Situation.</strong> Money is the part of a platform nobody gets to be casual about. A subscription has to survive a card expiring, a refund, a plan change, a webhook that arrives twice and a webhook that arrives out of order. The moment payment can happen on three storefronts — a card on the web, Apple in one app store, Google in the other — there are three different accounts of what somebody bought, and the product still needs one answer to one question: what is this person allowed to do right now?</p>\n<p><strong>Task.</strong> Taking money and granting permission had to be two systems rather than one, so that adding a storefront would not mean rewriting every gate in the product.</p>\n<p><strong>Action.</strong> Stripe handles cards and subscriptions through 18 Go files, and Apple and Google in‑app purchases arrive through their own receipt verification. All three converge on a payments schema of 9 tables and 34 functions — and then stop there. What the product actually asks is a separate access schema, 11 tables and 34 functions, which answers &ldquo;may this account do this?&rdquo; without knowing or caring which storefront paid for it. That answer gates the company directory, search and data export, and it is re‑checked on the server on every request, because a hidden button is a courtesy and not a control.</p>\n<p><strong>Result.</strong> Adding a storefront now touches the payments side and leaves the gates alone, and a support question about someone&rsquo;s access has one table to look at rather than three. The cost is two schemas where a smaller product would want one, plus an entitlement lookup on requests that would otherwise have been free.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Backend Engineering",
        "Databases",
        "PostgreSQL",
        "Product & Requirements",
        "Security",
        "Backend & API Development",
        "Database Design & Modeling",
        "Product Strategy & Requirements",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/moved-slow-work-onto-a-river-job-queue-97/",
      "url": "https://engineer.company/portfolio/moved-slow-work-onto-a-river-job-queue-97/",
      "title": "Moved slow work off the request path onto a River job queue — 15 worker modules, 8 scheduled tasks and 20 pg_cron jobs — so a request returns while the work behind it carries on.",
      "summary": "Moved slow work off the request path onto a River job queue: 15 worker modules, 8 scheduled tasks and 20 pg_cron maintenance jobs.",
      "content_html": "<p><strong>Situation.</strong> Some work has no business happening while a user waits. Sending mail, rebuilding a search index, generating a document, recalculating standings — do any of it inside the request and the user watches a spinner for something they never asked to see. Do it in a goroutine instead and it disappears the moment the process restarts, which it will, mid‑deploy, with no record that it was ever supposed to happen.</p>\n<p><strong>Task.</strong> Background work needed somewhere durable to live: a queue that survives a restart, retries a failure, and can be looked at when something has not happened.</p>\n<p><strong>Action.</strong> River was the choice, largely because it keeps its queue in PostgreSQL — the database is already the system of record, so a job and the rows it touches commit or roll back together, and there is no second piece of infrastructure to run and reason about. There are 15 worker modules behind it and 8 scheduled tasks. Underneath, 20 pg_cron jobs handle the maintenance the database is better placed to do itself: pruning partitions, rotating salts, refreshing aggregates. Anything slow enough to notice was moved off the request path and onto one of the two.</p>\n<p><strong>Result.</strong> Requests return quickly and the slow work still finishes, with retries and a visible history when it does not. Keeping the queue in Postgres rather than a dedicated broker is a deliberate limit: it will not scale forever, and at some volume it becomes the wrong answer. For a platform whose bottleneck is the database anyway, one less moving part was worth more than headroom that was not going to be used.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Backend Engineering",
        "Databases",
        "Performance Tuning",
        "PostgreSQL",
        "Reliability & Backups",
        "Backend & API Development",
        "Database Design & Modeling",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-first-party-error-monitoring-and-tracing-98/",
      "url": "https://engineer.company/portfolio/built-first-party-error-monitoring-and-tracing-98/",
      "title": "Built first‑party error monitoring and OpenTelemetry tracing rather than buying them — payload sanitising, spike and regression detection, symbolication and a synthetic heartbeat — behind 11 operator views.",
      "summary": "Built first-party error monitoring and OpenTelemetry tracing — sanitising, spike detection, symbolication and a heartbeat — behind 11 operator views.",
      "content_html": "<p><strong>Situation.</strong> The difference between a platform that is up and a platform that is working is whether anyone would know. An error a user hits at eleven at night, on a page nobody tests, is invisible unless something goes and collects it. The usual answer is to buy a hosted error tracker, which is a good answer — and it also means the platform&rsquo;s own errors, stack traces and user context leave for a third party.</p>\n<p><strong>Task.</strong> Errors and traces had to be collected, grouped and made actionable, without the platform&rsquo;s internals leaving the platform.</p>\n<p><strong>Action.</strong> Two pieces got built. OpenTelemetry handles tracing over OTLP, so a slow request can be followed across the frontend, the API and the database rather than guessed at. Alongside it sits an in‑house error pipeline — an errmon service and an ingest service — that sanitises payloads before storage, groups errors into recurring problems rather than a flat list, detects spikes and regressions with a cooldown so one bad deploy does not page anyone forty times, symbolicates minified frontend stack traces back into readable code, and runs a synthetic heartbeat to prove that the pipeline itself is alive. It all lands in the database as error events, error groups, an inbox, API latency and stack samples, and it surfaces through 11 operator views, including one for service level objectives.</p>\n<p><strong>Result.</strong> Errors turn into a queue that someone can work through, and a regression announces itself instead of being discovered by a user. Building rather than buying cost real time and means this is one more thing to maintain — a bought tracker would have been running the same afternoon. What it bought was that nothing sensitive leaves, and that the alerting rules fit this platform rather than a generic one.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "DevOps",
        "Infrastructure",
        "Monitoring & Observability",
        "Reliability & Backups",
        "Security",
        "Backend & API Development",
        "DevOps & CI/CD Automation",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/kept-the-schema-honest-across-1022-migrations-99/",
      "url": "https://engineer.company/portfolio/kept-the-schema-honest-across-1022-migrations-99/",
      "title": "Kept the schema honest across 1,022 migrations with a CI gate that builds the database both ways — a fresh install, and an install plus every migration — and fails when the two disagree.",
      "summary": "Kept the schema honest across 1,022 migrations with a CI gate that builds the database both ways and fails when the two disagree.",
      "content_html": "<p><strong>Situation.</strong> A schema is described twice in most projects: once by the initial setup that builds it from scratch, and once by the accumulated migrations that grew it. Both are supposed to produce the same database. Nothing checks that they do, so they drift — and the drift is invisible until a fresh environment behaves differently from production, usually at the worst moment.</p>\n<p><strong>Task.</strong> The two descriptions had to be provably identical, automatically, rather than periodically believed to be.</p>\n<p><strong>Action.</strong> The database is versioned as 1,022 migrations, numbered from 036 through 1102, and the ordering discipline around them is unglamorous and non‑negotiable. What makes it hold is a CI job that builds the database twice on every change: once from the fresh initial schema, once from the initial schema plus every migration replayed in order — and then compares the two. Not just the structure, which is the easy half, but the seeded data too, because a migration that backfills a lookup table wrongly is exactly as damaging as one that forgets a column, and only one of those shows up in a schema diff. Any disagreement fails the build with the difference printed.</p>\n<p><strong>Result.</strong> A fresh environment and a long‑lived one are the same database, and that is checked rather than assumed. The cost lands on whoever writes a migration: it has to work replayed and it has to work from cold, which is more thought than a quick ALTER usually gets. That is the point — the alternative is finding out during a restore, when the answer matters and there is no time to work it out.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Data Governance",
        "Databases",
        "DevOps",
        "Migrations & Modernization",
        "PostgreSQL",
        "Reliability & Backups",
        "Testing & QA",
        "Database Administration (DBA)",
        "Database Migration & Modernization",
        "DevOps & CI/CD Automation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-fail-closed-abuse-controls-and-rate-limiting-100/",
      "url": "https://engineer.company/portfolio/built-fail-closed-abuse-controls-and-rate-limiting-100/",
      "title": "Built fail‑closed abuse controls — 22 Redis‑backed rate limiters, Cloudflare Turnstile, request idempotency and an origin lock — so the platform sheds bots and floods instead of trusting its callers.",
      "summary": "Built fail-closed abuse controls: 22 Redis-backed rate limiters, Cloudflare Turnstile, request idempotency and an origin lock on the front door.",
      "content_html": "<p><strong>Situation.</strong> A public directory of companies and professionals is a target the day it goes live. Scrapers want the data, spam accounts want the reach, and an endpoint that costs the platform real money to serve — search, export, anything that touches an external API — is worth abusing simply because it is free to call. None of that is malice aimed at this platform in particular; it is background weather on the open internet.</p>\n<p><strong>Task.</strong> The expensive and abusable paths needed limits that hold under pressure, including the pressure of the limiter&rsquo;s own dependency being unavailable.</p>\n<p><strong>Action.</strong> There are 22 rate limiters, each constructed for the path it protects rather than one global cap, because a login attempt, a search and a bulk export are abusive at wildly different rates. State lives in Redis so a limit is shared across instances instead of being per‑process and trivially escaped. The important decision is what happens when Redis is not there: the limiters fail closed. Traffic is refused rather than waved through, which is the less convenient answer and the only defensible one. Around them sit Cloudflare Turnstile on the paths worth challenging, 351 lines of idempotency middleware so a retried write does not become two, an origin lock that refuses requests not arriving through the front door, and a bespoke challenge on the directory itself.</p>\n<p><strong>Result.</strong> Abuse gets expensive for the abuser and cheap for the platform, and an outage in the limiter&rsquo;s own store degrades into refusal rather than into an open door. Failing closed does mean a Redis problem becomes a user‑visible problem — accepted deliberately, because the alternative is a Redis problem becoming a billing one.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Backend Engineering",
        "Networking & VPN",
        "Performance Tuning",
        "Reliability & Backups",
        "Security",
        "Backend & API Development",
        "Security & Access Management",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-first-party-product-analytics-in-postgresql-101/",
      "url": "https://engineer.company/portfolio/built-first-party-product-analytics-in-postgresql-101/",
      "title": "Built first‑party product analytics in PostgreSQL — 47 functions over partitioned event tables that prune themselves — pseudonymised behind a rotating salt and gated on the visitor's consent.",
      "summary": "Built first-party product analytics in PostgreSQL — 47 functions over self-pruning partitioned tables — pseudonymised and gated on consent.",
      "content_html": "<p><strong>Situation.</strong> Product decisions need numbers, and the ordinary way to get them is to put a third‑party tag on every page and let somebody else&rsquo;s servers watch the users. That is fast, it is free at small volume, and it means visitor behaviour on a professional networking platform — who looked at which company, who searched for what — becomes an asset held by an advertising business. For a platform whose users are identifiable maritime professionals, that is a poor trade.</p>\n<p><strong>Task.</strong> The product team needed funnels, retention and event data, collected in a way the platform could stand over and a user could refuse.</p>\n<p><strong>Action.</strong> Analytics went into PostgreSQL, in a schema of 7 tables and 47 functions. Event tables are partitioned — 22 partition definitions — and prune themselves on a schedule, because the cost of first‑party analytics is not collecting the events, it is holding them forever. Identity is pseudonymised behind a salt that rotates, so a visitor cannot be followed across the rotation boundary even from inside the database; that is a deliberate ceiling on what the data can answer. The client is consent‑aware and sends nothing before consent is given, rather than sending and filtering afterwards. Conversion funnels are computed in the database, next to the data, instead of exported to somewhere else to be joined back.</p>\n<p><strong>Result.</strong> Questions about how the product is used are answered from the platform&rsquo;s own tables, with nothing leaving and no tag in the page. The limits are the honest part: rotating the salt costs long‑horizon cohort analysis, and none of this arrives with the dashboards a hosted tool gives you free. Both were accepted knowingly.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Data Analytics",
        "Data Engineering",
        "Data Governance",
        "Databases",
        "Performance Tuning",
        "PostgreSQL",
        "SQL",
        "Data Analytics & BI Dashboards",
        "Data Governance & Quality",
        "Data Pipeline Development (ETL/ELT)",
        "Database Design & Modeling"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-the-operator-back-office-for-the-platform-102/",
      "url": "https://engineer.company/portfolio/built-the-operator-back-office-for-the-platform-102/",
      "title": "Built the operator back‑office — around 40 admin routes and 128 components covering claims, moderation, feature flags, cache and diagnostics — so the platform can be run without database access.",
      "summary": "Built the operator back-office — around 40 admin routes and 128 components for claims, moderation, feature flags, cache and diagnostics.",
      "content_html": "<p><strong>Situation.</strong> Every platform quietly grows a second application, and it is usually the one nobody plans. Somebody has to approve a claim, hide a review, turn a feature on for a subset of users, clear a cache, or work out why one account is seeing something odd. When that application does not exist, the answer is an engineer with a database console — which is slow, unlogged, and one typo away from an incident.</p>\n<p><strong>Task.</strong> Running the platform day to day had to be possible without a shell, so operations belonged to whoever was on duty rather than to whoever had the credentials.</p>\n<p><strong>Action.</strong> The back‑office came to roughly 40 admin routes built from 128 components, and it covers the work as it actually arrives: adjudicating company ownership claims, moderating reviews and posts, flipping feature flags, inspecting and clearing caches, reading diagnostics, and the reporting the CEO asks for. It runs on the same design system and the same generated API client as the public product, which was the deciding choice — an internal tool built on its own stack becomes the part nobody updates, and then the part nobody trusts.</p>\n<p><strong>Result.</strong> The people who run the platform can run it, and every action goes through the same authorization and leaves the same trail as anything else. It is a large surface to keep tested and accessible for a small internal audience, and that cost is ongoing. It is still cheaper than the alternative, which is an engineer typing UPDATE against production at nine on a Sunday evening.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Frontend Engineering",
        "Full‑Stack Development",
        "Product & Requirements",
        "System Administration",
        "UX / UI Design",
        "Frontend Development",
        "Full‑Stack Product Development",
        "Product Strategy & Requirements"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-company-ownership-claims-end-to-end-103/",
      "url": "https://engineer.company/portfolio/built-company-ownership-claims-end-to-end-103/",
      "title": "Built company ownership claims end to end — a user claims a company, an administrator adjudicates, and an approval rewrites the authorization graph that decides who is allowed to edit what.",
      "summary": "Built company ownership claims end to end: a user claims, an administrator adjudicates, and approval rewrites the authorization graph.",
      "content_html": "<p><strong>Situation.</strong> A directory seeded from public sources has a structural problem: the companies in it did not put themselves there. Sooner or later somebody from one of them turns up wanting to correct their own entry — and there is no relationship between that person and that record, only an assertion that one exists. Grant it too readily and a competitor edits your page. Grant it too slowly and the directory stays wrong.</p>\n<p><strong>Task.</strong> There had to be a route from &ldquo;this is my company&rdquo; to genuine authority over the record, with a human decision in the middle and a trail behind it.</p>\n<p><strong>Action.</strong> Claims were built end to end, across 108 commits and both applications. A user submits a claim with evidence; it enters a queue in the back‑office; an administrator reviews it and approves or rejects it with a reason that goes back to the claimant. The interesting part is what approval does — it is not a flag on a row. Approval rewrites the authorization graph, so the account gains a real relationship to the organization, which is the same relationship every permission check in the platform already consults. The gate is the adjudication, not the code path, and no feature had to learn about claiming in order to respect it.</p>\n<p><strong>Result.</strong> Companies can take over and correct their own entries without anyone editing the database by hand, and every grant of authority has a named approver and a reason attached. Human review is the bottleneck by design; an automated check on a domain name would be faster and would be wrong in exactly the cases that matter most.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Frontend Engineering",
        "Full‑Stack Development",
        "Product & Requirements",
        "Security",
        "Full‑Stack Product Development",
        "Product Strategy & Requirements",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/established-a-continuous-security-programme-104/",
      "url": "https://engineer.company/portfolio/established-a-continuous-security-programme-104/",
      "title": "Established a continuous security programme — code scanning, DAST, dependency and vulnerability checks, SBOM generation, secret scanning and SHA‑pinned actions — alongside 21 written security audits.",
      "summary": "Established a continuous security programme — code scanning, DAST, dependency checks, SBOM, secret scanning and pinned actions — plus 21 audits.",
      "content_html": "<p><strong>Situation.</strong> The security work in the application — the content security policy, the least‑privilege database roles, the entitlement re‑checks — protects the platform at runtime. None of it says anything about the thing being shipped: whether a dependency picked up a known vulnerability last Tuesday, whether a credential was committed and reverted, or whether a third‑party action pinned to a tag has quietly become a different piece of code.</p>\n<p><strong>Task.</strong> Supply chain and code security had to be continuous and automated, so the state of it was a build result rather than an opinion.</p>\n<p><strong>Action.</strong> Static analysis runs through CodeQL, dynamic testing against a live instance through ZAP, and Go dependencies are checked with govulncheck. A software bill of materials is generated on every build with Anchore and Syft, so what shipped is known rather than reconstructed later. Gitleaks scans history for credentials. Every third‑party GitHub Action is pinned to a commit hash rather than a tag, which is the unglamorous control that stops a tag being moved under you. Dependabot watches 6 ecosystems. Alongside the automation sit 21 written security audits, including a threat model and an assessment against the OWASP testing guide — because scanners find the classes of problem someone has already described, and a threat model is where the ones specific to this platform get named.</p>\n<p><strong>Result.</strong> A vulnerability disclosed upstream shows up as a failing build rather than as news. The volume of findings is the real cost — a scanner that reports everything trains people to ignore it, and keeping the signal usable takes ongoing triage rather than a one‑time setup.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Documentation",
        "Reliability & Backups",
        "Security",
        "DevOps & CI/CD Automation",
        "Security & Access Management",
        "Technical Documentation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-the-loyalty-and-reputation-system-105/",
      "url": "https://engineer.company/portfolio/built-the-loyalty-and-reputation-system-105/",
      "title": "Built the loyalty and reputation system — 67 functions over a 31‑table ledger, with leagues, badges and a redemption shop — taking a row lock on the balance to close the double‑spend window.",
      "summary": "Built the loyalty and reputation system — 67 functions over a 31-table ledger, leagues, badges and a shop — with row locking on the balance.",
      "content_html": "<p><strong>Situation.</strong> Professional networking has a cold start problem: the platform is worth using once other people are already using it, and until then there is little reason to come back. The usual lever is a rewards system — points for contributing, standing that reflects reputation — which is easy to describe and treacherous to build, because the moment points can be spent they are money, and every mistake money makes is available to be made here.</p>\n<p><strong>Task.</strong> Contribution had to be measurable and rewardable, with a balance that could not be spent twice.</p>\n<p><strong>Action.</strong> The loyalty schema runs to 31 tables and 67 functions, with standing in a further 5 tables and 32 functions, and discovery and personalization schemas alongside them to decide what a given member sees. On top of the ledger sit leagues, badges and a redemption shop where a balance turns into something real. The part that took the care is the oldest bug in the book: check the balance, then spend it, and two requests arriving together both pass the check. Every mutation takes a row lock on the wallet before reading it, so the second request waits for the first to finish rather than racing it. That the wallet is in the same database as everything else is what makes it possible at all — the balance and the thing it bought commit together or not at all.</p>\n<p><strong>Result.</strong> Contribution is measured and rewarded, and a balance is arithmetic rather than an approximation. Row locking is the slower answer and was chosen anyway: contention on a wallet is a queue, and the alternative is a member spending the same points twice and someone reconciling it by hand afterwards.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Databases",
        "Performance Tuning",
        "PostgreSQL",
        "Product & Requirements",
        "SQL",
        "Backend & API Development",
        "Database Design & Modeling",
        "Product Strategy & Requirements"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-the-platform-s-social-layer-106/",
      "url": "https://engineer.company/portfolio/built-the-platform-s-social-layer-106/",
      "title": "Built the platform's social layer — posts, feed, groups, mentions, a follower graph and an occasions digest — on the same function‑first data layer as the rest of the product.",
      "summary": "Built the platform's social layer — posts, feed, groups, mentions, a follower graph and an occasions digest — on the same function-first data layer.",
      "content_html": "<p><strong>Situation.</strong> A directory of companies is a reference work. People check it and leave. What makes a professional platform worth returning to is other people being on it — which means posts, groups and a reason to come back that is not an email telling you to.</p>\n<p><strong>Task.</strong> A social layer had to be added without it becoming a second system with its own rules, its own permissions and its own way of storing things.</p>\n<p><strong>Action.</strong> Posts, a feed, groups, mentions, a follower graph and an occasions digest went in across 188 commits, all of it on the same function‑first data layer as the rest of the platform. That constraint did most of the work: the follower graph is tables and functions like everything else, a mention resolves through the same identity schema the directory uses, and a post inherits the moderation queue already built for reviews. Nothing here needed its own store or its own permission model. The feed is the one place that pushed back, because assembling a personalised timeline efficiently is a genuinely different problem from fetching a row, and it is where the personalization and discovery work earns its place.</p>\n<p><strong>Result.</strong> The platform has a reason to be opened on a day when nobody needs to look a company up, and it arrived without a parallel stack to maintain. Whether a social layer is the right investment for a maritime directory is a product question rather than an engineering one — it is here because the product asked for it, and it is built the same way as everything around it.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Databases",
        "Frontend Engineering",
        "Full‑Stack Development",
        "Product & Requirements",
        "Web Development",
        "Backend & API Development",
        "Full‑Stack Product Development",
        "Product Strategy & Requirements"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-the-hiring-marketplace-and-career-workspace-107/",
      "url": "https://engineer.company/portfolio/built-the-hiring-marketplace-and-career-workspace-107/",
      "title": "Built the hiring marketplace and the seafarer career workspace — 151 stored functions across 48 tables — covering vacancies, applications, certificates, rank progression and verified sea time.",
      "summary": "Built the hiring marketplace and seafarer career workspace — 151 stored functions across 48 tables — matching vacancies to documented qualifications.",
      "content_html": "<p><strong>Situation.</strong> The commercial argument for a maritime professional network is hiring: companies need crew and officers, and seafarers need berths. Both halves already existed on the platform in the wrong shape — companies were in the directory, professionals had profiles, and there was nothing connecting a vacancy to the person qualified to fill it. Qualification is the hard part, because in this industry it is certificates, ranks and documented sea time rather than a job title.</p>\n<p><strong>Task.</strong> Vacancies, applications and verifiable seafarer credentials had to be modelled properly rather than as free text on a profile.</p>\n<p><strong>Action.</strong> Two domains got built. The hiring side runs to 32 tables and 118 functions covering vacancies, applications, shortlisting and the employer&rsquo;s view of a pipeline. The career workspace carries 16 tables and 33 functions holding certificates, rank progression and sea time, with document upload and a reviewer queue so a credential is checked rather than claimed. Modelling progression as a graph rather than a list is what makes the matching useful — a rank is reachable from another rank given certain certificates and enough recorded time at sea, and that structure is what lets a vacancy be matched against a career rather than against a keyword. The career workspace ships behind a feature flag and has not been fully released; the hiring side is live.</p>\n<p><strong>Result.</strong> A vacancy can be matched against documented qualifications instead of a self‑described job title, which is the whole difference between a job board and a hiring tool in this industry. Verification is the bottleneck, deliberately — a reviewer queue does not scale the way an automated check would, and an automated check would certify people who should not be certified.</p>\n",
      "date_published": "2026-08-17T18:37:15+02:00",
      "date_modified": "2026-08-17T18:37:15+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Data Governance",
        "Databases",
        "Full‑Stack Development",
        "PostgreSQL",
        "Product & Requirements",
        "Backend & API Development",
        "Database Design & Modeling",
        "Full‑Stack Product Development",
        "Product Strategy & Requirements"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/accelerated-geographical-data-pipeline-performance-by-50x-by-5/",
      "url": "https://engineer.company/portfolio/accelerated-geographical-data-pipeline-performance-by-50x-by-5/",
      "title": "Accelerated geographical data pipeline performance by 50x by improving SQL programming and data modeling across PostgreSQL, MS SQL, and Google Cloud BigQuery.",
      "summary": "Accelerated a geographical data pipeline 50x through SQL and data-model tuning across PostgreSQL, MS SQL and Google BigQuery.",
      "content_html": "<p><strong>Situation.</strong> The organization faced significant delays in processing large sets of geographical data, which affected the efficiency of data‑driven decision‑making processes and analytical reporting.</p>\n<p><strong>Task.</strong> The objective was to optimize the geographical data pipeline to enhance performance and reduce processing time, ensuring that data analytics could be performed more effectively and in real‑time.</p>\n<p><strong>Action.</strong> The existing SQL programming and data modeling structures across PostgreSQL, MS SQL, and Google Cloud BigQuery were meticulously analyzed, which surfaced bottlenecks related to inefficient indexing strategies, suboptimal query designs, and lack of appropriate constraints. To address these issues:</p>\n<ul>\n<li>The data models were redesigned to normalize critical datasets, with partitioning strategies implemented to improve data retrieval times.</li>\n<li>Applied advanced indexing techniques, including B‑tree and GiST indexes for PostgreSQL, filtered indexes in MS SQL, and clustering in BigQuery.</li>\n<li>Optimized complex SQL queries by refactoring subqueries, reducing join operations, and incorporating materialized views where relevant.</li>\n<li>Established data integrity constraints such as foreign keys and check constraints to ensure data consistency without compromising performance.</li>\n</ul>\n<p><strong>Result.</strong> These comprehensive optimizations accelerated the geographical data pipeline performance by 50x, significantly reducing data processing time. This improvement facilitated real‑time analytics, enhanced reporting capabilities, and empowered stakeholders with timely, data‑driven insights, ultimately contributing to more informed strategic decisions.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Data Engineering",
        "Data Governance",
        "Data Pipelines (ETL/ELT)",
        "Data Warehousing",
        "Databases",
        "GIS / Geospatial",
        "Performance Tuning",
        "PostgreSQL",
        "SQL",
        "Data Pipeline Development (ETL/ELT)",
        "Data Warehouse Design",
        "Database Performance Tuning",
        "GIS & Geospatial Solutions"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/improved-geographical-map-application-performance-by-10x-through-6/",
      "url": "https://engineer.company/portfolio/improved-geographical-map-application-performance-by-10x-through-6/",
      "title": "Improved geographical map application performance by 10x through strategic database transition from MSSQL to PostgreSQL, optimizing processing and data security.",
      "summary": "Improved a GIS map application 10x by migrating MSSQL to PostgreSQL — spatial queries dropped from 2.5s to under 250ms.",
      "content_html": "<p><strong>Situation.</strong> The organization’s geographical map application, which supported real‑time spatial queries for many users, was experiencing severe performance bottlenecks. Latency in rendering map layers and querying location‑based data impacted both user experience and backend service reliability. The system was backed by a legacy Microsoft SQL Server (MSSQL) database that lacked native geospatial indexing.</p>\n<p><strong>Task.</strong> Improve the performance, scalability, and security of the map application, with a specific goal of reducing query latency and increasing throughput for spatial operations.</p>\n<p><strong>Action.</strong> Led a strategic database migration from MSSQL to PostgreSQL with the PostGIS extension to enable native geospatial support.</p>\n<ul>\n<li>Designed a new schema optimized for spatial data, introducing GIST and SP‑GiST indexes on geometry and geography columns for faster querying.</li>\n<li>Defined strict foreign key and check constraints to ensure relational integrity and enforce data validation rules on spatial coordinates.</li>\n<li>Migrated over 50 million spatial records using ETL pipelines with data transformation steps to conform to new SRID standards (EPSG:4326).</li>\n<li>Tuned PostgreSQL configuration parameters (e.g., work_mem, effective_cache_size) for optimal I/O performance under concurrent access.</li>\n<li>Implemented role‑based access controls (RBAC) and row‑level security to enforce data protection policies across multiple user groups.</li>\n</ul>\n<p><strong>Result.</strong> Achieved a 10x improvement in spatial query performance, reducing average response time from 2.5 seconds to under 250 milliseconds. Backend CPU load dropped by 65%, and system availability improved during peak usage. Security posture was also strengthened with granular access policies and data validation constraints, reducing potential for spatial data corruption.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Data Engineering",
        "Data Pipelines (ETL/ELT)",
        "Databases",
        "GIS / Geospatial",
        "Migrations & Modernization",
        "Performance Tuning",
        "PostgreSQL",
        "Security",
        "SQL",
        "Database Migration & Modernization",
        "Database Performance Tuning",
        "GIS & Geospatial Solutions"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/resolved-1-000-issues-in-geographical-data-and-7/",
      "url": "https://engineer.company/portfolio/resolved-1-000-issues-in-geographical-data-and-7/",
      "title": "Resolved 1,000 issues in geographical data and time‑series data, using GDAL, ArcGIS, PostGIS, Mapbox, QGIS, SQL (PL/pgSQL, Transact‑SQL), Bash, ensuring high‑quality big data processing.",
      "summary": "Resolved 1,000+ issues in geographic and time-series big data with GDAL, PostGIS, ArcGIS, Mapbox, QGIS and SQL — 35% faster queries.",
      "content_html": "<p><strong>Situation.</strong> While working on a large‑scale geospatial analytics project, the team encountered numerous inconsistencies and anomalies within the geographical and time‑series datasets. These issues were affecting the accuracy of spatial analyses and decision‑making tools used across several departments.</p>\n<p><strong>Task.</strong> The responsibility was to identify, resolve, and optimize over 1,000 data quality issues within these complex datasets to ensure the integrity and performance of downstream applications and visualizations.</p>\n<p><strong>Action.</strong> Spatial errors were systematically diagnosed and corrected using a combination of tools, including GDAL, QGIS, and ArcGIS, with automated workflows implemented in Bash scripting to streamline recurring data cleaning tasks. PostGIS handled advanced spatial queries and spatial indexing, and robust procedures were written in PL/pgSQL and Transact‑SQL to manage and transform both geographic and temporal data within the PostgreSQL and SQL Server databases. Additionally, the cleaned data was integrated into interactive visualizations using Mapbox, enhancing data accessibility for end users.</p>\n<p><strong>Result.</strong> Through these efforts, over 1,000 critical issues were resolved, significantly improving data accuracy and processing speed. This directly contributed to a 35% reduction in spatial query run times and enabled more reliable spatial analyses for the team. The work ensured that high‑quality, ready‑to‑use data was consistently available for analytics and reporting, supporting strategic decisions across the organization.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Data Engineering",
        "Data Governance",
        "Data Pipelines (ETL/ELT)",
        "Databases",
        "GIS / Geospatial",
        "Performance Tuning",
        "PostgreSQL",
        "SQL",
        "Data Governance & Quality",
        "Data Pipeline Development (ETL/ELT)",
        "GIS & Geospatial Solutions"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/designed-implemented-and-administered-6-etl-elt-pipelines-8/",
      "url": "https://engineer.company/portfolio/designed-implemented-and-administered-6-etl-elt-pipelines-8/",
      "title": "Designed, implemented, and administered 6 ETL/ELT pipelines, utilizing Google BigQuery, MSSQL, PostgreSQL, Shell scripting, PL/pgSQL, and Transact‑SQL, integrating data for efficient Python API processing.",
      "summary": "Designed and ran 6 ETL/ELT pipelines (BigQuery, MSSQL, PostgreSQL), cutting a 3-hour manual job to under 20 minutes with daily syncs.",
      "content_html": "<p><strong>Situation.</strong> The organization needed to consolidate and process large volumes of structured data from a remote data warehouse located behind an IPSec VPN. This data was crucial for powering internal analytics dashboards and external Python APIs used by clients and partners.</p>\n<p><strong>Task.</strong> The objective was to design, implement, and maintain a set of robust and automated ETL/ELT pipelines to securely retrieve, transform, and load data into Google BigQuery, ensuring data accuracy, performance, and scalability across systems.</p>\n<p><strong>Action.</strong> 6 end‑to‑end ETL/ELT pipelines were designed, implemented, and administered, spanning multiple technologies including Google BigQuery, MSSQL, PostgreSQL, Shell scripting, PL/pgSQL, and Transact‑SQL.</p>\n<ul>\n<li>Established secure connections to a remote server protected by an IPSec VPN to automate data retrieval.</li>\n<li>Scheduled and executed downloads of compressed data archives containing Parquet, CSV, and .bak files.</li>\n<li>Developed Bash and Python scripts to extract and classify files based on type and schema.</li>\n<li>For .bak files, backups were restored into a local MSSQL Server instance using RESTORE DATABASE workflows and schema integrity validated.</li>\n<li>Loaded structured data into staging schemas in MSSQL and PostgreSQL, using bcp, psql, and SSIS tools, depending on the source format.</li>\n<li>Wrote modular and reusable PL/pgSQL and T‑SQL procedures to clean, normalize, and enrich the data based on business logic.</li>\n<li>Exported curated datasets and tables into intermediate formats, compressed them using gzip, and securely transferred them to a Google Cloud Storage bucket.</li>\n<li>Automated the upload and schema mapping processes to Google BigQuery, using bq CLI and Python‑based data ingestion scripts.</li>\n</ul>\n<p><strong>Result.</strong> These automated pipelines significantly reduced the manual overhead and processing time — from 3+ hours of manual work to under 20 minutes end‑to‑end, while improving data freshness from weekly to daily syncs. The Python APIs consuming this data saw a 30% performance improvement, and the enhanced visibility helped business analysts deliver faster insights to stakeholders. The solution remains scalable and extensible for onboarding new data sources as the business grows.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Cloud",
        "Data Engineering",
        "Data Pipelines (ETL/ELT)",
        "Data Warehousing",
        "Databases",
        "Networking & VPN",
        "PostgreSQL",
        "Python",
        "SQL",
        "Backend & API Development",
        "Data Pipeline Development (ETL/ELT)",
        "Data Warehouse Design",
        "Database Design & Modeling"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/developed-and-launched-the-company-s-first-observability-9/",
      "url": "https://engineer.company/portfolio/developed-and-launched-the-company-s-first-observability-9/",
      "title": "Developed and launched the company's first observability dashboard, providing real‑time system performance insights and data visualization on the large office TV.",
      "summary": "Built the company's first observability dashboard for real-time system insight, helping teams detect and resolve incidents 40% faster.",
      "content_html": "<p><strong>Situation.</strong> The company was facing challenges in monitoring real‑time system performance, which often led to delayed incident responses and reduced visibility into infrastructure health. There was no centralized solution in place for teams to gain insights into operational metrics.</p>\n<p><strong>Task.</strong> The task was to develop a solution that would enable technical and non‑technical stakeholders to monitor key system metrics in real time, with a focus on accessibility, clarity, and proactive issue detection.</p>\n<p><strong>Action.</strong> The company&rsquo;s first observability dashboard was designed and implemented, collecting all essential system metrics — such as CPU, RAM, HDD, temperature, and more — from remote Linux servers via SSH. Even Docker containers were monitored using this method. Later, a second version was designed and implemented using Grafana and Prometheus for more advanced visualization and monitoring capabilities. Collaboration with DevOps and engineering teams identified the critical metrics, such as CPU utilization, memory usage, service uptime, and API latency. Data pipelines were configured to ingest and process performance metrics from various systems, and the dashboard deployed on a large office TV screen for maximum visibility. Alerting mechanisms for threshold breaches were also integrated to enable immediate action.</p>\n<p><strong>Result.</strong> The dashboard significantly improved system transparency and response time to operational issues. Teams were able to detect and resolve incidents 40% faster. It also fostered a culture of shared ownership over system health by making performance data accessible to everyone in the office, ultimately contributing to a more stable and efficient production environment.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Containers (Docker/Kubernetes)",
        "Data Analytics",
        "DevOps",
        "Infrastructure",
        "Linux & Servers",
        "Monitoring & Observability",
        "Reliability & Backups",
        "Data Analytics & BI Dashboards",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/delivered-8-power-bi-projects-with-comprehensive-manuals-10/",
      "url": "https://engineer.company/portfolio/delivered-8-power-bi-projects-with-comprehensive-manuals-10/",
      "title": "Delivered 8 Power BI projects with comprehensive manuals, integrating Microsoft Power BI tools with NodeJS API and Python FastAPI for effective data analytics and visualization.",
      "summary": "Delivered 8 Power BI analytics projects (Node.js API, Python FastAPI) with full manuals, improving report generation efficiency by 60%.",
      "content_html": "<p><strong>Situation.</strong> The organization needed dynamic, visual insights into complex datasets involving electrical grid performance and geographical distribution metrics to support decision‑making across technical and strategic teams.</p>\n<p><strong>Task.</strong> Develop interactive dashboards and reporting solutions that could effectively present both real‑time and historical geographical and electrical data, enabling stakeholders to quickly identify trends, anomalies, and performance indicators.</p>\n<p><strong>Action.</strong> Integrated Microsoft Power BI with a custom backend stack using NodeJS API and Python FastAPI to streamline data ingestion, transformation, and visualization. Designed and implemented dashboards with map visualizations, energy consumption metrics, outage tracking, and grid efficiency indicators. Developed reusable templates and detailed documentation to support scalability and ease of use.</p>\n<p><strong>Result.</strong> Successfully delivered 8 data analytics projects, improving report generation efficiency by 60% and enabling cross‑functional teams to make faster, data‑driven decisions. Stakeholders reported a significant increase in understanding of regional electrical performance and resource planning accuracy.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Backend Engineering",
        "Data Analytics",
        "Data Engineering",
        "Documentation",
        "GIS / Geospatial",
        "Product & Requirements",
        "Python",
        "Backend & API Development",
        "Data Analytics & BI Dashboards",
        "Technical Documentation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/released-500-electricity-and-gis-data-analysis-reports-11/",
      "url": "https://engineer.company/portfolio/released-500-electricity-and-gis-data-analysis-reports-11/",
      "title": "Released 500 electricity and GIS data analysis reports, utilizing deep research and troubleshooting to ensure accurate geographic and time series big data insights.",
      "summary": "Published 500 electricity and GIS data-analysis reports — a cross-department reference for load balancing, efficiency and anomaly detection.",
      "content_html": "<p><strong>Situation.</strong> The team managed vast datasets generated by smart‑meters installed across multiple geographic regions. These smart‑meters produced granular, time‑series electrical consumption data used by energy analysts, engineers, and regional planners for operational and strategic decision‑making.</p>\n<p><strong>Task.</strong> The responsibility was to produce high‑quality, transparent, and reproducible analytical reports that could uncover patterns in energy consumption, detect anomalies, and identify regional usage trends, while ensuring non‑technical stakeholders could easily interpret and reuse the findings.</p>\n<p><strong>Action.</strong> 500+ in‑depth data analysis reports were created and delivered, using pure SQL to perform all data extraction, transformation, and analysis tasks, working directly within cloud‑based environments such as PostgreSQL and BigQuery. The data included geolocation coordinates, meter IDs, timestamped energy usage, and environmental metadata. The SQL scripts featured CTEs, window functions, subqueries, and geospatial joins, allowing for scalable and efficient processing.</p>\n<p>Each report included annotated SQL code, enabling colleagues and collaborators to fully reproduce and audit the research, which significantly reduced the time needed for follow‑up analysis. Troubleshooting notes were also added and common data quality issues documented, such as missing GPS coordinates or corrupted meter values, with recommended handling procedures.</p>\n<p><strong>Result.</strong> The reports became a standard reference across departments, aiding in regional load balancing, energy efficiency planning, and anomaly detection. By ensuring full transparency and reproducibility, the work helped improve stakeholder trust in the data and contributed to more accurate forecasting models and a 10–15% improvement in operational planning efficiency.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Data Analytics",
        "Data Governance",
        "Data Warehousing",
        "Databases",
        "Documentation",
        "GIS / Geospatial",
        "PostgreSQL",
        "SQL",
        "Stakeholder & Reporting",
        "Data Analytics & BI Dashboards",
        "Data Governance & Quality",
        "GIS & Geospatial Solutions"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/architected-created-and-managed-100-postgresql-ms-sql-12/",
      "url": "https://engineer.company/portfolio/architected-created-and-managed-100-postgresql-ms-sql-12/",
      "title": "Architected, created, and managed 100 PostgreSQL, MS SQL, and Google BigQuery data warehouse databases with primarily GIS and time‑series data, optimizing performance and scalability.",
      "summary": "Architected and ran 100 PostgreSQL, MS SQL and BigQuery data-warehouse databases for GIS and time-series data — query times down 40–60%.",
      "content_html": "<p><strong>Situation.</strong> In a rapidly growing tech company, there was a critical need to create, manage, and optimize a diverse portfolio of 100+ databases across PostgreSQL, Microsoft SQL Server, and Google BigQuery. These databases primarily handled complex datasets, including geographic information systems (GIS) data (e.g., spatial coordinates, and location‑based analytics) and time‑series data (e.g., sensor readings, logs, and real‑time metrics). The existing infrastructure faced challenges with scalability, query performance, and data consistency, particularly as the volume of data increased exponentially. The organization required a robust architecture to ensure data reliability, and reduce operational costs.</p>\n<p><strong>Task.</strong> The primary objective was to design, implement, and manage a scalable, high‑performance data warehouse ecosystem tailored for GIS and time‑series data. This involved:</p>\n<ul>\n<li>Addressing performance bottlenecks in complex spatial and time‑based queries.</li>\n<li>Ensuring scalability to handle growing data volumes while maintaining cost efficiency.</li>\n<li>Collaborating with cross‑functional teams (e.g., data scientists, product managers) to align database design with business needs.</li>\n</ul>\n<p><strong>Action.</strong> To achieve these goals:</p>\n<ol>\n<li>Designed Scalable Architectures:</li>\n</ol>\n<ul>\n<li>Created normalized and denormalized schemas for PostgreSQL and SQL Server, leveraging spatial indexing (e.g., PostGIS for PostgreSQL) and time‑series partitioning to optimize query performance.</li>\n<li>Utilized BigQuery’s time‑partitioned and clustered tables for efficient handling of large‑scale time‑series data.</li>\n</ul>\n<ol start=\"2\">\n<li>Implemented Optimization Strategies:</li>\n</ol>\n<ul>\n<li>Introduced query optimization techniques, such as indexing, materialized views, and caching, to reduce latency for GIS and time‑series queries.</li>\n<li>Applied data compression and columnar storage in BigQuery to minimize storage costs and improve scan speeds.</li>\n</ul>\n<ol start=\"3\">\n<li>Collaborated on Cross‑Platform Integration:</li>\n</ol>\n<ul>\n<li>Documented best practices for GIS and time‑series data modeling to guide teams in future projects.</li>\n</ul>\n<p><strong>Result.</strong> The initiatives led to significant improvements:</p>\n<ul>\n<li>Performance Gains: Query response times for GIS and time‑series data decreased by 40–60%, enabling faster analytics and decision‑making.</li>\n<li>Operational Reliability: Automated monitoring reduced downtime by 50%, while standardized processes improved team productivity and reduced errors.</li>\n<li>Business Impact: The optimized infrastructure enabled the company to launch new data‑driven products (e.g., real‑time analytics dashboards) and meet regulatory compliance requirements for data governance.</li>\n</ul>\n<p>This work solidified the organization’s ability to handle complex data challenges.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Data Engineering",
        "Data Governance",
        "Data Warehousing",
        "Databases",
        "GIS / Geospatial",
        "Performance Tuning",
        "Platform Architecture",
        "PostgreSQL",
        "Reliability & Backups",
        "SQL",
        "Data Warehouse Design",
        "Database Administration (DBA)",
        "Database Design & Modeling",
        "Database Performance Tuning",
        "GIS & Geospatial Solutions"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/designed-deployed-and-maintained-10-postgresql-and-ms-13/",
      "url": "https://engineer.company/portfolio/designed-deployed-and-maintained-10-postgresql-and-ms-13/",
      "title": "Designed, deployed, and maintained 10 PostgreSQL and MS SQL servers on Ubuntu Linux VPS, ensuring optimal server performance and reliability.",
      "summary": "Designed and maintained 10 PostgreSQL and MS SQL servers on Ubuntu Linux VPS at 99.9% uptime, with 30% faster queries via tuning.",
      "content_html": "<p><strong>Situation.</strong> As a DevOps engineer at a mid‑sized tech company, the task was managing and optimizing database infrastructure to support a growing user base and critical business applications. The organization relied heavily on PostgreSQL and Microsoft SQL Server for data storage and analytics, requiring high availability, scalability, and security.</p>\n<p><strong>Task.</strong> The primary responsibility was to architect, deploy, configure, and maintain 10 PostgreSQL and MS SQL Server instances on Ubuntu Linux VPS environments. This included ensuring optimal performance, implementing robust security protocols, and establishing proactive monitoring to prevent outages. Additionally, the task included scaling the infrastructure to accommodate future growth while minimizing costs and maintaining compliance with industry standards.</p>\n<p><strong>Action.</strong> 1. Deployment &amp; Configuration:</p>\n<ul>\n<li>Installed and configured PostgreSQL 14 and MS SQL Server 2019 on Ubuntu 20.04 LTS VPS instances, ensuring compatibility with the company’s applications.</li>\n<li>Set up automated backups using <code>pg_dump</code> for PostgreSQL and SQL Server Agent jobs for MS SQL, with retention policies and offsite storage.</li>\n<li>Optimized server configurations (e.g., memory allocation, query caching, and connection pooling) to improve query performance and reduce latency.</li>\n</ul>\n<ol start=\"2\">\n<li>\n<p>Monitoring &amp; Maintenance:</p>\n<ul>\n<li>Implemented monitoring tools like Prometheus, Grafana, to track CPU, memory, disk I/O, and query performance metrics in real time.</li>\n<li>Conducted regular patching and updates for both databases and the Ubuntu OS to address security vulnerabilities and ensure compliance.</li>\n<li>Created custom scripts for log analysis.</li>\n</ul>\n</li>\n<li>\n<p>Security &amp; Scalability:</p>\n<ul>\n<li>Configured firewalls (UFW), enforced role‑based access control (RBAC) to protect sensitive data.</li>\n<li>Documented procedures for disaster recovery, including point‑in‑time restores and failover protocols.</li>\n</ul>\n</li>\n</ol>\n<p><strong>Result.</strong> - Achieved 99.9% uptime across all 10 database servers.</p>\n<ul>\n<li>Improved query response times by 30% through configuration tuning and index optimization, enhancing application performance.</li>\n<li>Reduced manual maintenance tasks by 50% via automation, freeing up 10+ hours per month for strategic projects.</li>\n<li>Successfully scaled the infrastructure to support a 40% increase in user traffic without service degradation, contributing to a 20% revenue growth in the following quarter.</li>\n<li>Received recognition from the CTO for implementing security best practices that prevented potential data breaches.</li>\n</ul>\n<p>This experience solidified deep expertise in database management, DevOps automation, and infrastructure optimization, delivering reliable, secure, and scalable solutions for complex enterprise environments.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Databases",
        "DevOps",
        "Infrastructure",
        "Linux & Servers",
        "Monitoring & Observability",
        "Performance Tuning",
        "PostgreSQL",
        "Reliability & Backups",
        "Security",
        "System Administration",
        "Database Administration (DBA)",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/enhanced-data-security-by-implementing-1-000-rbac-14/",
      "url": "https://engineer.company/portfolio/enhanced-data-security-by-implementing-1-000-rbac-14/",
      "title": "Enhanced data security by implementing 1,000 RBAC rules for developers, application instances, PostgreSQL, MS SQL, and other Linux servers, preventing unauthorized access; documented with Ansible automation.",
      "summary": "Hardened security with 1,000 RBAC rules across developers, apps and Linux/DB servers — unauthorised-access risk down 85%, Ansible-automated.",
      "content_html": "<p><strong>Situation.</strong> The organization needed to strengthen access controls across multiple systems, including developer environments, application instances, PostgreSQL, MS SQL, and Linux servers. While the underlying RBAC model was straightforward, the challenge lay in managing over 1,000 individual rules to cover diverse user roles and system requirements. The goal was to ensure strict access restrictions without introducing complexity.</p>\n<p><strong>Task.</strong> Implement a scalable RBAC solution by defining and enforcing 1,000+ rules for access control. This involved mapping permissions to specific roles (e.g., developers, application instances, database admins) and ensuring rules were applied consistently across all systems. The task also required documenting the rules and automating their deployment to avoid manual errors.</p>\n<p><strong>Action.</strong> The approach focused on creating a simple, modular RBAC structure, breaking down permissions into clear, reusable categories (e.g., &ldquo;read‑only access to production databases&rdquo;). Using Ansible, the configuration of each rule was automated, ensuring consistency across environments. For example, developers were granted access only to their designated servers, while application instances had limited permissions to prevent lateral movement. The process prioritized clarity over complexity, with each rule explicitly tied to a specific role and system.</p>\n<p><strong>Result.</strong> The implementation secured over 1,000 rules without introducing unnecessary complexity, reducing unauthorized access risks by 85%. Automation streamlined deployment, cutting setup time by 60% compared to manual methods. The documented framework allowed teams to quickly audit or modify rules, ensuring scalability as the infrastructure grew. By focusing on simplicity and volume, the solution achieved robust security while maintaining operational efficiency.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Databases",
        "DevOps",
        "Documentation",
        "Infrastructure",
        "Linux & Servers",
        "Security",
        "System Administration",
        "Database Administration (DBA)",
        "Infrastructure as Code",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/accelerated-postgresql-performance-by-10x-via-strategic-indexing-15/",
      "url": "https://engineer.company/portfolio/accelerated-postgresql-performance-by-10x-via-strategic-indexing-15/",
      "title": "Accelerated PostgreSQL performance by 10x via strategic indexing, partitioning, and query optimization, enhancing database efficiency for user, tenant, geospatial, and time‑series electrical data.",
      "summary": "Accelerated PostgreSQL 10x via indexing, partitioning and query tuning for user, tenant, geospatial and time-series data — resource use down 40%.",
      "content_html": "<p><strong>Situation.</strong> The PostgreSQL database served a mid‑sized application managing user accounts, tenant information, geographical data (e.g., locations, regions), and daily electrical consumption metrics. While the system operated under low traffic, performance issues emerged as data volumes grew. Queries involving geospatial data and time‑series electrical logs became sluggish, leading to inconsistent response times. The lack of optimized indexing, fragmented queries, and unpartitioned tables exacerbated the problem, creating bottlenecks for critical operations like user authentication, tenant management, and data reporting.</p>\n<p><strong>Task.</strong> The goal was to enhance database performance by 10x without overhauling the existing architecture. The focus was on optimizing query execution, reducing latency, and ensuring scalability for future data growth. Key priorities included improving response times for geospatial and time‑based queries, minimizing resource contention, and maintaining data integrity while implementing changes.</p>\n<p><strong>Action.</strong> 1. <strong>Indexing:</strong> Analyzed frequently queried columns (e.g., user IDs, tenant IDs, geospatial coordinates) and created targeted indexes. For geospatial data, GiST indexes were added to speed up spatial queries. Composite indexes were introduced for multi‑column filters, such as tenant ID + date ranges for electrical data.\n2. <strong>Partitioning:</strong> Implemented time‑based range partitioning for the electrical data table, splitting it by day/month. This reduced the dataset size for queries and improved scan efficiency. For geographical data, hash partitioning was used to distribute load evenly across nodes.\n3. <strong>Query Optimization:</strong> Rewrote complex queries to avoid full table scans, leveraging CTEs (Common Table Expressions) and materialized views for frequently accessed datasets. The <code>EXPLAIN ANALYZE</code> tool identified inefficient joins and subqueries, which were restructured for better execution plans. Additionally, query caching and connection pooling were configured to reduce overhead.</p>\n<p><strong>Result.</strong> Post‑optimization, query response times improved by 10x, with critical operations (e.g., user authentication, geospatial lookups) executing in milliseconds. The database’s resource utilization dropped by 40%, allowing the system to handle increased data volumes without performance degradation. Users reported smoother interactions, and the system became more scalable for future growth. The changes also reduced the need for hardware upgrades, saving costs while ensuring long‑term reliability. This project demonstrated how strategic indexing, partitioning, and query refinement can transform even low‑load systems into efficient, future‑ready databases.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Data Engineering",
        "Databases",
        "GIS / Geospatial",
        "Performance Tuning",
        "PostgreSQL",
        "SQL",
        "Database Administration (DBA)",
        "Database Performance Tuning"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/automated-gis-saas-application-deployment-data-processing-and-16/",
      "url": "https://engineer.company/portfolio/automated-gis-saas-application-deployment-data-processing-and-16/",
      "title": "Automated GIS SaaS application deployment, data processing, and reporting system using GitHub Actions CI/CD, Python, Bash, and SQL.",
      "summary": "Automated GIS SaaS deployment, data processing and reporting with GitHub Actions CI/CD, Python, Bash and SQL — shorter, reliable releases.",
      "content_html": "<p><strong>Situation.</strong> At Utiligize, getting the GIS SaaS app deployed, processing its data and producing the reports were all manual steps — and manual steps are both slow and quietly dangerous. Every release ate engineering time and carried the chance of a slip, and the recurring data and reporting work sat there eating capacity week after week.</p>\n<p><strong>Task.</strong> Automating the whole path from code to production, plus the recurring data‑processing and reporting, was the task — the goal being releases that were fast, safe and repeatable rather than a careful manual ritual each time.</p>\n<p><strong>Action.</strong> The whole path got automated. GitHub Actions pipelines took over the test‑build‑deploy cycle, so a release stopped depending on someone remembering the steps. The recurring data processing and the reports moved into scheduled Python, Bash and SQL jobs, so they just ran instead of being someone&rsquo;s chore. And the configuration and secrets were standardised so every environment behaved identically — which is what kills the &ldquo;works on my machine&rdquo; surprises, because there stops being a &ldquo;my machine&rdquo; that&rsquo;s different from production.</p>\n<p><strong>Result.</strong> Deployment, data processing and reporting all became automated and reliable, the manual toil came off the team&rsquo;s plate, and the release cycle got shorter. The team could put its attention on the product instead of the operations wrapped around it.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Cloud",
        "Data Engineering",
        "Data Pipelines (ETL/ELT)",
        "DevOps",
        "GIS / Geospatial",
        "Python",
        "Reliability & Backups",
        "SQL",
        "Data Pipeline Development (ETL/ELT)",
        "DevOps & CI/CD Automation",
        "GIS & Geospatial Solutions"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/automated-delivery-of-20-gis-data-pipelines-and-17/",
      "url": "https://engineer.company/portfolio/automated-delivery-of-20-gis-data-pipelines-and-17/",
      "title": "Automated delivery of 20 GIS data pipelines and app data ETL processes, streamlining infrastructure automation and reporting.",
      "summary": "Automated 20 GIS data pipelines and app-data ETL processes, streamlining infrastructure automation and delivering dependable, current data.",
      "content_html": "<p><strong>Situation.</strong> The platform ran on a lot of GIS data pipelines and application‑data ETL processes, and they were being delivered and watched by hand. Hand‑run pipelines create bottlenecks, they drift out of consistency, and worst of all they carry a constant low risk that one quietly fails and nobody notices until the data&rsquo;s already wrong downstream.</p>\n<p><strong>Task.</strong> Automating the delivery of those pipelines and ETL processes — so the data flowed reliably and predictably without someone shepherding it — was the task.</p>\n<p><strong>Action.</strong> Twenty GIS data pipelines and the application‑data ETL came under automated delivery, end to end. The scheduling, the logging and the failure handling got standardised, so every pipeline behaved the same way and, crucially, you could see when one didn&rsquo;t — a silent failure is only silent if nothing&rsquo;s watching. And they were folded into the existing infrastructure automation and reporting, so they were part of one coherent system rather than a drawer full of scripts someone had to remember to run.</p>\n<p><strong>Result.</strong> All twenty pipelines and their ETL ran automatically and predictably, and the whole infrastructure‑automation and reporting picture got tidier for it. The business got dependable, current data without anyone having to walk it through by hand — and without the quiet‑failure risk hanging over it.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Data Engineering",
        "Data Pipelines (ETL/ELT)",
        "DevOps",
        "GIS / Geospatial",
        "Monitoring & Observability",
        "Reliability & Backups",
        "Data Pipeline Development (ETL/ELT)",
        "DevOps & CI/CD Automation",
        "GIS & Geospatial Solutions"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/automated-100-critical-data-backups-using-barman-google-18/",
      "url": "https://engineer.company/portfolio/automated-100-critical-data-backups-using-barman-google-18/",
      "title": "Automated 100 critical data backups using Barman, Google Cloud, Bash, and Python, ensuring data integrity across databases.",
      "summary": "Automated 100 critical database backups with Barman, Google Cloud, Bash and Python — turning data recoverability into a tested fact.",
      "content_html": "<p><strong>Situation.</strong> The critical GIS data, the application instances and the databases were scattered across systems with backups that were inconsistent and partly manual. For a product that lives on its data, that&rsquo;s not a risk you can leave sitting — and the day you actually need a backup is precisely the worst day to find out it was incomplete.</p>\n<p><strong>Task.</strong> The task was to guarantee that all the critical data could be recovered, which meant automating comprehensive, verified backups across the whole estate — verified being the word that matters.</p>\n<p><strong>Action.</strong> The backup regime was built end to end. A hundred critical data backups got automated, with Barman handling the PostgreSQL side and Google Cloud holding the offsite copies, and the whole thing was orchestrated and validated with Bash and Python — because a backup you&rsquo;ve taken but never checked isn&rsquo;t really a backup, it&rsquo;s a hope. So there were retention policies to keep them current and integrity checks to confirm each one was actually good, not just present.</p>\n<p><strong>Result.</strong> Backups ran automatically and were verifiable across every database, which turned data recoverability from an assumption into something tested. A major operational risk came off the business and got replaced with a recovery path you could actually trust — the difference being that this one had been checked, not just configured.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Cloud",
        "Databases",
        "DevOps",
        "Infrastructure",
        "PostgreSQL",
        "Python",
        "Reliability & Backups",
        "Backup & Disaster Recovery",
        "Database Administration (DBA)",
        "DevOps & CI/CD Automation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/deployed-and-maintained-20-docker-containerized-applications-troubleshooting-19/",
      "url": "https://engineer.company/portfolio/deployed-and-maintained-20-docker-containerized-applications-troubleshooting-19/",
      "title": "Deployed and maintained 20 Docker containerized applications, troubleshooting with Podman and Kubernetes, and managing R‑based apps on Google Cloud and AWS.",
      "summary": "Deployed and maintained 20 Docker containerized apps across Google Cloud and AWS, troubleshooting with Podman and Kubernetes for stable releases.",
      "content_html": "<p><strong>Situation.</strong> There was a growing set of containerised applications — including some R‑based analytics apps — running across both Google Cloud and AWS. Spread over two clouds and a few container runtimes, they needed to deploy consistently and to be diagnosable quickly when something went wrong, which is harder than it sounds when no two environments are quite the same.</p>\n<p><strong>Task.</strong> Deploying and maintaining those workloads reliably, and being able to diagnose problems fast across the runtimes and clouds, was the job.</p>\n<p><strong>Action.</strong> The containerised estate was managed across both clouds — twenty Docker applications deployed and maintained with consistent configuration and monitoring, so they weren&rsquo;t each their own snowflake. When things went wrong, troubleshooting went through Podman and the orchestration debugging through Kubernetes. The R‑based analytics apps got dedicated attention in the Google Cloud and AWS production environments, kept stable and reproducible, which for analytics matters — a result you can&rsquo;t reproduce isn&rsquo;t much of a result.</p>\n<p><strong>Result.</strong> The containerised estate ran reliably across both clouds, problems got diagnosed faster, and deployments stayed stable and reproducible. The applications the product leaned on stayed dependable no matter which cloud they happened to be running in.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Cloud",
        "Containers (Docker/Kubernetes)",
        "Data Analytics",
        "DevOps",
        "Infrastructure",
        "Reliability & Backups",
        "Cloud Infrastructure & Migration",
        "Containerization & Orchestration",
        "DevOps & CI/CD Automation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/managed-30-ubuntu-linux-vps-instances-implementing-disaster-20/",
      "url": "https://engineer.company/portfolio/managed-30-ubuntu-linux-vps-instances-implementing-disaster-20/",
      "title": "Managed 30 Ubuntu Linux VPS instances, implementing disaster recovery strategies and ensuring optimal network configurations.",
      "summary": "Managed 30 Ubuntu Linux VPS instances with tested disaster-recovery strategies and solid networking — a resilient foundation to grow on.",
      "content_html": "<p><strong>Situation.</strong> Utiligize ran on a fleet of Ubuntu Linux VPS instances whose setup had grown organically over time — which is a polite way of saying it had accumulated rather than been designed. That left gaps: inconsistent configurations, and recovery and networking arrangements that were more historical accident than plan.</p>\n<p><strong>Task.</strong> The task was to get the fleet under proper management, harden the disaster‑recovery side, and make the network configuration consistent and sensible across every instance.</p>\n<p><strong>Action.</strong> The thirty instances came under deliberate management — treated as one coherent fleet rather than thirty individual pets. Real disaster‑recovery went in: backups, and restore procedures that were actually tested, because an untested restore is just a theory. And the network configuration was standardised for security and performance, so every instance followed the same hardened baseline instead of whatever it had happened to end up with.</p>\n<p><strong>Result.</strong> The infrastructure became resilient and consistent, with recovery paths that had been tested and networking you could rely on. The downtime risk dropped, and the business ended up with a dependable foundation to grow on rather than a patchwork it had to keep nursing along.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "DevOps",
        "Infrastructure",
        "Linux & Servers",
        "Networking & VPN",
        "Reliability & Backups",
        "Security",
        "System Administration",
        "Backup & Disaster Recovery",
        "Networking & VPN Setup"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/prevented-security-breaches-by-leading-access-management-initiatives-21/",
      "url": "https://engineer.company/portfolio/prevented-security-breaches-by-leading-access-management-initiatives-21/",
      "title": "Prevented security breaches by leading access management initiatives, utilizing M365, 1Password, Red Hat SSO, and OKTA SSO.",
      "summary": "Led access management with M365, 1Password, Red Hat SSO and OKTA, sharply cutting unauthorised-access risk and making access auditable.",
      "content_html": "<p><strong>Situation.</strong> Access to systems and services across Utiligize was managed inconsistently — permissions granted ad hoc, over time, by different people. That&rsquo;s a double problem: it opens the door to access nobody intended, and it makes auditing nearly impossible, because nobody can actually say who can reach what, or why they can.</p>\n<p><strong>Task.</strong> The goal was to close off the security exposure by centralising and tightening access management across the whole organisation.</p>\n<p><strong>Action.</strong> The overhaul consolidated the identities and access across M365, 1Password, Red Hat SSO and OKTA SSO, so there was a coherent picture instead of scattered per‑system permissions. Least privilege was enforced — people and systems having exactly what they needed and nothing spare — and the onboarding and offboarding standardised, so access got granted and, just as importantly, revoked promptly and consistently rather than lingering after someone had moved on.</p>\n<p><strong>Result.</strong> The unauthorised‑access risk dropped sharply and access became auditable and consistent — you could finally answer &ldquo;who can reach this, and why.&rdquo; The odd part is it also made daily life simpler for the team: the right doors opened easily and the wrong ones stayed shut, which is what good access management actually feels like.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Cloud",
        "Infrastructure",
        "Security",
        "System Administration",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/mitigated-operational-risks-by-implementing-a-monitoring-dashboard-22/",
      "url": "https://engineer.company/portfolio/mitigated-operational-risks-by-implementing-a-monitoring-dashboard-22/",
      "title": "Mitigated operational risks by implementing a monitoring dashboard using Grafana and Prometheus, improving system reliability.",
      "summary": "Built a Grafana + Prometheus monitoring dashboard that catches problems before they escalate, shifting the team from firefighting to prevention.",
      "content_html": "<p><strong>Situation.</strong> Problems at Utiligize usually got noticed after they&rsquo;d already hit users, because there was no single view of how the systems were doing. Without that visibility the team was permanently on the back foot — reacting to things that had already gone wrong instead of seeing them coming.</p>\n<p><strong>Task.</strong> The aim was to cut the operational risk by giving the team real‑time visibility into the systems they depended on.</p>\n<p><strong>Action.</strong> The observability layer was built out. A monitoring dashboard on Grafana and Prometheus, the key services instrumented, and — the part that actually matters — metrics that meant something rather than vanity numbers that look busy and tell you nothing. Then alert thresholds set on those, surfaced where the team would actually see them and could act while there was still time to act.</p>\n<p><strong>Result.</strong> Problems started getting caught and dealt with before they escalated, and system reliability improved for it. The team shifted from reactive firefighting to something calmer and more proactive — catching issues while they were still small enough to be boring.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Infrastructure",
        "Monitoring & Observability",
        "Reliability & Backups",
        "DevOps & CI/CD Automation",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/managed-and-troubleshooted-8-wireguard-vpn-and-ipsec-23/",
      "url": "https://engineer.company/portfolio/managed-and-troubleshooted-8-wireguard-vpn-and-ipsec-23/",
      "title": "Managed and troubleshooted 8 WireGuard VPN and IPSEC VPN connections, ensuring secure communication across Google Cloud and Linux systems.",
      "summary": "Managed and troubleshot 8 WireGuard and IPSEC VPN connections across Google Cloud and Linux, ending recurring connectivity outages.",
      "content_html": "<p><strong>Situation.</strong> Secure connectivity between the cloud and the on‑premises Linux systems ran over several VPN tunnels, and they were fragile and awkward to diagnose when they dropped. A dead tunnel could cut communication between environments, and troubleshooting one was slow and uncertain — you were never quite sure you&rsquo;d found the real cause.</p>\n<p><strong>Task.</strong> Managing and troubleshooting those connections, to guarantee secure and uninterrupted communication, was the job.</p>\n<p><strong>Action.</strong> The VPN estate came under control — eight WireGuard and IPSec tunnels across Google Cloud and the Linux systems, managed and troubleshot as a set rather than eight separate mysteries. Their configuration was standardised so they were consistent and understandable instead of each being its own special case, and their health was monitored, with the recurring routing and key‑exchange problems tackled at the root rather than papered over with a restart.</p>\n<p><strong>Result.</strong> All eight tunnels ran securely and reliably, communication between environments stayed protected, and the recurring connectivity incidents that used to interrupt work stopped happening. Fixing the root causes rather than nursing the symptoms is what turned them from a recurring headache into something that just worked.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Cloud",
        "Infrastructure",
        "Linux & Servers",
        "Networking & VPN",
        "Reliability & Backups",
        "Security",
        "System Administration",
        "Networking & VPN Setup",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/led-the-software-development-of-a-gis-map-24/",
      "url": "https://engineer.company/portfolio/led-the-software-development-of-a-gis-map-24/",
      "title": "Led the software development of a GIS map application, driving revenue growth by 10x and positioning the product as a primary data asset.",
      "summary": "Led development of a GIS map application that became a platform cornerstone, driving a 10x revenue increase via upsells and data licensing.",
      "content_html": "<p><strong>Situation.</strong> The core challenge was to build a platform that could track, monitor, and optimize renewable energy assets like solar panels and wind turbines. However, the initial solution lacked robust geospatial capabilities, making it difficult for clients to visualize asset locations, analyze spatial data, or derive actionable insights. Recognizing this gap, the leadership team prioritized developing a GIS (Geographic Information System) map application to enhance the platform’s value and meet evolving customer needs.</p>\n<p><strong>Task.</strong> The task was to lead the development of the GIS application, a critical component to differentiate the product in the competitive green energy market. The role extended beyond software development — spanning tech leader, devops engineer, data engineer, and SRE (Site Reliability Engineer), ensuring the solution aligned with the company’s growth trajectory. The goal was to create a scalable, intuitive GIS tool that integrated seamlessly with the SaaS platform, enabling users to visualize asset locations, track performance metrics, and leverage spatial data for decision‑making. This required balancing technical innovation with the constraints of a growing startup, while ensuring the product could evolve alongside the company.</p>\n<p><strong>Action.</strong> It began with collaborating with stakeholders to define the GIS application’s core functionalities, focusing on integration with the existing SaaS platform and real‑time data visualization. Given the small team size, a modular architecture was designed using open‑source GIS libraries to keep the system lightweight and scalable. CI/CD pipelines, automated infrastructure provisioning, and monitoring tools were also implemented to ensure reliability. As the team expanded, new engineers were mentored, cross‑functional collaboration facilitated, and user feedback prioritized to refine the application iteratively. Over time, the GIS tool evolved from a basic prototype into a sophisticated platform, incorporating advanced analytics and custom dashboards to meet customer demands.</p>\n<p><strong>Result.</strong> The GIS application became a cornerstone of the SaaS platform, driving a 10x increase in revenue through upsells, data licensing, and new customer acquisitions. Its ability to visualize green energy assets in real time improved operational efficiency for clients, while the tool’s continuous refinement positioned it as a primary data asset. The success of the project not only solidified the company’s reputation in the renewable energy sector but also demonstrated the value of a multi‑disciplinary approach in a fast‑paced startup environment. By combining technical expertise with strategic vision, the GIS application became a key differentiator, fueling long‑term growth and innovation.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "DevOps",
        "Full‑Stack Development",
        "GIS / Geospatial",
        "Mentoring & Coaching",
        "Platform Architecture",
        "Product & Requirements",
        "Reliability & Backups",
        "Solution Architecture",
        "Team Leadership",
        "Technical Leadership",
        "Full‑Stack Product Development",
        "GIS & Geospatial Solutions",
        "Platform & Solution Architecture",
        "Product Strategy & Requirements",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/directed-full-stack-gis-map-development-overseeing-postgresql-25/",
      "url": "https://engineer.company/portfolio/directed-full-stack-gis-map-development-overseeing-postgresql-25/",
      "title": "Directed full‑stack GIS map development, overseeing PostgreSQL, Mapbox, ReactJS, and NodeJS to deliver an integrated solution.",
      "summary": "Directed full-stack GIS map development (PostgreSQL, Mapbox, React, Node.js), delivering one integrated, extensible core of the platform.",
      "content_html": "<p><strong>Situation.</strong> By this stage the GIS map had stopped being a feature and become the reason customers logged in. The trouble was that it had grown up in pieces. Spatial data lived in PostgreSQL, the map itself was drawn with Mapbox, and the application around it was ReactJS on the front with NodeJS behind. Each part worked on its own. They just hadn&rsquo;t been built to fit together, and the seams were starting to show.</p>\n<p><strong>Task.</strong> The role covered the full‑stack development of the map and the technical direction that came with it: the data model, the rendering, the API and the React front end. The goal was to turn four things that happened to share a repository into one product worth standing behind.</p>\n<p><strong>Action.</strong> The architecture was set first, then the work stayed close to the code rather than steering from a distance. On the data side, the PostgreSQL spatial model was kept tidy so queries didn&rsquo;t slow to a crawl as the datasets grew. Mapbox did the drawing; the job was feeding it the right data at the right zoom levels instead of everything at once. On the application side, the ReactJS and NodeJS work got reviewed, the team was pushed toward shared conventions, and responsibilities kept getting moved back to the layer they belonged in whenever one started leaking into the next. A fair amount of it was unglamorous work: catching the small inconsistencies before they hardened into architecture.</p>\n<p><strong>Result.</strong> What came out was a single integrated map application, with data, rendering and interface finally pulling the same way. It became a core part of the platform, and something the team could keep extending without it buckling every time a layer got added.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Backend Engineering",
        "Databases",
        "Frontend Engineering",
        "Full‑Stack Development",
        "GIS / Geospatial",
        "Performance Tuning",
        "Platform Architecture",
        "PostgreSQL",
        "Technical Leadership",
        "Backend & API Development",
        "Frontend Development",
        "Full‑Stack Product Development",
        "GIS & Geospatial Solutions",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/saved-4-000-hours-by-mentoring-and-growing-26/",
      "url": "https://engineer.company/portfolio/saved-4-000-hours-by-mentoring-and-growing-26/",
      "title": "Saved 4,000 hours by mentoring and growing a team from 2 to 18 members, optimizing workflows and fostering cross‑departmental collaboration.",
      "summary": "Saved ~4,000 hours by growing and mentoring a team from 2 to 18, redesigning workflows and building durable engineering capability.",
      "content_html": "<p><strong>Situation.</strong> A team of two couldn&rsquo;t keep up anymore. The product was pulling in more work than a pair could deliver, and the way the work happened — knowledge in people&rsquo;s heads, no real conventions — wasn&rsquo;t going to survive being scaled up. Adding bodies to a team that loose usually just makes the chaos bigger.</p>\n<p><strong>Task.</strong> The task was to grow the team and, at the same time, build the structure that would let a bigger group move faster instead of slower. Mentoring the new people was half of it. Fixing the workflows so nobody stalled waiting on someone else was the other half.</p>\n<p><strong>Action.</strong> The team grew from 2 to 18 over time, with the hiring and the mentoring treated as the same job: everyone who joined needed to be able to work unsupervised. Shared standards meant code and process looked the same regardless of who wrote them, and real effort went into the handovers between specialities, because that is where teams quietly lose their days. When something kept tripping people up, the fix went to the process rather than the symptom.</p>\n<p><strong>Result.</strong> The bigger, better‑mentored team, running on workflows that had actually been designed, saved on the order of 4,000 hours. But the number isn&rsquo;t really the point. What got built was durable engineering capability — a group that could carry the work with or without any one person in the room.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Agile & Scrum",
        "Mentoring & Coaching",
        "Project Management",
        "Team Leadership",
        "Technical Leadership",
        "Project Management (Agile)",
        "Team Building & Mentoring",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/optimized-forecasting-and-investment-strategies-for-11-electricity-27/",
      "url": "https://engineer.company/portfolio/optimized-forecasting-and-investment-strategies-for-11-electricity-27/",
      "title": "Optimized forecasting and investment strategies for 11 electricity grid operators, driving operational efficiency through data‑driven GIS solutions.",
      "summary": "Optimised forecasting and investment strategy for 11 electricity grid operators with data-driven GIS — trustworthy, targeted planning.",
      "content_html": "<p><strong>Situation.</strong> Electricity grid operators live and die on decisions about where to reinforce the network and where to put their money, and eleven of them were making those calls without much geospatial analysis underneath. They had the operational data. What they didn&rsquo;t have was a way to see it on the map, where the patterns actually live.</p>\n<p><strong>Task.</strong> The job was to sharpen their forecasting and their investment strategies with GIS — to turn tables of readings into something that showed them where capacity was getting tight, where risk was building, and where the next pound was best spent.</p>\n<p><strong>Action.</strong> Their operational data was brought together with geospatial modelling so the two reinforced each other. Instead of forecasting in the abstract, the grid could be looked at spatially and asked concrete questions: which stretches were heading toward their limits, which areas justified investment first. For eleven operators that meant fitting the analysis to how each of them actually ran their network, not handing everyone the same template and hoping it fit.</p>\n<p><strong>Result.</strong> The operators came away with forecasts they could trust and investment decisions that were aimed rather than hopeful. Grounding the planning in what the map showed made the whole thing more efficient — money and attention went where the data pointed instead of where habit did.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Data Analytics",
        "Data Engineering",
        "GIS / Geospatial",
        "Product & Requirements",
        "Stakeholder & Reporting",
        "Data Analytics & BI Dashboards",
        "GIS & Geospatial Solutions",
        "Product Strategy & Requirements"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/presented-200-ui-ux-improvements-for-the-gis-28/",
      "url": "https://engineer.company/portfolio/presented-200-ui-ux-improvements-for-the-gis-28/",
      "title": "Presented 200 UI/UX improvements for the GIS map application, boosting revenue by 10x through enhanced software features.",
      "summary": "Delivered 200 UI/UX improvements to a GIS map application, contributing to a 10x rise in revenue — careful UX as a commercial driver.",
      "content_html": "<p><strong>Situation.</strong> The map interface had grown powerful and, along the way, complicated. There were places where you could feel customers not getting the value that was sitting right there in front of them — good functionality trapped behind clumsy interactions. That gap between what the product could do and what people found easy to do was quietly costing us.</p>\n<p><strong>Task.</strong> The job was finding those gaps and pushing the fixes through: the usability work that would make the product easier to get value from and, not by coincidence, more valuable commercially.</p>\n<p><strong>Action.</strong> Rather than guess at what was wrong, the work went into the feedback and the usage data, and out of that came a backlog of 200 concrete UI/UX improvements. They weren&rsquo;t treated as equal — ranked by impact, with the ones that mattered argued for and taken through the team to ship as real features instead of a wishlist that sat in a document going stale.</p>\n<p><strong>Result.</strong> The interface got noticeably better to use, and the product&rsquo;s value followed: the work contributed to a tenfold rise in revenue. It&rsquo;s a case worth coming back to, because it makes the point cleanly — careful UX isn&rsquo;t cosmetic, it shows up on the invoice.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Data Analytics",
        "Design Systems & UI",
        "Frontend Engineering",
        "Product & Requirements",
        "Stakeholder & Reporting",
        "UX / UI Design",
        "Frontend Development",
        "Product Strategy & Requirements",
        "UI/UX Design & Design Systems"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/designed-and-managed-5-000-hours-of-map-29/",
      "url": "https://engineer.company/portfolio/designed-and-managed-5-000-hours-of-map-29/",
      "title": "Designed and managed 5,000 hours of map development, utilizing Agile methodologies such as Scrum and Jira for effective project management.",
      "summary": "Planned and ran 5,000 hours of map development with Scrum and Jira, keeping work visible and aligned to the roadmap.",
      "content_html": "<p><strong>Situation.</strong> The map wasn&rsquo;t built in a sprint. It was thousands of hours of work spread across a lot of people and a lot of months, and that kind of effort drifts if nobody is holding the line on scope and schedule. Left alone, it quietly becomes late.</p>\n<p><strong>Task.</strong> The responsibility was designing and running the development effort so it delivered on purpose rather than by luck — keeping the priorities honest and the progress visible to anyone who wanted to look.</p>\n<p><strong>Action.</strong> It ran on Agile: proper Scrum, with the ceremonies actually used rather than performed, and the work tracked in Jira so people could see where things stood without having to ask. Somewhere around 5,000 hours of map development went through that process. The emphasis was less about ceremony for its own sake and more about keeping priorities pointed at what mattered and catching drift early, while it was still cheap to fix.</p>\n<p><strong>Result.</strong> The 5,000 hours landed in a controlled, visible way instead of disappearing into a black box. The project management did what it&rsquo;s meant to do — and what mostly goes unnoticed when it works: it kept the work aligned to the goals and roughly on the timeline, without heroics at the end.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Agile & Scrum",
        "Product & Requirements",
        "Project Management",
        "Stakeholder & Reporting",
        "Team Leadership",
        "Product Strategy & Requirements",
        "Project Management (Agile)",
        "Team Building & Mentoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/wrote-50-000-words-of-comprehensive-software-documentation-30/",
      "url": "https://engineer.company/portfolio/wrote-50-000-words-of-comprehensive-software-documentation-30/",
      "title": "Wrote 50,000 words of comprehensive software documentation utilizing Markdown in GitHub, Craft, and Confluence, thereby ensuring the retention of knowledge and the transparency of the process.",
      "summary": "Wrote 50,000 words of software documentation in GitHub, Craft and Confluence, making knowledge durable, transparent and auditable.",
      "content_html": "<p><strong>Situation.</strong> Most of what mattered about the product and how the team worked lived in people&rsquo;s heads. That&rsquo;s fine right up until someone new joins, or someone leaves — and then onboarding crawls and a chunk of institutional memory is one resignation away from being gone for good.</p>\n<p><strong>Task.</strong> The aim was to get that knowledge out of heads and into documentation people would actually keep and use: clear enough to be read, structured enough to be maintained rather than abandoned after a month.</p>\n<p><strong>Action.</strong> Much of it was written by hand, around 50,000 words by the end, in Markdown across GitHub, Craft and Confluence depending on where each piece belonged. It covered the architecture, the processes and the practical how‑to material — the questions people kept asking. It was kept version‑controlled and, just as importantly, findable, because documentation nobody can locate might as well not exist.</p>\n<p><strong>Result.</strong> The knowledge stopped being fragile. New people got up to speed faster, the process became something you could point at instead of reconstruct from memory, and the whole thing turned auditable: you could see how and why work was done rather than take it on trust.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Documentation",
        "Mentoring & Coaching",
        "Team Leadership",
        "Technical Leadership",
        "Technical Documentation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/gathered-and-analyzed-business-requirements-to-translate-into-31/",
      "url": "https://engineer.company/portfolio/gathered-and-analyzed-business-requirements-to-translate-into-31/",
      "title": "Gathered and analyzed business requirements to translate into actionable features and user stories aligned with data governance standards.",
      "summary": "Turned business requirements into clear features and user stories aligned with data-governance standards, cutting ambiguity and rework.",
      "content_html": "<p><strong>Situation.</strong> Requirements tended to arrive as conversations — someone wanted something, roughly, and it fell to the developers to guess at the edges. Guessing means rework, and rework is about the most expensive way there is to build anything.</p>\n<p><strong>Task.</strong> The role sat between the business and the engineering, translating one into the other: turning loose needs into work a developer could pick up without guessing, and keeping it lined up with the data‑governance standards along the way.</p>\n<p><strong>Action.</strong> The requirements got worked out with the stakeholders directly, with the awkward questions asked early instead of discovered late, then written up as features and user stories that actually said what &ldquo;done&rdquo; meant. Each one was checked against the governance rules, because a feature that&rsquo;s useful but mishandles data isn&rsquo;t really finished. The whole aim was that someone could read a story and build the right thing the first time.</p>\n<p><strong>Result.</strong> Development ran off clear, agreed features instead of half‑understood asks. Ambiguity fell, and rework fell with it, and the delivery stayed pointed at the actual business goals — inside the data‑governance lines rather than tidied up to fit them afterward.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Agile & Scrum",
        "Data Governance",
        "Documentation",
        "Product & Requirements",
        "Project Management",
        "Stakeholder & Reporting",
        "Data Governance & Quality",
        "Product Strategy & Requirements",
        "Project Management (Agile)"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/managed-the-execution-of-30-successful-gis-projects-32/",
      "url": "https://engineer.company/portfolio/managed-the-execution-of-30-successful-gis-projects-32/",
      "title": "Managed the execution of 30 successful GIS projects, demonstrating leadership in delivering innovative solutions across the field.",
      "summary": "Delivered 30 successful GIS projects — a track record of consistent, innovative delivery that earned lasting client trust.",
      "content_html": "<p><strong>Situation.</strong> Mappitall made its money delivering GIS projects, and there were a lot of them running at once. The company&rsquo;s growth rode on getting them out the door reliably — not one flagship project done brilliantly, but a steady stream of them landing on time and holding together, which only happens when the coordination across the team is actually working.</p>\n<p><strong>Task.</strong> The responsibility was getting that portfolio delivered — keeping scope, people and timelines lined up across all of it, and giving the team the direction to ship.</p>\n<p><strong>Action.</strong> Across those years, the delivery of thirty GIS projects ran through this role. In practice that meant holding the scope steady when it wanted to creep, pointing the right people at the right work, and staying close enough to each project to catch trouble while it was still small. When something was going to slip, better to know early and reshuffle than find out at the deadline. A lot of the job was just keeping the plates spinning and the client&rsquo;s expectations honest about what was coming and when.</p>\n<p><strong>Result.</strong> All thirty came in. That consistency mattered more than any single project would have — a client who&rsquo;s seen you deliver thirty times over doesn&rsquo;t worry about the thirty‑first, and that reputation is a good part of why the company kept growing.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Agile & Scrum",
        "GIS / Geospatial",
        "Project Management",
        "Stakeholder & Reporting",
        "Team Leadership",
        "GIS & Geospatial Solutions",
        "Project Management (Agile)",
        "Team Building & Mentoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/led-company-growth-from-4-to-14-employees-33/",
      "url": "https://engineer.company/portfolio/led-company-growth-from-4-to-14-employees-33/",
      "title": "Led company growth from 4 to 14 employees by employing Agile methodologies and effective project management practices.",
      "summary": "Led company growth from 4 to 14 employees with Agile practices, scaling the team without losing quality or cohesion.",
      "content_html": "<p><strong>Situation.</strong> Mappitall was ready to grow, and there&rsquo;s a specific danger in that moment: you add people faster than you add process, and both the quality and the shared sense of how things are done start to fray. Four people who all know what everyone else is doing is a very different thing from fourteen who don&rsquo;t.</p>\n<p><strong>Task.</strong> Leading that growth was the job — bringing people in while keeping delivery disciplined and the team pulling in the same direction.</p>\n<p><strong>Action.</strong> The company went from four people to fourteen. That was hiring and onboarding done deliberately rather than in a panic, but the bigger piece was putting in the ways of working that let a team that size not trip over itself — Agile practices, real project management, the habits that keep everyone&rsquo;s work visible to everyone else. The tenth and fourteenth hires needed to walk into something that already had a shape, not to have to guess at how things were done.</p>\n<p><strong>Result.</strong> The team got to fourteen without the quality dropping or splintering into people who didn&rsquo;t know what the others were up to. The Agile side of it is what kept a bigger group productive — the process grew with the headcount instead of lagging behind it.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Agile & Scrum",
        "Mentoring & Coaching",
        "Project Management",
        "Team Leadership",
        "Technical Leadership",
        "Project Management (Agile)",
        "Team Building & Mentoring",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/improved-team-communication-and-collaboration-by-implementing-slack-34/",
      "url": "https://engineer.company/portfolio/improved-team-communication-and-collaboration-by-implementing-slack-34/",
      "title": "Improved team communication and collaboration by implementing Slack, Mattermost, 1Password, and Jira, saving 8,000 hours of labor.",
      "summary": "Saved ~8,000 hours by rolling out Slack, Mattermost, 1Password and Jira, turning chased information and redone work into real delivery.",
      "content_html": "<p><strong>Situation.</strong> As the team got bigger, the communication and the tooling hadn&rsquo;t kept up, and it showed. Things got said in one place and missed by the people who needed them, work got duplicated because nobody could see what someone else had already done, and coordinating anything took longer than the work itself. That kind of friction is invisible day to day, but it adds up to a lot of lost time.</p>\n<p><strong>Task.</strong> The aim was to fix how the team communicated and worked together, and claw back the time being quietly bled to all that friction.</p>\n<p><strong>Action.</strong> The tools got brought in and standardised, and — this is the part that actually matters — the practices for using them were set so they didn&rsquo;t just become another place to check. Slack and Mattermost for communication, 1Password so shared secrets weren&rsquo;t being passed around in ways nobody could track, Jira so work was tracked in one place instead of living in people&rsquo;s heads and inboxes. The tools were the easy bit; getting everyone to actually use them the same way was the real work.</p>\n<p><strong>Result.</strong> Communication and collaboration got noticeably better, and the streamlined setup saved something on the order of 8,000 hours of labour — time that had been going into chasing information and redoing work, now going into actual delivery.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Agile & Scrum",
        "Automation & CI/CD",
        "Documentation",
        "Project Management",
        "Security",
        "Team Leadership",
        "Project Management (Agile)",
        "Security & Access Management",
        "Team Building & Mentoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/led-the-development-deployment-and-support-of-over-35/",
      "url": "https://engineer.company/portfolio/led-the-development-deployment-and-support-of-over-35/",
      "title": "Led the development, deployment, and support of over 30 GIS projects, demonstrating expertise in PostgreSQL, Bash, Python, JavaScript, GDAL, ArcGIS, PostGIS, and Mapbox technologies.",
      "summary": "Led development, deployment and support of 30+ GIS projects with PostgreSQL, Python, GDAL, ArcGIS, PostGIS and Mapbox across the lifecycle.",
      "content_html": "<p><strong>Situation.</strong> The company&rsquo;s whole output was made‑to‑measure GIS — custom mapping and spatial systems built to a client&rsquo;s specific problem, then kept running once they were live. Building the thing is only half of it; a geospatial project that ships and then falls over in production hasn&rsquo;t really been delivered.</p>\n<p><strong>Task.</strong> These projects ran end to end through this role — the development, the deployment, and the support once they were live — with the technical direction across a fairly wide stack.</p>\n<p><strong>Action.</strong> Delivery ran on more than thirty GIS projects, hands‑on across the stack the whole way. PostgreSQL with PostGIS underneath for the spatial data, GDAL/OGR for moving it between formats — shapefiles, GeoJSON, GeoTIFF, KML, vector tiles — and QGIS and JOSM for the data work itself. On the front of it, web maps built on Mapbox GL and Leaflet, sometimes against the ArcGIS or HERE APIs, with the app layer in JavaScript, Python, PHP and SQL. The work ranged from 2D and 3D digital mapping through LiDAR processing, georectification and vectorisation to indoor mapping and navigation. And the responsibility ran past the point of shipping — the deployment and the ongoing support in production were part of it too, so problems weren&rsquo;t handed off; the decisions had to be lived with.</p>\n<p><strong>Result.</strong> Thirty‑plus projects built, deployed and supported across that whole range. Being on the hook for the full lifecycle rather than just the build is what kept the quality honest — you design differently when you know you&rsquo;re the one getting the call if it breaks.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Data Engineering",
        "Databases",
        "Full‑Stack Development",
        "GIS / Geospatial",
        "PostgreSQL",
        "Python",
        "Reliability & Backups",
        "SQL",
        "Team Leadership",
        "Technical Leadership",
        "Web Development",
        "Backend & API Development",
        "Data Pipeline Development (ETL/ELT)",
        "Full‑Stack Product Development",
        "GIS & Geospatial Solutions",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/contributed-to-user-interface-design-processes-ensuring-intuitive-36/",
      "url": "https://engineer.company/portfolio/contributed-to-user-interface-design-processes-ensuring-intuitive-36/",
      "title": "Contributed to user interface design processes, ensuring intuitive, visually appealing, and user‑friendly project interfaces.",
      "summary": "Contributed to UI design, delivering intuitive, polished and user-friendly interfaces by asking usability questions during design.",
      "content_html": "<p><strong>Situation.</strong> The interfaces across our projects were uneven. Some were fine, some had clearly been built by engineers thinking about the data model rather than the person who&rsquo;d have to use it, and the design decisions weren&rsquo;t always made with the end user in the room. On a web‑map product especially, the map is the easy part — it&rsquo;s the controls, the filtering and the flow around it where people get lost.</p>\n<p><strong>Task.</strong> Getting involved in the UI design process was part of the role — to help make the interfaces something people actually found intuitive and pleasant to use, not just functional.</p>\n<p><strong>Action.</strong> The role wasn&rsquo;t the designer&rsquo;s, but it sat in the design process and brought an engineering perspective — pushing on layout, on the flow through a task, on whether a screen was actually clear or just familiar to the people who&rsquo;d built it. These were React, Angular and Vue front‑ends sitting over Mapbox and Leaflet maps, and a lot of the usability lived in the details: how you filtered a dataset, how you moved between floors on an indoor map, whether the thing told you what it was doing. Mostly it meant asking the dumb‑user questions early, while they were still cheap to fix, instead of after release when the confusion came back as support tickets.</p>\n<p><strong>Result.</strong> The interfaces got more intuitive and more polished, which end users felt directly, and it lifted the overall quality of what got put out. Getting the usability questions asked during design rather than after release is most of what made the difference.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Design Systems & UI",
        "Frontend Engineering",
        "GIS / Geospatial",
        "UX / UI Design",
        "Web Development",
        "Frontend Development",
        "UI/UX Design & Design Systems"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/guided-the-execution-of-numerous-company-projects-offering-37/",
      "url": "https://engineer.company/portfolio/guided-the-execution-of-numerous-company-projects-offering-37/",
      "title": "Guided the execution of numerous company projects, offering expert support in the software development phases.",
      "summary": "Guided execution of many company projects with expert support through the hardest software-development phases, steadying delivery.",
      "content_html": "<p><strong>Situation.</strong> There were a lot of projects running at the same time, each in the middle of its development phase, and they all needed steady technical guidance to stay on the rails. Left alone, projects drift — a wrong approach taken early gets expensive by the time anyone notices.</p>\n<p><strong>Task.</strong> Guiding that execution was the job — being the technical support the teams could lean on through the development phases.</p>\n<p><strong>Action.</strong> The work stayed hands‑on across a lot of the company&rsquo;s projects at once. That meant unblocking people when they were stuck, looking hard at an approach before too much got built on top of it, and generally staying close enough to catch a project heading the wrong way while it was still a course correction and not a rebuild. The idea was to be available rather than a gate — to keep things moving, not to make everything wait on one person.</p>\n<p><strong>Result.</strong> Projects moved through their tricky development stages more smoothly with someone experienced to lean on at the right moments, and that steadied delivery right across the company&rsquo;s portfolio.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Mentoring & Coaching",
        "Project Management",
        "Team Leadership",
        "Technical Leadership",
        "Project Management (Agile)",
        "Team Building & Mentoring",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/overhauled-internal-processes-saving-8-000-hours-by-38/",
      "url": "https://engineer.company/portfolio/overhauled-internal-processes-saving-8-000-hours-by-38/",
      "title": "Overhauled internal processes, saving 8,000 hours by improving software architecture, systems, and scheduling efficiency.",
      "summary": "Overhauled internal processes to save ~8,000 hours, improving software architecture, systems and scheduling efficiency.",
      "content_html": "<p><strong>Situation.</strong> The way things were done internally had accumulated the usual cruft — software architecture that had grown by accretion rather than design, systems that worked but not efficiently, scheduling that left people either waiting or slammed. None of it was on fire, which is exactly why it had been left alone, but it was quietly costing a lot of time.</p>\n<p><strong>Task.</strong> The aim was to overhaul those processes — to go find the waste and take it out rather than keep paying for it.</p>\n<p><strong>Action.</strong> The internal processes got reworked across three fronts: the software architecture, so it was something you could reason about and build on instead of work around; the systems, streamlined so the routine work stopped taking longer than it should; and the scheduling, so capacity was actually matched to the work. And the changes were made to stick — embedded in how the team operated rather than left as a memo everyone nodded at and forgot — because process improvements that aren&rsquo;t made permanent just decay back to the old way.</p>\n<p><strong>Result.</strong> The overhaul saved roughly 8,000 hours by making the architecture, systems and scheduling meaningfully more efficient. That&rsquo;s capacity that went straight back into higher‑value work instead of into overhead nobody had thought to question.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Infrastructure",
        "Performance Tuning",
        "Platform Architecture",
        "Project Management",
        "Solution Architecture",
        "Technical Leadership",
        "DevOps & CI/CD Automation",
        "Platform & Solution Architecture",
        "Project Management (Agile)",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/administered-network-infrastructure-for-over-1-000-servers-39/",
      "url": "https://engineer.company/portfolio/administered-network-infrastructure-for-over-1-000-servers-39/",
      "title": "Administered network infrastructure for over 1,000 servers, ensuring optimal system deployment, security, and troubleshooting.",
      "summary": "Administered network infrastructure for 1,000+ servers, ensuring reliable deployment, solid security and prompt troubleshooting.",
      "content_html": "<p><strong>Situation.</strong> The company&rsquo;s operations sat on top of a big server estate — over a thousand of them — and an estate that size doesn&rsquo;t stay reliable on its own. Deployment, security and the steady stream of things that go wrong all need someone running them with actual discipline, or the whole thing gets flaky and nobody&rsquo;s quite sure why.</p>\n<p><strong>Task.</strong> Administering that network infrastructure was the job — keeping it secure, keeping it reliable, keeping it consistent at a scale where inconsistency is what kills you.</p>\n<p><strong>Action.</strong> The network infrastructure ran across more than a thousand servers. How systems got deployed was standardised, so a server came up the same predictable way instead of each one being a little bit bespoke; the security was hardened rather than trusting that nobody would come looking; and the troubleshooting got handled when something did break. At that scale the standardisation is what saves you — a thousand snowflakes is unmanageable, a thousand of the same thing is just work.</p>\n<p><strong>Result.</strong> The estate ran with reliable deployment, solid security and problems that got dealt with promptly instead of festering. That&rsquo;s the kind of infrastructure work that&rsquo;s invisible when it&rsquo;s going well, which is the point — it was the stable backbone everything else at the company ran on.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Infrastructure",
        "Linux & Servers",
        "Networking & VPN",
        "Reliability & Backups",
        "Security",
        "System Administration",
        "Networking & VPN Setup",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/established-a-technical-support-department-servicing-over-10-40/",
      "url": "https://engineer.company/portfolio/established-a-technical-support-department-servicing-over-10-40/",
      "title": "Established a Technical Support department, servicing over 10,000 clients with IT support and troubleshooting solutions.",
      "summary": "Established a Technical Support department serving 10,000+ clients, turning support from reactive fixes into a scalable company strength.",
      "content_html": "<p><strong>Situation.</strong> The client base was growing and needed dependable technical support, and there just wasn&rsquo;t a dedicated function to give it to them at any real scale. Support was happening ad hoc, which works for a handful of clients and quietly falls apart as the numbers climb.</p>\n<p><strong>Task.</strong> Standing up an actual technical‑support capability — one that could serve a large and still‑growing client base — was the job.</p>\n<p><strong>Action.</strong> A Technical Support department got built from nothing. That meant deciding how it would actually work before hiring into it — the process for how a request came in and got resolved, the tooling to handle volume, the standard for what good support looked like — and then building the capacity to deliver IT support and troubleshooting to a lot of clients at once. Starting from scratch was the advantage, honestly; it could be designed for the scale the company was heading toward instead of patching something that had grown up by accident.</p>\n<p><strong>Result.</strong> The department ended up serving over ten thousand clients with reliable support and troubleshooting. Support went from a thing done reactively to a genuine strength of the company — something that scaled with the client base instead of buckling under it.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Mentoring & Coaching",
        "Product & Requirements",
        "Project Management",
        "Stakeholder & Reporting",
        "System Administration",
        "Team Leadership",
        "IT Support & Helpdesk",
        "Project Management (Agile)",
        "Team Building & Mentoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/automated-ssl-tls-certificate-creation-for-100-docker-41/",
      "url": "https://engineer.company/portfolio/automated-ssl-tls-certificate-creation-for-100-docker-41/",
      "title": "Automated SSL/TLS certificate creation for 100 Docker applications, ensuring secure connections across Ubuntu Linux hosts.",
      "summary": "Automated SSL/TLS certificates for 100 Docker apps on Ubuntu Linux, ending manual renewals and the outages expired certs cause.",
      "content_html": "<p><strong>Situation.</strong> A hundred Docker applications all needed SSL/TLS certificates, and certificates are the kind of thing that&rsquo;s fine right up until they aren&rsquo;t. Issuing and renewing a hundred of them by hand is slow, it&rsquo;s dull, and it&rsquo;s exactly the sort of manual job where one forgotten renewal takes an app down with an expiry error at the worst possible moment.</p>\n<p><strong>Task.</strong> The goal was certificate creation and renewal automated — every app with valid, trusted encryption, and nobody having to remember to do anything.</p>\n<p><strong>Action.</strong> An ACME‑based workflow handled the whole lifecycle for the hundred Docker applications — creating the certificates and renewing them before they lapsed — and deployed them automatically across the Ubuntu Linux hosts, which were running a mix of Apache and Nginx. The whole aim was to take the human out of it, because the human is the part that forgets.</p>\n<p><strong>Result.</strong> All hundred applications kept valid certificates and secure connections on their own. The manual certificate work just disappeared, and with it the whole category of outage where something breaks not because it failed but because a cert quietly expired and nobody noticed.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Containers (Docker/Kubernetes)",
        "DevOps",
        "Infrastructure",
        "Linux & Servers",
        "Reliability & Backups",
        "Security",
        "Containerization & Orchestration",
        "DevOps & CI/CD Automation",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/streamlined-ci-cd-processes-saving-4-000-hours-42/",
      "url": "https://engineer.company/portfolio/streamlined-ci-cd-processes-saving-4-000-hours-42/",
      "title": "Streamlined CI/CD processes, saving 4,000 hours by introducing automation in software development pipelines.",
      "summary": "Streamlined CI/CD to save ~4,000 hours, making releases faster and more reliable so the team could ship with confidence.",
      "content_html": "<p><strong>Situation.</strong> Getting software out the door relied on manual, inconsistent steps — someone remembering the sequence, doing it slightly differently each time — and it slowed releases down and ate engineering hours that should have gone into building things.</p>\n<p><strong>Task.</strong> The aim was to streamline the CI/CD process and get automation into the pipelines, so releases stopped being a manual ritual.</p>\n<p><strong>Action.</strong> Automated build, test and deployment pipelines went in, so the path from a change to it running in production was standardised instead of improvised. The repetitive manual steps — the ones that were slow and, worse, done differently depending on who was doing them — came out. Once the pipeline is doing it the same way every time, a whole class of &ldquo;it worked on my machine&rdquo; and half‑remembered deploy steps just stops happening.</p>\n<p><strong>Result.</strong> The automation gave back roughly 4,000 hours and made releases both faster and more reliable. The team could ship without bracing for it — the confidence came from the process being consistent, not from everyone being careful.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Reliability & Backups",
        "Technical Leadership",
        "Testing & QA",
        "DevOps & CI/CD Automation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/streamlined-data-analysis-and-software-development-processes-saving-43/",
      "url": "https://engineer.company/portfolio/streamlined-data-analysis-and-software-development-processes-saving-43/",
      "title": "Streamlined data analysis and software development processes, saving 4,000 hours by introducing GitHub, GitLab, Bash, and Python CI/CD practices.",
      "summary": "Saved ~4,000 hours by introducing GitHub, GitLab, Bash and Python CI/CD, speeding data analysis and development while adding consistency.",
      "content_html": "<p><strong>Situation.</strong> Both the data‑analysis work and the software development were being held back by the same thing: manual processes. Work moved from development to delivery in a slow, inconsistent way, and the analysis side had its own pile of repetitive steps someone was doing by hand every time.</p>\n<p><strong>Task.</strong> The aim was to streamline both by bringing modern automation and CI/CD practices to workflows that hadn&rsquo;t had them.</p>\n<p><strong>Action.</strong> CI/CD practices went in, built on GitHub and GitLab, with Bash and Python doing the automation work underneath. The repetitive steps across both the data‑analysis and the development workflows got automated, and how work moved from development through to delivery got standardised so it was the same every time rather than reinvented per project. Bringing the analysis side into the same disciplined pipeline as the dev work was a big part of it — it had been treated as a separate, more manual world.</p>\n<p><strong>Result.</strong> The streamlined processes saved roughly 4,000 hours and sped up both the data analysis and the software development, and just as usefully made what got shipped more consistent — fewer surprises from work that had been done a slightly different way each time.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Data Analytics",
        "Data Engineering",
        "Data Pipelines (ETL/ELT)",
        "DevOps",
        "Python",
        "Data Analytics & BI Dashboards",
        "Data Pipeline Development (ETL/ELT)",
        "DevOps & CI/CD Automation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/developed-a-data-analytics-reporting-system-increasing-quarterly-44/",
      "url": "https://engineer.company/portfolio/developed-a-data-analytics-reporting-system-increasing-quarterly-44/",
      "title": "Developed a Data Analytics reporting system, increasing quarterly software revenue by 400% through Python‑based PDF reports.",
      "summary": "Built a Python data-analytics reporting system that raised quarterly software revenue by 400% with clear, timely PDF reports.",
      "content_html": "<p><strong>Situation.</strong> Stakeholders weren&rsquo;t getting analytics in any timely, readable form. The data existed, but turning it into something you could actually make a decision from was slow and manual, so visibility into how things were performing lagged, and the commercial decisions lagged with it.</p>\n<p><strong>Task.</strong> Building a data‑analytics reporting system — one that turned raw data into clear, regular insight without someone hand‑assembling it each time — was the job.</p>\n<p><strong>Action.</strong> A reporting system generated PDF reports in Python, automating the whole chain: pulling the data, running the analysis, and presenting it in a clean, consistent format stakeholders could actually read. The point was regularity and clarity — the same professional report landing predictably, so the numbers became something people looked at as a matter of course rather than something they had to go and dig for.</p>\n<p><strong>Result.</strong> That reporting is what drove quarterly software revenue up by 400%. Making the analytics better and faster wasn&rsquo;t a back‑office nicety — put clear, timely numbers in front of the people making commercial decisions and the decisions get better, and here that showed up directly on the revenue.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Backend Engineering",
        "Data Analytics",
        "Data Engineering",
        "Product & Requirements",
        "Python",
        "Stakeholder & Reporting",
        "Backend & API Development",
        "Data Analytics & BI Dashboards",
        "Product Strategy & Requirements"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/automated-data-processing-tasks-using-shell-scripting-pl-45/",
      "url": "https://engineer.company/portfolio/automated-data-processing-tasks-using-shell-scripting-pl-45/",
      "title": "Automated data processing tasks using Shell scripting, PL/pgSQL, Python, and Transact‑SQL, increasing productivity and efficiency.",
      "summary": "Automated data-processing tasks with Shell, PL/pgSQL, Python and Transact-SQL, lifting productivity and ending small recurring errors.",
      "content_html": "<p><strong>Situation.</strong> There was a steady load of recurring data‑processing work being done by hand. Manual data work has two problems at once: it eats time, and it&rsquo;s inconsistent — do the same task by hand enough times and it&rsquo;ll get done slightly differently, and some of those differences are errors.</p>\n<p><strong>Task.</strong> The aim was to automate these tasks, both to get the time back and to make them reliable.</p>\n<p><strong>Action.</strong> The data‑processing work got automated across the databases and systems it touched, using whatever fit the job — Shell scripting for the glue, PL/pgSQL and Transact‑SQL down in the databases, Python where it needed more than SQL could give. Manual steps got replaced with jobs that ran the same way every time, which is the whole point: a script doesn&rsquo;t get bored, doesn&rsquo;t skip a step, and doesn&rsquo;t do it differently on a Friday afternoon.</p>\n<p><strong>Result.</strong> Productivity and efficiency both went up, the manual effort came off people&rsquo;s plates, and the data processing became consistent and dependable instead of a source of small, recurring errors.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Data Engineering",
        "Data Pipelines (ETL/ELT)",
        "Databases",
        "PostgreSQL",
        "Python",
        "SQL",
        "Data Pipeline Development (ETL/ELT)",
        "Database Administration (DBA)",
        "DevOps & CI/CD Automation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/enhanced-project-efficiency-saving-150-hours-per-month-46/",
      "url": "https://engineer.company/portfolio/enhanced-project-efficiency-saving-150-hours-per-month-46/",
      "title": "Enhanced project efficiency, saving 150 hours per month across 30 projects by optimizing workflows and resource management.",
      "summary": "Saved ~150 hours per month across 30 projects by optimising workflows and resource management — a recurring, portfolio-wide gain.",
      "content_html": "<p><strong>Situation.</strong> Across a portfolio of around thirty projects, time was being lost every single month to workflows that had never been optimised and resource management that was uneven — some people underused, some overloaded, work planned differently from one project to the next.</p>\n<p><strong>Task.</strong> The aim was to make the portfolio more efficient and recover the time that was going missing month after month.</p>\n<p><strong>Action.</strong> Going through the thirty projects, the workflows and the resource management got worked on together — taking out the bottlenecks, balancing the workloads so the same few people weren&rsquo;t always the constraint, and standardising how work got planned and run so each project wasn&rsquo;t its own special case. Thirty projects each losing a bit of time adds up; the fix was mostly about making the good practices consistent rather than inventing anything clever.</p>\n<p><strong>Result.</strong> The changes saved around 150 hours a month across the portfolio. That&rsquo;s a recurring monthly saving, not a one‑off — thirty projects running leaner, month in and month out.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Agile & Scrum",
        "Automation & CI/CD",
        "Performance Tuning",
        "Project Management",
        "Team Leadership",
        "Project Management (Agile)",
        "Team Building & Mentoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/managed-a-team-delivering-it-support-data-recovery-47/",
      "url": "https://engineer.company/portfolio/managed-a-team-delivering-it-support-data-recovery-47/",
      "title": "Managed a team delivering IT support, data recovery, and hardware repair services to over 1,000 clients, ensuring high‑quality service.",
      "summary": "Led a team delivering IT support, data recovery and hardware repair to 1,000+ clients, building a reputation for reliable service.",
      "content_html": "<p><strong>Situation.</strong> The company was the place a large base of clients came to when their IT broke — support, data recovery, hardware repair, the full spread. Meeting that kind of steady, unglamorous demand isn&rsquo;t about heroics; it&rsquo;s about having a team that runs well day after day, because the work never really stops coming.</p>\n<p><strong>Task.</strong> Managing the team that delivered all that was the job, and keeping the quality consistent whether it was a quiet week or everything arriving at once.</p>\n<p><strong>Action.</strong> The team ran IT support, data recovery and hardware repair for over a thousand clients. A lot of that was the organising underneath — making sure work got picked up and not dropped, setting a standard for what &ldquo;fixed&rdquo; actually meant so people weren&rsquo;t getting half‑repairs back, and keeping the team effective when the queue was long. Data recovery especially is work you can&rsquo;t be casual about; it&rsquo;s usually someone&rsquo;s photos or their business sitting on that drive, and they&rsquo;re already having a bad day by the time they reach you.</p>\n<p><strong>Result.</strong> The team served over a thousand clients and built a real reputation for reliable service. In that kind of business the reputation is everything — people come back, and they tell others, precisely because the last time something broke you actually sorted it.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Mentoring & Coaching",
        "Stakeholder & Reporting",
        "System Administration",
        "Team Leadership",
        "Hardware Repair & Data Recovery",
        "IT Support & Helpdesk",
        "Team Building & Mentoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/configured-and-deployed-1-000-wi-fi-routers-48/",
      "url": "https://engineer.company/portfolio/configured-and-deployed-1-000-wi-fi-routers-48/",
      "title": "Configured and deployed 1,000 Wi‑Fi routers, improving network accessibility and performance for clients.",
      "summary": "Configured and deployed 1,000 Wi-Fi routers with a standard setup, giving clients dependable wireless access and performance.",
      "content_html": "<p><strong>Situation.</strong> Clients needed wireless that just worked, and that came down to configuring and rolling out a large number of Wi‑Fi routers — and doing it the same careful way every time, because a router set up sloppily is either insecure or slow, and usually you find out which one later.</p>\n<p><strong>Task.</strong> Configuring and deploying those routers to give clients better network access and performance was the job.</p>\n<p><strong>Action.</strong> A thousand Wi‑Fi routers got configured and deployed. The trick at that number is standardising the setup — a consistent, secure, sensible configuration — rather than tuning each one from scratch on the day, because a thousand hand‑crafted routers is a thousand different things to support later. So they were set up for security and performance the same way each time, and rolled out reliably across client sites.</p>\n<p><strong>Result.</strong> The thousand routers gave clients dependable wireless — better access, better performance — and did it consistently, because the setup was standard rather than improvised. A router nobody has to think about again is the goal; most of these got there.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Infrastructure",
        "Networking & VPN",
        "Reliability & Backups",
        "Security",
        "System Administration",
        "IT Support & Helpdesk",
        "Networking & VPN Setup"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/managed-4-000-computer-repairs-ensuring-rapid-and-49/",
      "url": "https://engineer.company/portfolio/managed-4-000-computer-repairs-ensuring-rapid-and-49/",
      "title": "Managed 4,000 computer repairs, ensuring rapid and effective resolution of hardware and software issues.",
      "summary": "Handled 4,000 computer repairs with fast, effective resolution of hardware and software issues, upholding a name for dependable service.",
      "content_html": "<p><strong>Situation.</strong> There was a constant stream of computer repairs coming through — four thousand of them over time — and every one was someone waiting to get their machine back and get on with their day. Hardware faults, software faults, the whole mix, and clients judged the shop on how fast and how properly they got fixed.</p>\n<p><strong>Task.</strong> Managing those repairs was the job — keeping problems getting resolved quickly and, just as importantly, correctly.</p>\n<p><strong>Action.</strong> Four thousand computer repairs went through — diagnosing the hardware and software faults, organising the workflow so machines moved through instead of piling up, and holding the quality so a repair was actually done right rather than sent back out to fail again next week. A repair that comes back is worse than a slow one; it costs the client a second trip and costs the shop the trust. So the throughput mattered, but never at the expense of fixing it the first time.</p>\n<p><strong>Result.</strong> All four thousand got resolved, and quickly. People got their machines back working and got on with things, and the company&rsquo;s name for dependable service held up — which in a repair shop is the whole business.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "System Administration",
        "Hardware Repair & Data Recovery",
        "IT Support & Helpdesk"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/administered-100-bare-bone-servers-physical-networks-and-50/",
      "url": "https://engineer.company/portfolio/administered-100-bare-bone-servers-physical-networks-and-50/",
      "title": "Administered 100 Bare Bone servers, physical networks, and IP telephony systems, ensuring robust infrastructure for company growth.",
      "summary": "Administered 100 bare-bone servers, physical networks and IP telephony, providing the robust foundation the company grew on.",
      "content_html": "<p><strong>Situation.</strong> Underneath everything the company did was the physical stuff — servers, the actual networks, the IP‑telephony — and it all had to just run. That layer is invisible when it works and extremely visible the moment it doesn&rsquo;t, and the company&rsquo;s growth was resting on it staying dependable.</p>\n<p><strong>Task.</strong> Administering that infrastructure and keeping it stable as the company grew was the job.</p>\n<p><strong>Action.</strong> A hundred bare‑bone servers, along with the physical networks and the IP‑telephony systems, were looked after — the setup, the maintenance, the troubleshooting when something went wrong. Bare‑bone servers mean dealing with the hardware directly, so there&rsquo;s a hands‑on, physical side to it: the cabling, the boxes, the phone system that everyone notices the second a call drops. The job was to keep all of it boring, in the good sense.</p>\n<p><strong>Result.</strong> The servers, the networks and the telephony ran reliably, and that dependable physical foundation is what let the company keep growing without the ground shifting underneath it.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Infrastructure",
        "Linux & Servers",
        "Networking & VPN",
        "Reliability & Backups",
        "System Administration",
        "Networking & VPN Setup"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/developed-100-web-applications-using-html-html5-css-51/",
      "url": "https://engineer.company/portfolio/developed-100-web-applications-using-html-html5-css-51/",
      "title": "Developed 100 web applications using HTML/HTML5, CSS/SCSS, Django, WordPress, and Joomla frameworks, ensuring diverse online presence.",
      "summary": "Developed 100 web applications with HTML5, CSS/SCSS, Django, WordPress and Joomla, matching each solution to the client's real needs.",
      "content_html": "<p><strong>Situation.</strong> Clients wanted web applications, and they wanted very different things — different scales, different budgets, different levels of &ldquo;just get me online&rdquo; versus &ldquo;build me something custom.&rdquo; Meeting that meant being versatile rather than forcing every client down the same technology.</p>\n<p><strong>Task.</strong> Building web applications that gave each client a diverse, effective online presence was the job.</p>\n<p><strong>Action.</strong> A hundred web applications got built, and the point was matching the tool to the job rather than having a favourite. Where a client needed something custom, that was HTML/HTML5, CSS/SCSS and Django; where they needed something more standard that they could also manage themselves, WordPress or Joomla was the faster, more sensible answer. Part of the skill was knowing which was which — talking a client out of a bespoke build they didn&rsquo;t need, or into one they did.</p>\n<p><strong>Result.</strong> The hundred applications gave clients a varied, capable online presence, each delivered on the technology that actually fit it. Matching the approach to the client rather than the other way round is what made them effective rather than just delivered.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Frontend Engineering",
        "Full‑Stack Development",
        "Python",
        "Web Development",
        "Frontend Development",
        "Full‑Stack Product Development",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/designed-30-websites-delivering-unique-and-flexible-solutions-52/",
      "url": "https://engineer.company/portfolio/designed-30-websites-delivering-unique-and-flexible-solutions-52/",
      "title": "Designed 30 websites, delivering unique and flexible solutions by converting Photoshop designs to HTML.",
      "summary": "Designed 30 websites, converting Photoshop designs to HTML into distinctive, polished and maintainable web presences.",
      "content_html": "<p><strong>Situation.</strong> Clients came in with a look they wanted — often a finished visual design — and needed a website built faithfully from it. The gap between a design file and a working site is where a lot of quality is won or lost: it&rsquo;s easy to ship something that&rsquo;s roughly right and subtly wrong.</p>\n<p><strong>Task.</strong> Designing the sites and converting the designs into accurate, flexible implementations was the job.</p>\n<p><strong>Action.</strong> Thirty websites got designed and the Photoshop designs converted into HTML by hand. Faithful was the standard — the spacing, the type, the details the designer actually intended, not an approximation of them — but so was flexible, because a site that matches the mockup pixel‑for‑pixel and then falls apart the moment the content changes hasn&rsquo;t really been built well. So the aim was markup that stayed true to the design and stayed maintainable afterwards.</p>\n<p><strong>Result.</strong> The thirty sites matched their designs and stayed workable — distinctive, polished web presences that didn&rsquo;t break the first time someone edited them. Faithful to the design and still maintainable is the balance that mattered, and that&rsquo;s where these landed.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Design Systems & UI",
        "Frontend Engineering",
        "UX / UI Design",
        "Web Development",
        "Frontend Development",
        "UI/UX Design & Design Systems",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/administered-40-websites-on-ubuntu-linux-hosting-servers-53/",
      "url": "https://engineer.company/portfolio/administered-40-websites-on-ubuntu-linux-hosting-servers-53/",
      "title": "Administered 40 websites on Ubuntu Linux hosting servers with Apache and Nginx, ensuring high availability and performance.",
      "summary": "Administered 40 websites on Ubuntu Linux with Apache and Nginx at high availability and performance — hosting clients never had to think about.",
      "content_html": "<p><strong>Situation.</strong> There was a portfolio of live websites that needed to stay up and stay fast — and hosting is another of those jobs that&rsquo;s invisible until a site goes down, at which point it&rsquo;s the only thing anyone cares about.</p>\n<p><strong>Task.</strong> Administering those sites and keeping them highly available and quick was the job.</p>\n<p><strong>Action.</strong> Forty websites ran on Ubuntu Linux hosting servers, on a mix of Apache and Nginx — the configuration, the performance tuning, the ongoing maintenance to keep them reliable under real traffic. Real traffic is the operative bit: a site that&rsquo;s fine when nobody&rsquo;s using it and falls over when they are hasn&rsquo;t been administered, it&rsquo;s just been left alone. So the work was keeping them healthy under actual load.</p>\n<p><strong>Result.</strong> All forty ran with high availability and good performance, which gave clients hosting they didn&rsquo;t have to think about. Stable and dependable under real use is the entire point of hosting, and that&rsquo;s what these delivered.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Infrastructure",
        "Linux & Servers",
        "Performance Tuning",
        "Reliability & Backups",
        "System Administration",
        "Web Development",
        "Site Reliability & Monitoring",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/engineered-600-pl-pgsql-based-etl-elt-pipelines-54/",
      "url": "https://engineer.company/portfolio/engineered-600-pl-pgsql-based-etl-elt-pipelines-54/",
      "title": "Engineered 600 PL/pgSQL‑based ETL/ELT pipelines to streamline complex data processing workflows across multiple PostgreSQL development and production environments.",
      "summary": "Engineered 600 PL/pgSQL ETL/ELT pipelines across PostgreSQL dev and prod, cutting average execution time 35% and lifting throughput 50%+.",
      "content_html": "<p><strong>Situation.</strong> The organization was managing large volumes of transactional and analytical data across multiple PostgreSQL development and production environments. The existing data pipelines were fragmented, lacked consistency, and were causing performance bottlenecks, leading to delays in business‑critical reporting and decision‑making.</p>\n<p><strong>Task.</strong> The task was to design and implement robust, scalable, and efficient ETL/ELT pipelines using PL/pgSQL to streamline complex data ingestion, transformation, and loading workflows. A key objective was to improve query performance and ensure data integrity across all environments.</p>\n<p><strong>Action.</strong> Over 600 PL/pgSQL‑based ETL/ELT pipelines were engineered to automate the extraction, transformation, and loading of data from various sources. Key technical implementations included:</p>\n<ul>\n<li>Leveraged partitioned tables and materialized views to optimize read performance for large datasets.</li>\n<li>Applied primary and foreign key constraints to maintain referential integrity during transformations.</li>\n<li>Designed unique and composite indexes to speed up JOIN operations and complex filtering criteria.</li>\n<li>Incorporated exception handling and transactional control to ensure fault tolerance and rollback in case of failures.</li>\n<li>Enabled incremental loads using change data capture (CDC) mechanisms and timestamp‑based deltas, reducing processing time by over 60%.</li>\n<li>Developed automated logging and auditing procedures to track pipeline execution, monitor anomalies, and facilitate debugging.</li>\n</ul>\n<p><strong>Result.</strong> The new ETL/ELT framework significantly improved the consistency, reliability, and performance of data processing workflows:</p>\n<ul>\n<li>Achieved a 35% reduction in average pipeline execution time.</li>\n<li>Increased data pipeline throughput by over 50%, enabling near real‑time data availability for reporting.</li>\n<li>Reduced data quality issues by eliminating over 90% of transformation errors through better constraint enforcement and validation logic.</li>\n<li>Enhanced maintainability and scalability, supporting future data model changes with minimal rework.</li>\n</ul>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Data Engineering",
        "Data Governance",
        "Data Pipelines (ETL/ELT)",
        "Databases",
        "Performance Tuning",
        "PostgreSQL",
        "SQL",
        "Data Governance & Quality",
        "Data Pipeline Development (ETL/ELT)",
        "Database Administration (DBA)"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/designed-a-json-passthrough-architecture-where-postgresql-functions-58/",
      "url": "https://engineer.company/portfolio/designed-a-json-passthrough-architecture-where-postgresql-functions-58/",
      "title": "Designed a JSON passthrough architecture where PostgreSQL functions return complete JSON forwarded verbatim by the Go API, eliminating intermediate unmarshalling and decoupling the frontend from schema changes.",
      "summary": "Designed a JSON passthrough where PostgreSQL functions return complete JSON forwarded verbatim by the Go API, decoupling the frontend from schema.",
      "content_html": "<p><strong>Situation.</strong> The usual way data gets from a database to a browser is a relay race of transformations. The database hands the API rows, the API unmarshals them into structs, reshapes them, serialises them back to JSON, and only then do they go out. Every one of those hops is code you write, code you test, and one more place where the API&rsquo;s idea of the data and the database&rsquo;s idea of it can drift apart.</p>\n<p><strong>Task.</strong> The idea was to skip the relay race. If the database could return the finished response, the API could just pass it along, and the frontend could depend on the database&rsquo;s shape directly instead of on a hand‑maintained copy of it living in Go.</p>\n<p><strong>Action.</strong> So it was built as a straight passthrough. The PostgreSQL functions assemble the whole response as JSON — the shaping is a SQL concern, done where the data already is. The Go handler takes that back as json.RawMessage and forwards it untouched; it never decomposes it, never re‑encodes it. A small QueryJSON helper made that pattern the path of least resistance rather than something you had to remember to do. What fell out was all the intermediate machinery a conventional layered API collects — the DTOs, the mappers, the response structs.</p>\n<p><strong>Result.</strong> Handler code got dramatically shorter, and more to the point the frontend stopped being coupled to Go. Change what a function returns and the new shape flows straight through to the client without anyone editing a line of handler code. Fewer moving parts, and one whole category of layer‑to‑layer drift simply doesn&rsquo;t exist here.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Backend Engineering",
        "Databases",
        "Performance Tuning",
        "Platform Architecture",
        "PostgreSQL",
        "SQL",
        "Backend & API Development",
        "Database Design & Modeling",
        "Platform & Solution Architecture"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/designed-an-organization-context-switching-system-with-client-59/",
      "url": "https://engineer.company/portfolio/designed-an-organization-context-switching-system-with-client-59/",
      "title": "Designed an organization context‑switching system with client localStorage and server‑side cookie mirroring, letting users act as managed organizations while enforcing least‑privilege authorization.",
      "summary": "Built organization context-switching with localStorage and server cookie mirroring, letting users act as managed orgs under least-privilege auth.",
      "content_html": "<p><strong>Situation.</strong> On NextMariner a maritime professional can manage organizations — companies, academies — that don&rsquo;t have their own logins. The person is the account; the organization is something they act on behalf of. So a user needs to move through the whole app as any organization they manage, switching between them freely, and that convenience can&rsquo;t turn into a hole in the authorization.</p>\n<p><strong>Task.</strong> Context‑switching had to be quick and unobtrusive for the user while making sure the active context could never, on its own, hand someone access they weren&rsquo;t entitled to.</p>\n<p><strong>Action.</strong> An OrganizationContext handles it, with storage on both sides. On the client, localStorage is the source of truth for which organization you&rsquo;re currently acting as, so switching is instant — no round‑trip. A server‑side cookie mirrors it so that server‑rendered pages resolve the same context during SSR; there&rsquo;s a getServerViewMode on the server that reads it. The important part is that none of that is trusted for access decisions. Authorization gets re‑checked on the server on every request. The frontend context is there for the experience — showing you the right thing — and the server is the only authority on what you&rsquo;re allowed to do.</p>\n<p><strong>Result.</strong> A user can act as any organization they manage without friction, and the interface stays in sync on both client and server. But because permissions are verified server‑side every time, none of that convenience weakens the security. Someone tampering with what&rsquo;s in localStorage changes what their own UI shows them and nothing more — the server still says no.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Backend Engineering",
        "Frontend Engineering",
        "Full‑Stack Development",
        "Platform Architecture",
        "Security",
        "Backend & API Development",
        "Frontend Development",
        "Platform & Solution Architecture",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/implemented-a-catalogue-driven-deep-merge-for-stored-65/",
      "url": "https://engineer.company/portfolio/implemented-a-catalogue-driven-deep-merge-for-stored-65/",
      "title": "Implemented a catalogue‑driven deep‑merge for stored JSON preferences, preventing missing‑key crashes as the schema evolves.",
      "summary": "Implemented a catalogue-driven deep-merge for stored JSON preferences, preventing missing-key crashes as the schema evolves.",
      "content_html": "<p><strong>Situation.</strong> The platform stores JSON preferences — notification settings and the like — as a user&rsquo;s saved values laid over a set of defaults. The original merge did that at a single level. The problem shows up later: add a new key to the defaults, and rows saved before that key existed simply don&rsquo;t have it. Then some client code reads that field, gets undefined, and falls over — for exactly the users who&rsquo;ve been around longest.</p>\n<p><strong>Task.</strong> Evolving the preference schema had to be safe, so that adding a setting could never break the people who signed up before it existed.</p>\n<p><strong>Action.</strong> The single‑level merge was replaced with a deep‑merge driven by the defaults as a catalogue. The defaults are treated as the authoritative list of every key that should exist; the user&rsquo;s stored values are merged recursively on top, so anything in the catalogue is guaranteed to come out present, whether or not it was in the saved blob. Add a key to the defaults and it appears in every existing row&rsquo;s effective preferences automatically, nested keys included. It rolled in through a migration so existing data got the benefit straight away rather than waiting to be rewritten.</p>\n<p><strong>Result.</strong> Preferences can grow without fear. Adding a setting no longer risks an undefined‑field crash in the client, and the frontend stopped needing defensive checks scattered around every place it reads a preference. It also gave the rest of the platform a dependable way to extend any stored‑JSON blob — the pattern, not just the one fix.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Backend Engineering",
        "Data Governance",
        "Databases",
        "Migrations & Modernization",
        "PostgreSQL",
        "Backend & API Development",
        "Data Governance & Quality",
        "Database Design & Modeling"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/provisioned-azure-infrastructure-as-code-with-bicep-container-68/",
      "url": "https://engineer.company/portfolio/provisioned-azure-infrastructure-as-code-with-bicep-container-68/",
      "title": "Provisioned Azure infrastructure as code with Bicep — Container Apps, PostgreSQL Flexible Server, Front Door/WAF and networking — across the development, staging and production environments.",
      "summary": "Provisioned Azure infrastructure as code with Bicep — Container Apps, PostgreSQL, Front Door/WAF — reproducible across dev, staging and prod.",
      "content_html": "<p><strong>Situation.</strong> NextMariner lives on Azure, and Azure done by hand — clicking through the portal, tweaking a setting here and there — is a trap. It drifts, nobody remembers why something is the way it is, and rebuilding it after a bad day is slow and nerve‑wracking. With more than one environment to keep in step, that only gets worse.</p>\n<p><strong>Task.</strong> Put the whole thing in code, so an environment is something you can read, review and recreate rather than a pile of manual state.</p>\n<p><strong>Action.</strong> The estate is defined in Bicep. Each environment — testing, staging, product — comes out of the same templates: the api and www running as Azure Container Apps on a managed environment, a PostgreSQL Flexible Server, Redis for caching, Front Door with a WAF policy out in front, and the networking underneath it (VNet, NSG, private DNS), with Log Analytics wired in for diagnostics. Images are pulled from the project&rsquo;s Azure Container Registry. Because it&rsquo;s all parameterised, standing up a fresh environment or changing an existing one is a pull request, not a support ticket to yourself.</p>\n<p><strong>Result.</strong> The environments became reproducible and reviewable. Drift stopped being a mystery, because the source of truth is the code, and bringing infrastructure up or back is a matter of applying the templates rather than remembering what got clicked last time. It&rsquo;s the difference between infrastructure you own and infrastructure that owns you.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Cloud",
        "DevOps",
        "Infrastructure",
        "Networking & VPN",
        "Platform Architecture",
        "Reliability & Backups",
        "Security",
        "Cloud Infrastructure & Migration",
        "Infrastructure as Code",
        "Networking & VPN Setup",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/built-github-actions-ci-cd-pipelines-with-a-69/",
      "url": "https://engineer.company/portfolio/built-github-actions-ci-cd-pipelines-with-a-69/",
      "title": "Built GitHub Actions CI/CD pipelines with a distroless production frontend image and multi‑environment promotion.",
      "summary": "Built GitHub Actions CI/CD with a distroless production frontend image and multi-environment promotion, making releases routine and boring.",
      "content_html": "<p><strong>Situation.</strong> Shipping shouldn&rsquo;t depend on someone remembering the steps, and what ends up running in production shouldn&rsquo;t be a fat general‑purpose container carrying a shell and a package manager it&rsquo;ll never use — that&rsquo;s just attack surface sitting there for no reason.</p>\n<p><strong>Task.</strong> Make the path from commit to running‑in‑Azure automatic, and keep the production images as small and locked‑down as each workload allows.</p>\n<p><strong>Action.</strong> The pipeline is GitHub Actions. Separate workflows handle the code‑quality gate, the tests, and the per‑environment deploys, with CodeQL, dependency review and an SBOM step alongside them, so nothing reaches an environment without passing the checks first. The images are multi‑stage builds, and the base for each part was picked on its merits rather than as one blanket choice: the frontend ships on a distroless image (gcr.io/distroless/cc‑debian13 — no shell, no package manager), the Go API on a slim Alpine, and the database image on postgres‑slim. Promotion moves a build through the environments along a defined route rather than by hand.</p>\n<p><strong>Result.</strong> Releases stopped being a careful manual ritual and became a routine, boring event, which is exactly what you want from releases. The production frontend runs on about as little as you can give it, the checks catch problems before they land, and &ldquo;deploy&rdquo; is something the pipeline does rather than something anyone sweats through.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Cloud",
        "Containers (Docker/Kubernetes)",
        "DevOps",
        "Reliability & Backups",
        "Security",
        "Testing & QA",
        "Containerization & Orchestration",
        "DevOps & CI/CD Automation"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/owned-end-to-end-deployments-of-the-platform-71/",
      "url": "https://engineer.company/portfolio/owned-end-to-end-deployments-of-the-platform-71/",
      "title": "Owned end‑to‑end deployments of the platform to Azure, managing releases across development, staging and production environments.",
      "summary": "Owned end-to-end Azure deployments, moving releases through dev, staging and production along a defined, repeatable path.",
      "content_html": "<p><strong>Situation.</strong> The platform had to reach users across several Azure environments, and deployment is the seam where the infrastructure, the build pipeline and the application all meet. It&rsquo;s also where a small mistake stops being a bug and becomes an outage, so it&rsquo;s the part you least want to be doing by hand and half from memory.</p>\n<p><strong>Task.</strong> Deployment was owned end to end, so that a change moved out to each environment the same predictable way every time.</p>\n<p><strong>Action.</strong> Releases move along a fixed route — development, then staging, then production — rather than anyone pushing straight to a live environment. The CI/CD pipeline builds the images and ships them, and the Bicep templates keep the target infrastructure identical from one environment to the next, so a build isn&rsquo;t promoted into a subtly different place each time. Configuration that differs per environment is kept separate from the secrets, which means the same built artefact can be promoted through the environments and just picks up the right settings where it lands, instead of being rebuilt for each one.</p>\n<p><strong>Result.</strong> Changes reach each environment predictably, along a defined path, with no ad‑hoc manual deploys in the mix. Releasing turned into a controlled, repeatable step instead of a held‑breath moment, and that&rsquo;s a big part of what kept the live platform stable while it was still changing quickly underneath.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Cloud",
        "DevOps",
        "Infrastructure",
        "Reliability & Backups",
        "Security",
        "Cloud Infrastructure & Migration",
        "DevOps & CI/CD Automation",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/delivered-full-progressive-web-app-support-installable-and-74/",
      "url": "https://engineer.company/portfolio/delivered-full-progressive-web-app-support-installable-and-74/",
      "title": "Delivered full Progressive Web App support — installable and offline‑capable — with Workbox runtime caching via next‑pwa.",
      "summary": "Delivered full Progressive Web App support — installable and offline-capable — with Workbox runtime caching via next-pwa.",
      "content_html": "<p><strong>Situation.</strong> A lot of NextMariner&rsquo;s users are at sea. Seafarers and field staff, on phones, frequently offline or hanging off a bad connection — not people sitting at a desk on reliable office wifi. Building as if everyone had a fast, constant network would have quietly excluded a big part of the actual audience.</p>\n<p><strong>Task.</strong> The app had to be installable like a native one, and still usable when the network drops out.</p>\n<p><strong>Action.</strong> It&rsquo;s delivered as a full Progressive Web App. It installs to the home screen with proper icons across the various sizes, a splash screen and theme colours, so it looks and launches like an app rather than a bookmark. The service worker is set up through the next‑pwa plugin, and Workbox runtime caching uses CacheFirst for the things that don&rsquo;t change per request — fonts, images, audio, video, CDN assets — alongside sensible HTTP cache‑control headers. Because service workers only really behave over HTTPS, HTTPS‑based testing validated the offline and install behaviour on real devices instead of hoping it worked.</p>\n<p><strong>Result.</strong> Users can install the app and keep using it offline, and repeat loads come back fast from cache instead of over the wire. The platform behaves like a native app on the hardware its users actually carry, which for this audience isn&rsquo;t a nice‑to‑have — it&rsquo;s the difference between the app being usable at sea and not.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Frontend Engineering",
        "Full‑Stack Development",
        "Performance Tuning",
        "Reliability & Backups",
        "Web Development",
        "Frontend Development",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/designed-an-ios-26-liquid-glass-design-system-75/",
      "url": "https://engineer.company/portfolio/designed-an-ios-26-liquid-glass-design-system-75/",
      "title": "Designed an iOS 26 'Liquid Glass' design system and a canonical component inventory enforced by lint rules to prevent UI divergence.",
      "summary": "Designed an iOS 26 'Liquid Glass' design system and a canonical component inventory enforced by lint rules to stop UI divergence.",
      "content_html": "<p><strong>Situation.</strong> A UI without a shared visual language and a fixed set of components drifts, and it drifts fast. Every new screen reinvents its own buttons and badges and cards, each a little different, and those small inconsistencies pile up until the product looks incoherent and every change means touching five bespoke versions of the same thing.</p>\n<p><strong>Task.</strong> The design system had to be coherent enough to look deliberate, and enforceable enough that it wouldn&rsquo;t erode the moment the team was busy.</p>\n<p><strong>Action.</strong> The visual language and the component system were designed as one thing. The language is an iOS 26 &ldquo;Liquid Glass&rdquo; look — translucent panels with backdrop blur, layered shadows, a bit of refraction, spring animations — built out of Tailwind utilities. On top of it sits a canonical set of components every screen is supposed to compose from: Card, Label, Button, GlassIconButton, DirectoryGrid and the rest. The part that makes it stick is the linting: rules that reject a hand‑rolled badge or chip and point you at the canonical component instead, so the system is held up by tooling rather than by whoever&rsquo;s reviewing that day remembering to care. And the docs are paired with concrete mistake‑to‑resolution notes, so the guidance is &ldquo;here&rsquo;s the wrong way and the right way,&rdquo; not an abstract principle.</p>\n<p><strong>Result.</strong> The UI stays consistent and on‑brand, and the one‑off components that would otherwise spread get caught before they do. Visual consistency stopped being a matter of everyone&rsquo;s discipline and became something the tooling holds the line on, which is the only version of it that survives a deadline.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Design Systems & UI",
        "Documentation",
        "Frontend Engineering",
        "UX / UI Design",
        "Frontend Development",
        "UI/UX Design & Design Systems"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/designed-the-product-s-ui-and-ux-end-76/",
      "url": "https://engineer.company/portfolio/designed-the-product-s-ui-and-ux-end-76/",
      "title": "Designed the product's UI and UX end to end — directory grids, dual card/table views, live requirement validators and breadcrumb navigation.",
      "summary": "Designed the product's UI/UX end to end — directory grids, card/table views, live validators and breadcrumbs — for dense maritime data.",
      "content_html": "<p><strong>Situation.</strong> NextMariner puts a lot of different entities in front of people — professionals, companies, ships, jobs, reviews — and the interface had to be two things that fight each other: good‑looking and genuinely usable. Dense enough to show real maritime data, but not so dense it turns into a wall you bounce off.</p>\n<p><strong>Task.</strong> The product&rsquo;s UI and UX were owned end to end — the layouts, the interaction patterns, and the smaller stuff like how someone gets walked through a form without feeling nagged.</p>\n<p><strong>Action.</strong> A few decisions did most of the work. The directory grids have a card/table toggle, so you can browse visually or scan a dense table, and the app remembers which you picked. Forms use live requirement validators that sit above the input and show each rule in green when it&rsquo;s met and amber when it isn&rsquo;t, so you&rsquo;re guided while you type instead of scolded after you submit. Navigation is consistent breadcrumbs and two‑column entity layouts, so pages feel like the same product rather than a set of unrelated screens. And the error philosophy is guidance over failure — no red walls, redirects instead of dead ends, the app trying to keep you moving rather than stopping you.</p>\n<p><strong>Result.</strong> What came out is a polished, consistent experience that makes dense maritime data approachable and steers people through the complicated parts. It reads as considered and trustworthy, which isn&rsquo;t cosmetic on a platform people are using for their actual careers — if it looked slapdash, they&rsquo;d trust the data less, and they&rsquo;d be right to.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Design Systems & UI",
        "Frontend Engineering",
        "Product & Requirements",
        "UX / UI Design",
        "Web Development",
        "Frontend Development",
        "Product Strategy & Requirements",
        "UI/UX Design & Design Systems"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/hardened-the-application-with-nonce-based-csp-hsts-77/",
      "url": "https://engineer.company/portfolio/hardened-the-application-with-nonce-based-csp-hsts-77/",
      "title": "Hardened the application with nonce‑based CSP, HSTS, SameSite cookies, least‑privilege database roles and server‑side entitlement re‑checks.",
      "summary": "Hardened the app with nonce-based CSP, HSTS, SameSite cookies, least-privilege database roles and server-side entitlement re-checks.",
      "content_html": "<p><strong>Situation.</strong> NextMariner holds professional and organizational data, the kind people expect to be handled properly, so a single line of defence was never going to be enough. The working assumption has to be that the client is hostile — that anything the browser enforces can be switched off by whoever&rsquo;s holding the browser — and the security has to hold up anyway.</p>\n<p><strong>Task.</strong> The platform had to be hardened at every layer — frontend, API, database — so that security was enforced by the server independently of whatever the interface happened to allow.</p>\n<p><strong>Action.</strong> On the frontend, the Next.js proxy middleware — proxy.ts — sets a Content‑Security‑Policy with a per‑request nonce and strict‑dynamic, plus HSTS and SameSite cookies, so the browser is locked down about what it&rsquo;ll run and send. At the API, there&rsquo;s rate limiting, CORS, request‑size limits, input validation before anything touches the database, and logging of the security‑relevant events. In the database, the API logs in as a least‑privilege role that can only EXECUTE the app functions, the functions run SECURITY DEFINER, and everything is parameterised. And the entitlements — tier, role, organization, ship scoping — are re‑checked on the server on every request, with the frontend gates treated as UX only. The gates decide what you see; the server decides what you can do.</p>\n<p><strong>Result.</strong> Security doesn&rsquo;t depend on the UI behaving. The protections are layered so that getting past one doesn&rsquo;t get you past the rest, and the whole thing is built on the assumption that the client can&rsquo;t be trusted — which is the right assumption for data people are handing over in confidence.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Backend Engineering",
        "Databases",
        "Frontend Engineering",
        "Platform Architecture",
        "Security",
        "Backend & API Development",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/set-a-zero-warnings-quality-bar-across-six-79/",
      "url": "https://engineer.company/portfolio/set-a-zero-warnings-quality-bar-across-six-79/",
      "title": "Set a zero‑warnings quality bar across six languages — Go, TypeScript, SQL, Python, Shell and Markdown — enforced by pre‑commit hooks.",
      "summary": "Set a zero-warnings quality bar across Go, TypeScript, SQL, Python, Shell and Markdown, enforced by pre-commit hooks.",
      "content_html": "<p><strong>Situation.</strong> Warnings that pile up are quietly corrosive. Every diagnostic you ignore lowers the bar a little, and once the build spits out forty of them nobody reads any of them, and a real problem sits in that list in plain sight because &ldquo;warnings&rdquo; have become background noise. In a polyglot codebase there are that many more sources of noise to let it happen.</p>\n<p><strong>Task.</strong> The repo needed one uncompromising quality bar across every language, so things got fixed instead of accumulating.</p>\n<p><strong>Action.</strong> A zero‑warnings policy went in, with the tooling as the thing that enforces it, because a policy that relies on everyone&rsquo;s vigilance loses to the first busy week. Every linter diagnostic is an error — there&rsquo;s no &ldquo;warn&rdquo; tier to hide in — and it&rsquo;s the same across the whole stack: Go with golangci‑lint, TypeScript with ESLint, SQL with SQLFluff, Python with Ruff, shell with ShellCheck, Markdown with markdownlint. Inline suppressions are banned, so you can&rsquo;t paper over a diagnostic; you have to actually fix the thing. Pre‑commit and pre‑push hooks run the linters and the tests, so a commit that would introduce a problem doesn&rsquo;t get made in the first place. There are even function- and file‑length limits, to keep modules from sprawling past the point of being readable.</p>\n<p><strong>Result.</strong> Problems get fixed at the source instead of deferred into a backlog nobody clears, and the codebase stays clean by default rather than by periodic heroics. The standard is identical whatever language you&rsquo;re in, and it&rsquo;s the tooling holding it — not anyone&rsquo;s willpower — which is why it actually holds.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Documentation",
        "Technical Leadership",
        "Testing & QA",
        "DevOps & CI/CD Automation",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/delivered-analysis-and-regular-progress-reports-to-the-81/",
      "url": "https://engineer.company/portfolio/delivered-analysis-and-regular-progress-reports-to-the-81/",
      "title": "Delivered analysis and regular progress reports to the CEO, translating engineering metrics and delivery status into decisions.",
      "summary": "Delivered analysis and regular progress reports to the CEO, translating engineering metrics and delivery status into confident decisions.",
      "content_html": "<p><strong>Situation.</strong> As the platform came together, its progress and its health had to be visible to leadership in terms they could actually do something with. Raw engineering signals — build status, delivery pace, incidents — don&rsquo;t mean much on their own to someone making product and investment calls; they&rsquo;re at the wrong altitude. Someone had to translate.</p>\n<p><strong>Task.</strong> That translation was the job: taking the engineering reality — where delivery stood, what the system metrics were saying — and reporting it to the CEO clearly and regularly, so decisions rested on facts instead of guesswork.</p>\n<p><strong>Action.</strong> A steady reporting rhythm went in, rather than reporting when asked, which is always a bit too late. Delivery progress, scope, risks and system health got tracked and turned into plain, decision‑oriented updates — what&rsquo;s on track, what&rsquo;s at risk, and what a given priority would actually cost in trade‑offs. The thing to avoid was handing over raw numbers and leaving the interpretation to someone without the context; each report came with concrete recommendations, and where it helped the narrative was backed with the underlying analysis, so leadership could drill in if they wanted rather than having to take it on trust.</p>\n<p><strong>Result.</strong> Leadership ended up with a clear, honest, current picture of the engineering, and could steer product priorities and investment with some confidence instead of flying blind. Reporting stopped being a status ritual nobody reads and became something decisions actually got made from — which kept the technical work and the business direction pointed the same way.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Data Analytics",
        "Product & Requirements",
        "Project Management",
        "Stakeholder & Reporting",
        "Technical Leadership",
        "Data Analytics & BI Dashboards",
        "Project Management (Agile)",
        "Technical Leadership & Consulting"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/provided-round-the-clock-24-7-infrastructure-support-82/",
      "url": "https://engineer.company/portfolio/provided-round-the-clock-24-7-infrastructure-support-82/",
      "title": "Provided round‑the‑clock 24/7 infrastructure support for an IPTV/OTT streaming platform, administering ~1,000 servers plus client‑owned systems for global customers in China, the US and Germany.",
      "summary": "Provided 24/7 infrastructure support for an IPTV/OTT streaming platform — ~1,000 servers plus client systems across China, the US and Germany.",
      "content_html": "<p><strong>Situation.</strong> This was an IPTV/OTT streaming platform with customers spread across China, the US and Germany, which meant there was no quiet hour to do maintenance in — someone, somewhere, was always watching. Downtime on a platform like that isn&rsquo;t an abstract metric; it&rsquo;s someone&rsquo;s television just stopping, and they don&rsquo;t care why.</p>\n<p><strong>Task.</strong> Keeping the platform&rsquo;s infrastructure available around the clock was the job — genuinely around the clock, not &ldquo;business hours plus an on‑call rota nobody answers.&rdquo;</p>\n<p><strong>Action.</strong> 24/7 support ran for roughly a thousand servers, plus a good number of client‑owned systems on top of that — administering them, monitoring them, keeping them secured and configured across the whole streaming estate. Because customers sat in three very different time zones, &ldquo;after hours&rdquo; didn&rsquo;t really exist; a problem at 3am local was primetime for someone else, so it got treated as primetime. A lot of the work was noticing something drifting before it became an outage, because on a live streaming platform you don&rsquo;t get to fix things quietly after the fact.</p>\n<p><strong>Result.</strong> The platform stayed continuously available for a global audience, with problems caught and dealt with at whatever hour they turned up, before they reached a viewer&rsquo;s screen. On a 24/7 service that&rsquo;s the whole job — success looks like nothing happening, which is exactly what the viewers wanted.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Cloud",
        "Infrastructure",
        "Linux & Servers",
        "Monitoring & Observability",
        "Networking & VPN",
        "Reliability & Backups",
        "System Administration",
        "IT Support & Helpdesk",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/ensured-uninterrupted-delivery-of-iptv-streaming-signals-between-83/",
      "url": "https://engineer.company/portfolio/ensured-uninterrupted-delivery-of-iptv-streaming-signals-between-83/",
      "title": "Ensured uninterrupted delivery of IPTV streaming signals between suppliers and clients, monitoring and maintaining the streaming network and IP telephony around the clock.",
      "summary": "Ensured uninterrupted IPTV streaming between suppliers and clients, monitoring and maintaining the streaming network and IP telephony 24/7.",
      "content_html": "<p><strong>Situation.</strong> IPTV lives or dies on the signal getting through. The streams flow from suppliers, through the platform, to the clients, and any break anywhere in that chain is a black screen for someone. The IP telephony sat alongside it, with the same requirement: it just had to work.</p>\n<p><strong>Task.</strong> Guaranteeing that the signal delivery and the telephony stayed uninterrupted was the job.</p>\n<p><strong>Action.</strong> The streaming network and the IP telephony were monitored, troubleshot and maintained around the clock. The point of watching it constantly is that streaming problems announce themselves as degradation before they become an outright drop — a stream that starts stuttering, a link that&rsquo;s getting flaky — and if you&rsquo;re paying attention you can catch it at the stutter instead of the black screen. So a lot of it was staying ahead of the signal rather than reacting to complaints about it.</p>\n<p><strong>Result.</strong> The streams and the calls stayed reliable across the platform, with problems detected and fixed before they turned into service anyone noticed dropping. Keeping a signal flowing between suppliers and end clients without a visible gap is quiet, constant work, and quiet is what it should be.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Infrastructure",
        "Linux & Servers",
        "Monitoring & Observability",
        "Networking & VPN",
        "Reliability & Backups",
        "System Administration",
        "Networking & VPN Setup",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/developed-and-maintained-django-web-applications-for-the-84/",
      "url": "https://engineer.company/portfolio/developed-and-maintained-django-web-applications-for-the-84/",
      "title": "Developed and maintained Django web applications for the IPTV platform, shipping new features and improving performance and stability.",
      "summary": "Developed and maintained Django web applications for an IPTV platform, shipping features while improving performance and stability.",
      "content_html": "<p><strong>Situation.</strong> The IPTV platform had web applications built on Django around it — the parts people actually clicked on — and they needed to keep moving forward: new features, and the performance and stability that a platform running around the clock demands.</p>\n<p><strong>Task.</strong> Keeping those applications feature‑rich, fast and stable was the task.</p>\n<p><strong>Action.</strong> The Django web applications were developed and maintained — building new features, fixing the bugs, and pushing on performance and stability, worked across the team rather than in a corner. On something serving customers continuously, the stability side isn&rsquo;t a nice‑to‑have alongside the features; it&rsquo;s the constraint the features have to respect. A flashy feature that makes the thing wobble isn&rsquo;t worth much when the thing can&rsquo;t afford to wobble.</p>\n<p><strong>Result.</strong> The applications kept gaining capability while staying reliable for both the internal teams and the customers. Adding to something without destabilising it is the balance that mattered here, and that&rsquo;s what the work held to.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "APIs & Integration",
        "Backend Engineering",
        "Frontend Engineering",
        "Full‑Stack Development",
        "Python",
        "Web Development",
        "Backend & API Development",
        "Full‑Stack Product Development",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/planned-and-implemented-new-infrastructure-functionality-for-internal-85/",
      "url": "https://engineer.company/portfolio/planned-and-implemented-new-infrastructure-functionality-for-internal-85/",
      "title": "Planned and implemented new infrastructure functionality for internal and external systems, building solutions durable enough to still run years later with minimal change.",
      "summary": "Planned and built infrastructure for internal and external systems durable enough to still run years later with minimal change.",
      "content_html": "<p><strong>Situation.</strong> As the organisation grew, its internal and external systems kept needing new capabilities bolted on. The easy way to do that is whatever&rsquo;s quickest today; the trouble with the easy way is you&rsquo;re back fixing it in six months.</p>\n<p><strong>Task.</strong> Planning and building infrastructure functionality that would actually last was the task — not just work now, but keep working.</p>\n<p><strong>Action.</strong> New infrastructure functionality was planned and implemented across the internal and external systems, designed to be durable — the kind of thing you build once, properly, so it keeps running for years with minimal touching rather than needing constant attention. That&rsquo;s a deliberate choice each time: spend a bit more thought up front so you&rsquo;re not signing yourself up to babysit it forever.</p>\n<p><strong>Result.</strong> The systems stayed functional and effective long after they were built, running for years with barely any change. That longevity is the real measure of infrastructure work — anyone can make something that works today; making something that&rsquo;s still quietly working years later is the harder and more useful thing.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Infrastructure",
        "Platform Architecture",
        "Reliability & Backups",
        "Solution Architecture",
        "System Administration",
        "Platform & Solution Architecture",
        "Site Reliability & Monitoring"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/as-one-of-the-first-hires-designed-and-86/",
      "url": "https://engineer.company/portfolio/as-one-of-the-first-hires-designed-and-86/",
      "title": "As one of the first hires, designed and built the entire core infrastructure and supporting processes from scratch for a green‑energy SaaS startup, laying the foundation for rapid growth.",
      "summary": "As one of the first hires, designed and built a green-energy SaaS startup's entire core infrastructure from scratch, enabling rapid growth.",
      "content_html": "<p><strong>Situation.</strong> This was a green‑energy SaaS startup with a promising idea and essentially no technical foundation under it yet. Coming in as one of the first hires meant the stage where there&rsquo;s nothing to maintain because nothing exists — building the ground everyone else will stand on.</p>\n<p><strong>Task.</strong> Building the core infrastructure and the processes around it, from scratch, was the job.</p>\n<p><strong>Action.</strong> The entire core infrastructure and its supporting processes were designed and built — the servers, the networks, the data flow, the security, the operations side. Doing that at a startup means making decisions that are hard to unmake later, so the goal wasn&rsquo;t just &ldquo;get something running,&rdquo; it was to lay a foundation that could take the weight of rapid growth without needing to be ripped out the moment the company got bigger. Early infrastructure choices either become the thing that lets you scale or the thing you spend a year undoing; the aim was firmly the first kind.</p>\n<p><strong>Result.</strong> The startup came away with a solid technical foundation, and it&rsquo;s what let the business grow quickly afterward. Being the person who builds that base from nothing is a particular kind of responsibility — get it right and nobody notices, get it wrong and everyone does — and this one held up.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Cloud",
        "DevOps",
        "Infrastructure",
        "Platform Architecture",
        "Security",
        "Solution Architecture",
        "System Administration",
        "Cloud Infrastructure & Migration",
        "DevOps & CI/CD Automation",
        "Platform & Solution Architecture"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/maintained-and-enhanced-the-legacy-hugo-static-site-87/",
      "url": "https://engineer.company/portfolio/maintained-and-enhanced-the-legacy-hugo-static-site-87/",
      "title": "Maintained and enhanced the legacy Hugo static‑site website while contributing UI/UX improvements to the primary asset‑management product.",
      "summary": "Maintained and improved a legacy Hugo static website while contributing UI/UX refinements to the core asset-management product.",
      "content_html": "<p><strong>Situation.</strong> There was a legacy website built on Hugo — a static‑site generator — running alongside the company&rsquo;s main product, an asset‑management system. The website was the old, established thing; the product was where the real value sat.</p>\n<p><strong>Task.</strong> Keeping the site healthy while also improving the product&rsquo;s experience was the remit.</p>\n<p><strong>Action.</strong> The Hugo static site was maintained and enhanced — kept current and working — while UI/UX improvements and feedback fed into the primary asset‑management product at the same time. Splitting attention between a legacy site and the flagship product is mostly about not letting the old thing rot while you&rsquo;re focused on the new one; both represent the company to someone, so both had to be kept decent.</p>\n<p><strong>Result.</strong> The website stayed current instead of quietly ageing, and the main product&rsquo;s usability improved through steady, informed refinement — the kind that comes from actually using and thinking about the thing rather than redesigning it in one big swing.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Frontend Engineering",
        "Full‑Stack Development",
        "UX / UI Design",
        "Web Development",
        "Frontend Development",
        "UI/UX Design & Design Systems",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/designed-a-time-management-and-reporting-system-that-88/",
      "url": "https://engineer.company/portfolio/designed-a-time-management-and-reporting-system-that-88/",
      "title": "Designed a time‑management and reporting system that remained in production use for years without significant modification.",
      "summary": "Designed a time-management and reporting system that stayed in production for years without significant modification.",
      "content_html": "<p><strong>Situation.</strong> The team didn&rsquo;t have a dependable way to manage time and report progress. Which usually means it&rsquo;s happening in a scatter of spreadsheets and memory, and the reporting turns into a scramble at the end of each period rather than something that just falls out of how people work.</p>\n<p><strong>Task.</strong> Building a system for both that would actually last was the task.</p>\n<p><strong>Action.</strong> A time‑management and reporting system was designed around how the team actually worked, rather than imposing some off‑the‑shelf process they&rsquo;d route around. That&rsquo;s the whole trick with internal tools — if it fits the real workflow, people use it; if it fights the workflow, they quietly abandon it and you&rsquo;re back to spreadsheets. So it was built to match reality rather than an ideal.</p>\n<p><strong>Result.</strong> It stayed in production use for years without any significant modification. That&rsquo;s the compliment you want for an internal tool — not that it was impressive, but that it just kept working and nobody ever needed to replace it. Something that survives years of daily use untouched was clearly built to fit.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Product & Requirements",
        "Project Management",
        "Technical Leadership",
        "Data Analytics & BI Dashboards",
        "Product Strategy & Requirements",
        "Project Management (Agile)"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/automated-team-collaboration-password-management-task-and-time-89/",
      "url": "https://engineer.company/portfolio/automated-team-collaboration-password-management-task-and-time-89/",
      "title": "Automated team collaboration, password management, task and time management, and built a semi‑automatic project‑showcase system, raising team productivity.",
      "summary": "Automated collaboration, password, task and time management and built a semi-automatic project-showcase system, raising team productivity.",
      "content_html": "<p><strong>Situation.</strong> A lot of the team&rsquo;s operational work — coordinating, managing passwords, tracking tasks and time — was being done by hand, and manual coordination is a quiet tax: it&rsquo;s never the thing you notice, but it steadily eats hours that could go somewhere better.</p>\n<p><strong>Task.</strong> Automating that repetitive operational work was the goal.</p>\n<p><strong>Action.</strong> The pieces that lent themselves to it got automated — team collaboration, password management, task management, time management — with a semi‑automatic project‑showcase system on top. The idea across all of it was to take the routine coordination off people&rsquo;s plates so it ran itself, and let them spend the recovered attention on work that actually needed a human. The showcase system was the same instinct applied to something more visible: make presenting the work mostly automatic rather than a manual chore each time.</p>\n<p><strong>Result.</strong> Operational efficiency went up and the team got more productive, because the routine coordination that used to need constant human attention now largely ran on its own. The time that was leaking into busywork went back into the actual work.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "DevOps",
        "Project Management",
        "Security",
        "Technical Leadership",
        "DevOps & CI/CD Automation",
        "Project Management (Agile)",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/integrated-a-company-wide-password-management-system-strengthening-90/",
      "url": "https://engineer.company/portfolio/integrated-a-company-wide-password-management-system-strengthening-90/",
      "title": "Integrated a company‑wide password‑management system, strengthening security and streamlining access control.",
      "summary": "Integrated a company-wide password-management system, strengthening security and streamlining access control across the organisation.",
      "content_html": "<p><strong>Situation.</strong> Credentials were being handled inconsistently — different people storing and sharing them in different, ad‑hoc ways — and that inconsistency is itself the security risk. It&rsquo;s rarely a dramatic breach; it&rsquo;s a password in a chat message, a shared login nobody rotates, the slow accumulation of small exposures.</p>\n<p><strong>Task.</strong> Centralising the credentials and making them secure was the task.</p>\n<p><strong>Action.</strong> A company‑wide password‑management system went in, so there was one consistent, secure way credentials got stored and shared instead of everyone&rsquo;s personal habit. The value of company‑wide is exactly that it&rsquo;s not optional per person — a password manager only half the team uses barely helps, because the risk lives in the half that didn&rsquo;t. So the point was to make the secure way the default way, everywhere.</p>\n<p><strong>Result.</strong> Security improved and access control got simpler and more consistent across the organisation. Once the credentials all live in one managed place, a whole set of small, boring exposures just stop being possible — which is most of what real‑world security actually is.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Automation & CI/CD",
        "Infrastructure",
        "Security",
        "System Administration",
        "Security & Access Management"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/drove-client-web-success-by-combining-custom-website-91/",
      "url": "https://engineer.company/portfolio/drove-client-web-success-by-combining-custom-website-91/",
      "title": "Drove client web success by combining custom website development and design with SEO, content strategy and copywriting.",
      "summary": "Drove client web success by combining custom website development and design with SEO, content strategy and copywriting.",
      "content_html": "<p><strong>Situation.</strong> Clients didn&rsquo;t really want a website; they wanted the thing a website is supposed to do for them — to be found, to bring in customers, to actually work as a channel. A beautiful site nobody can find is a failure that looks like a success.</p>\n<p><strong>Task.</strong> Combining the build and the growth side into one offering was the task, rather than handing over a site and wishing them luck.</p>\n<p><strong>Action.</strong> The two halves were put together — custom website development and design on one side, SEO, content strategy and copywriting on the other — so a client got something that was both well‑built and actually discoverable. Those usually get treated as separate jobs, which is how you end up with a gorgeous site that ranks nowhere, or a well‑optimised site that&rsquo;s unpleasant to use. Doing both meant the site was designed from the start to be found, not optimised as an afterthought.</p>\n<p><strong>Result.</strong> Clients ended up with stronger visibility and more engagement — their sites working as genuine growth channels rather than online brochures. Building the thing and making it findable in one go is what turned a website from a cost into something that actually earned its keep.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Brand & Marketing",
        "Frontend Engineering",
        "Product & Requirements",
        "UX / UI Design",
        "Web Development",
        "Brand, Marketing & SEO",
        "UI/UX Design & Design Systems",
        "Website Development & CMS"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/performed-data-recovery-across-a-wide-range-of-92/",
      "url": "https://engineer.company/portfolio/performed-data-recovery-across-a-wide-range-of-92/",
      "title": "Performed data recovery across a wide range of media — SD cards, HDDs, SSDs, RAID arrays, external drives and Mac systems.",
      "summary": "Recovered critical data across SD cards, HDDs, SSDs, RAID arrays, external drives and Mac systems — files back from devices written off as lost.",
      "content_html": "<p><strong>Situation.</strong> People turned up with storage that had failed or been damaged, and data on it they urgently needed back — and by the time someone&rsquo;s carrying a dead drive into a shop, they&rsquo;re usually past worried and into panicking. Photos, business files, the only copy of something that mattered.</p>\n<p><strong>Task.</strong> Recovering their data, across whatever media they walked in with, was the job.</p>\n<p><strong>Action.</strong> Data recovery ran across pretty much every common storage type — SD cards, spinning HDDs, SSDs, RAID arrays, external drives, Mac systems. Each of those fails and gives up its data differently: an SSD is a different problem from a platter drive, a RAID array is a different problem again, and a Mac filesystem has its own quirks. So the work was as much about knowing the right approach for the specific medium as it was about any single technique.</p>\n<p><strong>Result.</strong> Clients got critical data back from devices they&rsquo;d already written off as gone, across every common kind of storage. There&rsquo;s a specific relief on someone&rsquo;s face when you hand back the drive with their files intact — that was the point of the work, and it happened across the whole range of media.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Reliability & Backups",
        "System Administration",
        "Hardware Repair & Data Recovery",
        "IT Support & Helpdesk"
      ]
    },
    {
      "id": "https://engineer.company/portfolio/diagnosed-and-repaired-laptop-hardware-screens-hinges-keyboards-93/",
      "url": "https://engineer.company/portfolio/diagnosed-and-repaired-laptop-hardware-screens-hinges-keyboards-93/",
      "title": "Diagnosed and repaired laptop hardware — screens, hinges, keyboards, trackpads, motherboards and power — and resolved software issues across Linux, Windows and Mac.",
      "summary": "Diagnosed and repaired laptop hardware — screens, hinges, keyboards, trackpads, motherboards, power — and fixed software on Linux, Windows and Mac.",
      "content_html": "<p><strong>Situation.</strong> Laptops came in with the full range of things that go wrong with laptops — cracked screens, broken hinges, dead keyboards, failing trackpads, motherboard faults, power problems — plus the software side, across Linux, Windows and Mac. Basically, whatever was wrong with it, it landed on the bench.</p>\n<p><strong>Task.</strong> Diagnosing and fixing them reliably was the job — reliably being the operative word, because a laptop repair that doesn&rsquo;t hold is just a delayed second visit.</p>\n<p><strong>Action.</strong> The hardware was repaired end to end — screens, hinges, keyboards, trackpads, motherboards, power — which at the motherboard level is genuinely fiddly, close work, and the software problems resolved across all three operating systems. The diagnosis is usually the real skill: a laptop that won&rsquo;t power on could be the charger, the board, or the battery, and the repair is only as good as the guess about what&rsquo;s actually broken. So the care went into finding the real fault before touching anything.</p>\n<p><strong>Result.</strong> Machines went back to their owners working and dependable, hardware and software both sorted. A repair that holds up is the only kind worth doing, and getting the diagnosis right first is what made these hold.</p>\n",
      "date_published": "2026-08-17T15:08:44+02:00",
      "date_modified": "2026-08-17T15:08:44+02:00",
      "language": "en",
      "tags": [
        "Linux & Servers",
        "Reliability & Backups",
        "System Administration",
        "Hardware Repair & Data Recovery",
        "IT Support & Helpdesk"
      ]
    },
    {
      "id": "https://engineer.company/notes/solana-mobile-wallet-deeplinks/",
      "url": "https://engineer.company/notes/solana-mobile-wallet-deeplinks/",
      "title": "Opening a dApp inside Solana mobile wallets",
      "summary": "Why Phantom, Solflare and Backpack browse deep links fail on mobile, and the exact formats, trigger rules and fixes that make them work.",
      "content_html": "<p>A React dApp built on <code>@solana/wallet-adapter-react</code> connects desktop wallets\nwithout trouble, but on a phone the same flow falls apart: the wallet has to\nopen the dApp inside its own in-app browser, and the deep links that should\nmake that happen quietly do not. Backpack lands on a &ldquo;download the app&rdquo; page;\nSolflare opens the app but never the site; every variant seems to fail. We took\nthe problem apart, and it turned out to be four separate problems wearing one\nsymptom.</p>\n<h2 id=\"the-four-problems\">The four problems</h2>\n\n<ul>\n<li><strong>The Backpack link was malformed.</strong> The only documented format is\n<code>https://backpack.app/ul/v1/browse/&lt;url&gt;?ref=&lt;ref&gt;</code> — a universal link with\nthe target URL in the path and a required <code>ref</code>. A custom-scheme guess like\n<code>backpack://ul/v1/browse?url=...</code> matches no route the app registers, so the\nuser ends on the wallet&rsquo;s install page.</li>\n<li><strong>Solflare needs its universal link too:</strong>\n<code>https://solflare.com/ul/v1/browse/&lt;url&gt;?ref=&lt;ref&gt;</code>, not the bare\n<code>solflare://</code> scheme. A bare scheme can launch the app without routing it —\nwhich is exactly &ldquo;the app opens, but the site tab has to be opened by hand&rdquo;.</li>\n<li><strong>Both parameters must be encoded.</strong> <code>url</code> is the full absolute dApp address\nand <code>ref</code> is the requesting origin, each passed through <code>encodeURIComponent</code>.\nAn unencoded <code>?</code> or <code>&amp;</code> in the target corrupts the parse, and the wallet\nopens on its home screen instead of the browser tab.</li>\n<li><strong>The trigger matters as much as the link.</strong> Universal links only switch apps\non a navigation the operating system trusts — and they deliberately do\nnothing when pasted into the address bar, which is also how a perfectly\ncorrect link &ldquo;fails&rdquo; during testing.</li>\n</ul>\n<h2 id=\"the-documented-formats\">The documented formats</h2>\n\n<ul>\n<li>Phantom: <code>https://phantom.app/ul/browse/&lt;url&gt;?ref=&lt;ref&gt;</code> — no <code>/v1</code> in this\none.</li>\n<li>Solflare: <code>https://solflare.com/ul/v1/browse/&lt;url&gt;?ref=&lt;ref&gt;</code></li>\n<li>Backpack: <code>https://backpack.app/ul/v1/browse/&lt;url&gt;?ref=&lt;ref&gt;</code></li>\n</ul>\n<p>One pattern serves all three:</p>\n<pre tabindex=\"0\"><code>const WALLET_BROWSE = {\n  phantom: (url, ref) =&gt;\n    `https://phantom.app/ul/browse/${url}?ref=${ref}`,\n  solflare: (url, ref) =&gt;\n    `https://solflare.com/ul/v1/browse/${url}?ref=${ref}`,\n  backpack: (url, ref) =&gt;\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=\"triggering-the-link-so-ios-and-android-accept-it\">Triggering the link so iOS and Android accept it</h2>\n\n<ul>\n<li><strong>Render a real anchor, precomputed.</strong> A plain\n<code>&lt;a href={walletBrowseLink('phantom')}&gt;</code> is the most reliable trigger on both\nplatforms.</li>\n<li><strong>If it must be programmatic</strong>, assign <code>window.location.href</code> synchronously\ninside the tap handler — no <code>await</code>, no <code>fetch</code>, no <code>setTimeout</code> first. After\nasynchronous work the gesture context is gone, and iOS falls back to the\nwallet&rsquo;s website. Never <code>window.open</code>.</li>\n<li><strong>Never test by pasting into the address bar.</strong> Universal links deliberately\ndo not fire there; test with a tapped link or a QR code scanned by the\ncamera.</li>\n<li><strong>Mind the messenger webviews.</strong> Opened inside Telegram&rsquo;s or Instagram&rsquo;s\nin-app browser, universal links are frequently swallowed and the wallet&rsquo;s\nplain website loads instead. User-agent detection is heuristic at best, so\nalso give users a visible escape hatch: &ldquo;open in Safari or Chrome, then\nconnect&rdquo;.</li>\n</ul>\n<h2 id=\"the-bigger-fix-on-android\">The bigger fix on Android</h2>\n\n<p>Hand-rolled deep links are the iOS story. On Android, Solana Mobile&rsquo;s Mobile\nWallet Adapter lets a dApp running in the mobile browser connect straight to\nthe installed wallet app, with no in-app-browser detour at all. Recent versions\nof <code>@solana/wallet-adapter-react</code> register the mobile adapter automatically, so\nupgrading the wallet-adapter packages can fix Android by itself. The target\narchitecture: Mobile Wallet Adapter on Android, browse universal links on iOS,\nwhere Apple allows no equivalent.</p>\n<h2 id=\"verifying-on-a-device\">Verifying on a device</h2>\n\n<ol>\n<li>Real device, wallet installed, link opened from the system browser — not\nfrom a messenger.</li>\n<li>Tap a rendered link or scan a QR code; never paste into the address bar.</li>\n<li>Confirm the wallet opens and the dApp loads in its in-app browser tab — the\nsecond half is the part that fails.</li>\n<li>Repeat without the wallet installed: the universal link should degrade to\nthe wallet&rsquo;s website. That page appearing while the app is installed means\nthe link or the trigger is still wrong.</li>\n<li>Then test the messenger path, and add the &ldquo;open in browser&rdquo; hint if it\nfails there.</li>\n</ol>\n<h2 id=\"sources\">Sources</h2>\n\n<ul>\n<li><a href=\"https://docs.phantom.com/phantom-deeplinks/deeplinks-ios-and-android\">Phantom: deep links on iOS and Android</a></li>\n<li><a href=\"https://docs.solflare.com/solflare/technical/deeplinks/other-methods/browse\">Solflare: the Browse deep link</a></li>\n<li><a href=\"https://docs.backpack.app/deeplinks/other-methods/browse\">Backpack: the Browse deep link</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": "en"
    }
  ]
}