diff --git a/.envrc b/.envrc
deleted file mode 100644
index 3550a30..0000000
--- a/.envrc
+++ /dev/null
@@ -1 +0,0 @@
-use flake
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
deleted file mode 100644
index 1ee3911..0000000
--- a/.github/workflows/build.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-name: deploy
-
-on:
- push:
- branches: [master]
- schedule:
- - cron: "0 1 * * *" # daily 01:00 UTC, publishes posts dated that day
- workflow_dispatch: # manual trigger, also re-arms the 60-day inactivity timer
-
-permissions:
- contents: read
- pages: write
- id-token: write
-
-concurrency:
- group: "pages"
- cancel-in-progress: false
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/configure-pages@v5
- - uses: actions/jekyll-build-pages@v1
- with:
- source: ./
- destination: ./_site
- - uses: actions/upload-pages-artifact@v3
-
- deploy:
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- runs-on: ubuntu-latest
- needs: build
- steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
diff --git a/.gitignore b/.gitignore
index 93fcead..c407f65 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,14 @@
-_site/
-.jekyll-cache/
-.jekyll-metadata
-.bundle/
-vendor/
-
-.gem/
-.direnv/
+/target
+/classes
+/checkouts
+profiles.clj
+pom.xml
+pom.xml.asc
+*.jar
+*.class
+/.lein-*
+/.nrepl-port
+/.prepl-port
+.hgignore
+.hg/
+dist/
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
deleted file mode 100644
index edae596..0000000
--- a/CLAUDE.md
+++ /dev/null
@@ -1,67 +0,0 @@
-# CLAUDE.md
-
-## Project overview
-
-sourceless.org — a personal blog built with Jekyll, hosted on GitHub Pages.
-
-## Development environment
-
-This repo uses a **Nix flake** for dev tooling. Enter the dev shell with:
-
-```
-nix develop
-```
-
-Or automatically via **direnv** (`.envrc` contains `use flake`). The shell provides Ruby 3.3, bundler, and native dependencies. Gems install to `.gem/` in the project root.
-
-## Common commands
-
-- **Dev server:** `bundle exec jekyll serve` (serves at localhost:4000)
-- **Build:** `bundle exec jekyll build` (outputs to `_site/`)
-- **Install deps:** `bundle install`
-- **Regenerate social cards:** `python tools/generate_og_images.py`
-
-## Deployment
-
-Pushes to `master` trigger a GitHub Actions workflow (`.github/workflows/build.yml`) that builds and deploys to GitHub Pages.
-
-## Project structure
-
-- `_posts/` — blog posts in Markdown (named `YYYY-MM-DD-slug.md`)
-- `_layouts/` — page templates (`default.html`, `post.html`, `landing.html`, `home.html`, `page.html`)
-- `assets/css/main.css` — site styles
-- `assets/og/` — generated Open Graph cards, one per post plus `default.png`
-- `tools/generate_og_images.py` — renders those cards
-- `index.html` — landing page
-- `posts.html` — post index
-- `_config.yml` — Jekyll config (permalink: `/posts/:title.html`)
-
-## Linting, formatting, and testing
-
-There are no linters, formatters, or test suites configured for this project. Validate changes by running `bundle exec jekyll build` and checking the dev server output.
-
-## Post artwork
-
-Every post has a Julia set fractal seeded from a SHA-256 of its body text,
-rendered by `tools/generate_og_images.py` and committed under `assets/og/`. Four
-files per post, since the site has both themes and the index needs something
-small:
-
-- ` Hi, I'm Laurence. I'm an Director of SRE in the AdTech industry. In previous lives I've done SRE, security, and software in Healthcare and Government. Notable past projects include: My topics of particular interest are: I've worked on a lot of projects in a lot of languages and frameworks; the best of those were backed by great teams that knew their tools inside out. If you want to talk, or just want to see a CV, check out my linkedin. So, I built a crappy site generator. It's pretty bad, which is the result of some very targeted design decisions: Feel free to look at the code (linked above) if you want, but it's really the least interesting part of this post. At a quick summary, here's what it can do: I hasten to note this is by no means revolutionary or new. But here's how I did it. Github Pages comes with Jekyll site generation built in. This is nice, unless you want to use your own generator. Now of course, you could generate it locally and push it, but really, where's the fun in that? So, let's get github to do all the work. For reference, here is the action that builds the site. At a high level, it: (Note to readers: I haven't gone through this with a fine-tooth comb. It's quite possible that the reset is entirely unnecessary.) The fantastic GitHub Push Action by ad-m was instrumental in getting this to work so easily. So what am I left with? Seems like it fits the bill! I'm happy with the output, and maybe I will spend some time improving the code to something I would be proud to show to people, but the fact remains: working code that solves the problem is better than neat code that doesn't work yet. Follow the discussion on Hacker News Before I say anything else, it should be noted that this solution requires a little bit of background knowledge to set up and use. At the very least you'll need to be comfortable on the command line in linux/osx, especially with installing packages, git, ssh, and a little gpg. Don't worry if you don't know all of that – a little familiarity should be enough. You might be wondering what the point is of setting up your own password manager is when so many good, paid alternatives exist – and you'd be right! There is however, some advantage in knowing exactly how your passwords are encrypted and being able to integrate your password store with EVERYTHING. And it's run at no cost to me. Standard warnings apply; the UX will not be as good. If you do not back up your GPG keys, you could lose access to your passwords forever. The security of your password manager is only as good as the security of your system. Our password manager will have the following features: We'll make it with these components: Make sure you have all the above installed before continuing! There's lots of guides on doing this, but here's an example (from 2022, if you're reading this in the future, google it first because best practice has probably changed!). This will generate a new multi-purpose ed25519 key with no expiration date. It's up to you if you want to use this to generate subkeys to use for the password store. I would, however, recommend that you make one key per device sou you can revoke access if a device is lost or stolen! If you've already got a gpg key and want to use it – use that. **And before anything else - back up your key! On a storage drive, paper, obelisk, yubikey - and in more than one place!** That's it! If you want to use multiple keys (e.g. one per device), just add any additional IDs after the first. This will generate a random 100-character password for you named It'll print on the terminal in the first instance, and you probably don't want that, so you can pass the To edit a password, use You'll see that on the first line of this file is your generated password. You can add more lines with notes or key-value pairs, which some clients can use for autofill: For more commands, see the password-store docs. First, init the pass git repository: Then, go to your chosen git hosting provider and create a private repo. Once you have an empty private repo, you can go ahead and add the remote: Then, do your first push: Now when you go to view your repo, you'll see an encrypted file called If you want to auto-sync whenever a password is changed, add a Take a look at the list of compatible clients to find I would suggest creating a new gpg key or sub-key for each device so you can revoke them individually. For an extra layer of security, you can add a password to the keys, but this will impact UX. Now the only remaining step is to move your old passwords! Fortunately there's a plethora of tools available! Perfect is the enemy of the good Or so goes the old adage. For developers, perfection is a mightier enemy than users, project managers, and even one's own lack of skill. Many junior (and senior!) engineers strive to write the best code. Clean code. Well-tested. Fully covered. Abstractions on abstractions making beautiful, elegant interfaces. Beautiful, elegant, fragile interfaces, which shatter upon exposure to the expectations of an ever-changing world. No (useful) system exists in a vacuum. Users' needs change over time, as does the substrate on which software relies. Nothing is permanent. Very early in my career, I built a system to click through HTML forms, submit some information and return some information. There weren't APIs for this kind of thing, so traversing through legacy websites was the only way. Even with that short description, you've probably already thought how you would do it; and most of you probably have a better plan than I, a junior developer at the time, did. So I birthed a new language in to the world. A powerful tool to be sure. By issuing a list of simple commands, a headless browser would go off and perform the commands, fill the forms, and return the information we needed. Great! Right? But then there were different forms that were differently bad. Commands had to change and gain options. Commands were constantly bug-fixed. The code that ran was stored in a SQL database, so changing anything required a full run of the system and debugging was waiting for logs to populate. Every new integration made the system heavier and bigger and buggier. If you're cringing – that's correct. If you're not – come back in a couple years (after you finish reading this). Why was that system so bad? What was it that I did that made it so inextensible and brittle? The first reason is greed. I wanted to make something cool. I sought a challenge where non existed! The problem I was seeking to solve was primarily my own creation. The second reason is ignorance. I naively believed that the original purpose of the system is all it would ever do. The third, and perhaps worst reason, is hubris. Perfection for my own selfish reasons – not for the user, but the maker. I didn't learn how to control each of these right away, and still combat each of these in my working and hobby lives. In moderation, these things are a positive force for learning, but when they take over – beware. My most recent relapse was creating an internal tool for a team to register public keys into a repository. By most measures I did an extremely good job of it – it was reliable, did only what it needed to, and was easy for me to extend. 'easy for me to extend' Ah. Now there was the problem. This tool I created became a victim of its own success and, due to other commitments, changes needed to be made by other members of my team. Which, for a little web frontend, would have been fine. Had I been on a team used to writing purely functional code, and had I not written it in Elm (no shade to Elm, it's a fantastic language, but it was the wrong choice in this context). I had failed to keep in mind the future life of this thing I had created. Code, much like art, ceases to belong to its author once it is released in to the world. Each of these issues of greed, ignorance, and hubris can be avoided through a judicious application of mediocrity. The key to this is recognising that the time you spend trying to create something perfect is not only extremely expensive (you could release something much earlier), but also holds little value. A program is worthless until it is used, and will not grow in value if you make it too hard to change. This doesn't mean you shouldn't experiment and sharpen your skills - just make sure you are aiming at a target, and not your own or someone else's foot. It's here, then, that I urge you to do the dumbest possible thing. That may be somewhat hyperbolic, but here's what I mean by it: In other words; make it Just Good Enough to do what you need. If you've been programming for more than a couple of years, you've probably heard of functional programming. You might even have picked up a functional language and played around with it. Functional programming languages span a range of styles. On the one hand, you have LISP-style languages, the masters of which pull off incredible feats of language reinvention. On the other, you have Haskell wizards, whose carefully structured types give their programs and data structures properties that C programmes can only dream of. However, functional programming is not confined to functional languages; the languages merely support the use of functional programs. So what does it mean to be functional? Surely every programming language is functional, in the sense that it does something? True – but "functional" in this context means centering your programs around functions. You know what a function is, right? Well, you probably have some conception at least. You migh have heard it in a maths class, or you know it's what you make every time you write Here's a very loose definition: A function f is a transformation that takes some input X and returns some output f(X).
-{% for post in site.posts %}
-
diff --git a/_layouts/default.html b/_layouts/default.html
deleted file mode 100644
index 3bcee7c..0000000
--- a/_layouts/default.html
+++ /dev/null
@@ -1,191 +0,0 @@
-
-
-
-
- {{ page.title }}
{% endif %}
- {{ content }}
- {{ latest.title }}
-{{ latest.content }}
-
-
-
-all posts
-{% include post-list.html %}
diff --git a/_layouts/page.html b/_layouts/page.html
deleted file mode 100644
index 73dae17..0000000
--- a/_layouts/page.html
+++ /dev/null
@@ -1,4 +0,0 @@
----
-layout: default
----
-{{ content }}
diff --git a/_layouts/post.html b/_layouts/post.html
deleted file mode 100644
index e460c98..0000000
--- a/_layouts/post.html
+++ /dev/null
@@ -1,20 +0,0 @@
----
-layout: default
----
-
-{{ content }}
-
-
diff --git a/_posts/2021-01-17-how-i-built-this-site.md b/_posts/2021-01-17-how-i-built-this-site.md
deleted file mode 100644
index 35127fa..0000000
--- a/_posts/2021-01-17-how-i-built-this-site.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-layout: post
-title: "Getting github pages to work with my custom site generator"
-date: 2021-01-17
-tags: [tutorial, guide, how]
----
-
-So, I built a [crappy site generator](https://github.com/Sourceless/sourceless.github.io).
-It's pretty bad, which is the result of some very targeted design decisions:
-* I didn't want to spend a huge amount of effort building it. This is for fun, not work. You're not going to find any unit tests or any significant refactoring effort.
-* I want to generate a fast site that I can host for free (sans domain costs).
-* I want it to be easy to author new posts in my tool of choice (emacs).
-* Deploying the website should be a `git push`.
-
-Feel free to look at the code (linked above) if you want, but it's really the least interesting part of this post. At a quick summary, here's what it can do:
-* Fixed index page that lists posts and pages
-* Pages and posts generated from markdown
-* Page/post metadata stored as EDN (Jekyll-style, but EDN isntead of YAML)
-
-### Getting Github actions to deploy from my own generator
-I hasten to note this is by no means revolutionary or new. But here's how I did it.
-
-[Github Pages comes with Jekyll site generation built in](https://docs.github.com/en/github/working-with-github-pages/setting-up-a-github-pages-site-with-jekyll).
-This is nice, unless you want to use your own generator.
-
-Now of course, you _could_ generate it locally and push it, but really, where's the fun in that? So, let's get github to do all the work.
-
-For reference, [here is the action that builds the site](https://github.com/Sourceless/sourceless.github.io/blob/master/.github/workflows/build.yml).
-At a high level, it:
-1. Generates the site from the files in the repo
-2. Resets to the `www` branch, [which contains the built output](https://github.com/Sourceless/sourceless.github.io/blob/master/.github/workflows/build.yml) served by GitHub pages (you can change the branch it watches in settings)
-3. Checks out and adds files
-4. Makes a new commit postfixed by the actions run number
-5. Pushes to the `www` branch.
-
-(Note to readers: I haven't gone through this with a fine-tooth comb. It's quite possible that the reset is entirely unnecessary.)
-
-The fantastic [GitHub Push Action by ad-m](https://github.com/ad-m/github-push-action) was instrumental in getting this to work so easily.
-
-### The end result
-
-So what am I left with?
-* A small, lean, html-only site
-* On free hosting
-* Using any site generator I want
-* Pushing a new version to master auto-generates a new site
-
-Seems like it fits the bill! I'm happy with the output, and maybe I _will_ spend some time improving the code to something I would be proud to show to people, but the fact remains: *working code that solves the problem is better than neat code that doesn't work _yet_*.
-
-# 👋
-
-[Follow the discussion on Hacker News](https://news.ycombinator.com/item?id=25813217)
diff --git a/_posts/2021-03-19-the-documentation-triangle.md b/_posts/2021-03-19-the-documentation-triangle.md
deleted file mode 100644
index db1f126..0000000
--- a/_posts/2021-03-19-the-documentation-triangle.md
+++ /dev/null
@@ -1,102 +0,0 @@
----
-layout: post
-title: "The Documentation Triangle"
-date: 2021-03-19
-tags: [documentation, self-documenting, documentation-as-code, opinion, triangle]
----
-
-At some point in your coding career, you've probably heard something like:
-
-> "My code is self documenting"
-
-or
-
-> "Code IS documentation"
-
-These statements can be hard to argue against, especially if they come from
-someone who is more experienced than you. A lot of the trouble is that they
-*are not wrong* -- well, they're not *completely* wrong.
-
-### The goal of documentation
-Why do we document things? If the code was enough, we would never need
-tutorials, API documentation, or anything of the sort, right? What user
-need is being fulfilled by having more than just code to communicate what our
-programs and services do?
-
-It sounds obvious, but **documentation exists so that code can be easily used**.
-
-This is incredibly easy to forget when the program is all in your head and you
-have an intuitive understanding -- but to fresh eyes, is your code really all
-that easy to approach?
-
-### What, Why, How: The Documentation Triangle
-```
- What
- /\
- / \
- / \
- / \
- / \
- /__________\
- Why How
-```
-Here's a rule of thumb I've seen repeated in a few places regarding how to
-ensure that your documentation is up to scratch.
-
-Why a triangle? Well, triangles are a strong shape, and any missing side renders
-it entirely structurally unsound - just as missing out vital documentation
-can make your code as good as unusable to others.
-
-#### What (code)
-This is generally what people are going for when they say "my code is self
-documenting". Clear, concise, and well-written code is a form of documentation
-in itself.
-
-It is the most honest account of what actually happens when you run the code,
-what the most important data structures are, and what interfaces you'll be
-able to interact with. Some languages are better than this for others, and
-supplementary information such as type signatures can be incredibly helpful
-in understanding some code that's new to you.
-However, there are some things this alone leaves out...
-
-#### Why (comments)
-Every program has a history - whether in the very mundane sense of having a
-history in version control, or a long and detailed history of changes it has
-undergone to meet various challenges.
-
-It's this latter case that is the most crucial to note **why** certain pieces
-of code exist in their current state.
-
-Comments are most often the vehicle for these sort of margin notes. Noting
-that this thing was done in this way for optimization reasons, or another
-thing was a dirty hack leaves signposts for the next maintainer to know
-what compromises have been made in building this software.
-
-Maintaining (legacy) code without any comments is less like being a mechanic,
-and much more like being an archaeologist.
-
-#### How (context)
-The last side of the triangle is often the most neglected. It might live in
-docstrings, or in confluence, be automatically generated, or be in a carefully
-crafted README, but it's all information on the context in which the code
-will be executed.
-
-**No program exists in a vacuum**. There is always some environment, some
-organisation, some process that it lives in or serves, and taken out of said
-context, it is entirely useless.
-
-Failing to recognise this is the number one leading cause of new users and
-junior devs being unable to do 'this simple task' (don't quote me on this,
-I have no data).
-
-There's a great litmus test for this. Take the new process or pipeline or
-whatever that you've implemented (be it setting up a repo, running a job, etc.)
-and find yourself someone that's technically competent but unfamiliar with
-what you're working on. Plonk them in front of it and **shut the fuck up**.
-Watch them struggle through it and only help them if they are genuinely, truly
-stumped. This will very quickly tell you where you need to spend effort on
-documentation.
-
-Heck - make it part of your code review.
-
-[Discussion of this article on Hacker News](https://news.ycombinator.com/item?id=31819926)
diff --git a/_posts/2022-06-19-relearning-to-learn.md b/_posts/2022-06-19-relearning-to-learn.md
deleted file mode 100644
index 76d8c4d..0000000
--- a/_posts/2022-06-19-relearning-to-learn.md
+++ /dev/null
@@ -1,164 +0,0 @@
----
-layout: post
-title: "Relearning to Learn"
-date: 2022-06-19
-tags: [learning, learning-to-learn]
----
-
-Many years ago, I took a test. It was the first step of our
-"Learning to Learn" class. After answering a series of multiple
-choice question, the form issued forth a proclomation:
-
-> YOU ARE A VISUAL LEARNER.
-
-Over the course of my years at school I would learn this piece
-of information to be on the whole useless and entirely
-innefectual.
-
-To cut a long story short, school and university didn't go
-particularly well for me.
-
-### Learning by accident
-
-It's somewhat embarassing to admit, but I don't think I really
-learned how to teach myself things effectively until at least
-the age of 26.
-
-When I look back, I realise much of my learning
-was a result of accidental chance; for example, I learned to
-program mostly by virtue of complete immersion and stumbling
-upon 'the right things'. I just followed my nose (and to be fair
-it worked out).
-
-But now I'm an adult with a job and a family, and my time is
-far more precious. If I want to learn something new, I can't
-count on stumbling on the right approach anymore. Simply
-creating the conditions in which learning is possible is not
-sufficient, since I lack the time that approach requires.
-
-### Enter Mentor
-
-How I learned to learn is, surprisingly, not at all related to
-my area of study or profession (software), but a hobby.
-
-I met my whistle teacher Becky in 2020 on a video call. It
-was near the start of the pandemic, and I had some extra time
-on my hands. Becky is somewhat unlike other teachers I've had,
-in that she could mechanically break down and explain music to
-me in a way that allowed me not just to play it, but understand
-what I was playing.
-
-The whistle is a small, cheap, end-blown flute -- somewhat like a
-budget recorder. It has six tone holes that you cover with three
-fingers of either hand, and a mouthpiece that you blow in to.
-Out of this deceptively simple instrument, you can get two full
-octaves of range in two major keys and all the keys that come
-along with that. That is to say, it's small, but surprisingly
-versatile.
-
-Irish traditional music, along with other traditional music
-of the British isles, is generally learned and played by ear,
-though sheet music is available. What sets this folk music
-apart from most classical and contemporary music is that the
-sheet music is essentially a suggestion. Using your skill,
-wits, and guile, you have to take a couple of parts of
-a repetitive tune and make it *interesting*.
-
-This is where Becky comes in, with two really solid pieces of advice:
-one about how to practice, and one about how to learn.
-
-### Mindful Practice
-
-The first of the things Becky taught me was how to practice; how to
-gain the mechanical skill required to execute what I knew, and to
-transform my knowledge into understanding.
-
-> "Know what you're going to practice before you pick up the whistle"
-
-In retrospect, startlingly obvious. Noodling around randomly does
-very little but ingrain what you've already learned;
-**if you want to learn something new then you have to have a goal**.
-But, as with many things in life, having a plan (even if you have no
-idea what you're getting in to) is half the battle.
-
-> "Five minutes every day is better than an hour once per week"
-
-This subtly implies something else; you should make your practice
-not just habit and routine, but low-friction and easy to access.
-You will do much better chipping away at small, easy problems,
-than trying to tackle a hard problem you are not ready for (and
-all hard problems are just *n* easy problems in a trenchcoat anyway).
-
-> "Do it slow until you can do it right"
-
-This one has a very "slow is smooth, smooth is fast" kind of vibe,
-and it's right. Doing something slowly and accurately is **hard**.
-In a programming context, it's very easy to whack together some code
-that delivers a feature, but the end result is of limited use
-if it's buggy, doesn't actually deliver the desired feature, or is
-riddled with security or structural issues.
-
-### Learning to Learn
-
-The second thing Becky taught me was how to learn. Specifically,
-how to learn an instrument, or a tune -- the approach is broadly
-the same.
-
-It's a model, and as my colleague Craig likes to say; all models
-are wrong, but some are useful. Hopefully this is one of the latter.
-
-> "First, learn the notes"
-
-You have to be able to play a tune through in its entirety. Obvious,
-right? But there's a lot here -- some tunes are difficult. Some tunes
-don't have a written form, and you'll have to learn them by ear. Worse,
-some tunes have very poorly transcribed sheet music that will lead
-you astray! Not all learning material is created equal.
-
-Beyond that, you will also have to contend with your own (lack of)
-mechanical skill. You will likely get stumped by a strange combination
-of notes. Perhaps you're not accustomed to switching octaves so quickly,
-or you accidentally bang out the wrong note because of some pattern
-you've learned in a previous tune.
-
-> "Second, make it more interesting"
-
-Boy, isn't that '*interesting*' a really load-bearing word? In this
-context, it means adding ornamentation, variation, dynamics, and
-a plethora of other tools in the folk musician's toolbox.
-
-Making the piece flow better - the folk musician's version of 'clean code' -
-is key to turning a roughly shaped tune into refined birdsong.
-
-> "Third, play the tune"
-
-Taking all you have learned, play the **tune**. Not the notes.
-Not the parts. The whole thing. Feel the rhythm, lean in to the swing,
-and add to it with your performance.
-
-This is not just the culmination of learning how to play the tune - it's
-the start of learning the meta-skill of picking up and playing the
-tunes you come across.
-
-In code terms; release it. You must make mistakes to improve, and
-**more often than not you will be startled how others see something that is so mundane to you as so magical**.
-
-### Some observations
-
-This is not a new topic, or a new field. People much smarter and
-more experienced than me have studied this for a long time, but I share it
-in hopes that for someone out there, the same realisations I had will click.
-
-If you want to read more on this topic, take a look at:
-* [Shuhari](https://en.wikipedia.org/wiki/Shuhari)
-* [The Dreyfus Model of Skill Acquisition](https://en.wikipedia.org/wiki/Dreyfus_model_of_skill_acquisition)
-* [The four stages of competence](https://en.wikipedia.org/wiki/Four_stages_of_competence)
-
-And if I've somehow convinced you to listen to whistle music:
-* [Flow, In The Year of Wu Wei - Brian Finnegan](https://www.youtube.com/watch?v=-6TS7eiKZO8)
-* [Slide from Grace/The Queen of Rangoon - Kan](https://www.youtube.com/watch?v=frH7A7a00NY)
-* [Farewall to Uist/The Lochaber Badger/RIP the Calico - Fred Morrison, Michael McGoldrick and Donal Lunny](https://www.youtube.com/watch?v=qVztCC37wlc)
-
-Thanks readers, and thanks Becky!
-
-[Discussion of this article on Hacker News](https://news.ycombinator.com/item?id=31797431)
diff --git a/_posts/2022-06-22-perfect-isnt-good-enough.md b/_posts/2022-06-22-perfect-isnt-good-enough.md
deleted file mode 100644
index ae5c9ab..0000000
--- a/_posts/2022-06-22-perfect-isnt-good-enough.md
+++ /dev/null
@@ -1,133 +0,0 @@
----
-layout: post
-title: "Perfect isn't Good Enough"
-date: 2022-06-22
-tags: [perfect, perfection, good-enough, laziness]
----
-
-> Perfect is the enemy of the good
-
-Or so goes the old adage. For developers, perfection is a mightier enemy
-than users, project managers, and even one's own lack of skill.
-
-### In pursuit of greatness
-
-Many junior (and senior!) engineers strive to write the *best* code.
-Clean code. Well-tested. Fully covered. Abstractions on abstractions
-making beautiful, elegant interfaces.
-
-Beautiful, elegant, fragile interfaces, which shatter upon exposure to
-the expectations of an ever-changing world.
-
-No (useful) system exists in a vacuum. Users' needs change over time,
-as does the substrate on which software relies. **Nothing is permanent.**
-
-### The worst thing I've ever built
-
-Very early in my career, I built a system to click through HTML forms,
-submit some information and return some information. There weren't APIs
-for this kind of thing, so traversing through legacy websites was the
-only way.
-
-Even with that short description, you've probably already thought how you
-would do it; and most of you probably have a better plan than I, a junior
-developer at the time, did.
-
-So I birthed a new **language** in to the world. A powerful tool to be sure.
-By issuing a list of simple commands, a headless browser would go off and
-perform the commands, fill the forms, and return the information we needed.
-
-Great! Right?
-
-But then there were different forms that were differently bad. Commands had
-to change and gain options. Commands were constantly bug-fixed. The code
-that ran was stored in a SQL database, so changing anything required a full
-run of the system and debugging was waiting for logs to populate. Every
-new integration made the system heavier and bigger and buggier.
-
-If you're cringing -- that's correct. If you're not -- come back in a couple
-years (after you finish reading this).
-
-
-### The making of burdens
-
-Why was that system so bad? What was it that I did that made it so
-inextensible and brittle?
-
-The first reason is **greed**. I wanted to make something *cool*. I sought
-a challenge where non existed! The problem I was seeking to solve was
-primarily my own creation.
-
-The second reason is **ignorance**. I naively believed that the original
-purpose of the system is all it would ever do.
-
-The third, and perhaps worst reason, is **hubris**. Perfection for my own
-selfish reasons -- not for the user, but the maker.
-
-I didn't learn how to control each of these right away, and still combat
-each of these in my working and hobby lives. In moderation, these things
-are a positive force for learning, but when they take over -- beware.
-
-### A successful mistake
-
-My most recent relapse was creating an internal tool for a team to register
-public keys into a repository. By most measures I did an extremely good
-job of it -- it was reliable, did only what it needed to, and was easy
-for me to extend.
-
-> 'easy for me to extend'
-
-Ah. Now there was the problem. This tool I created became a victim of its
-own success and, due to other commitments, changes needed to be made by
-other members of my team.
-
-Which, for a little web frontend, would have been fine. Had I been on a
-team used to writing purely functional code, and had I not written it in
-Elm (no shade to Elm, it's a fantastic language, but it was the wrong
-choice in this context).
-
-I had failed to keep in mind the future life of this thing I had created.
-
-Code, much like art, ceases to belong to its author once it is released
-in to the world.
-
-### Valueless perfection and glorious mediocrity
-
-Each of these issues of greed, ignorance, and hubris can be avoided through
-a judicious application of mediocrity.
-
-The key to this is recognising that the time you spend trying to create
-something perfect is not only extremely expensive (you could release something
-much earlier), but also holds little value. A program is worthless until it
-is used, and will not grow in value if you make it too hard to change.
-
-This doesn't mean you shouldn't experiment and sharpen your skills - just
-make sure you are aiming at a target, and not your own or someone else's
-foot.
-
-### Doing the dumbest possible thing
-
-It's here, then, that I urge you to **do the dumbest possible thing**.
-
-That may be somewhat hyperbolic, but here's what I mean by it:
-
-* Your code probably isn't written to serve you, so before anything else
- make sure it serves the user's needs. You have plenty of time to play
- on your own time.
-
-* Don't write more than you need to. Doing anything more than is sufficient
- is a waste of time. One-off script? Don't write tests for it. Wrote the
- same thing twice? Great, leave it. You don't know if every use case is going
- to look like that (aka [Avoid Hasty Abstractions](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)).
-
-* Get it out the door. The code you write has no value until it is used.
- Your assumptions can only be validated if you have something to test against!
- This doesn't mean you should ship completely broken stuff - what
- it does mean, however, is that you need a full solution to the problem,
- even if it's a crappy solution, so that you can make sure you're building
- the right thing.
-
-* Optimise nothing. You don't know what's going to be the slowest part yet,
- so don't waste effort solving a problem you don't have.
-
-In other words; make it **Just Good Enough** to do what you need.
diff --git a/_posts/2022-07-07-the-continuous-delivery-test.md b/_posts/2022-07-07-the-continuous-delivery-test.md
deleted file mode 100644
index 28fd146..0000000
--- a/_posts/2022-07-07-the-continuous-delivery-test.md
+++ /dev/null
@@ -1,158 +0,0 @@
----
-layout: post
-title: "The Continuous Delivery Test"
-date: 2022-07-07
-tags: [joel-test, devops, continuous-delivery]
----
-
-In 2000, Joel Spolsky published a piece on what he believed were the
-key factors that separated high-performing teams from the rest, in a post titled
-[The Joel Test: 12 Steps to Better Code](https://www.joelonsoftware.com/2000/08/09/the-joel-test-12-steps-to-better-code/).
-It contains many insights that are still useful to this day; however, the world,
-software, and the teams that make it have moved on in the nearly 22 years since
-that post.
-
-In 2015, John Kodumal of LaunchDarkly published an updated [Joel Test for Continuous Delivery](https://circleci.com/blog/joel-test-for-continuous-delivery/).
-While being far more up-to-date, it's still slipped a little in relevance.
-
-If this post were to have an alternate title, it'd be 'Front-load Your Risks'.
-All of the advice below seeks to move risks away from the act of deployment
-and move them to earlier steps of the development process.
-
-So, here's my best shot at a CI/CD Joel Test for the 2020s.
-
-
-### 1. Do you use a distributed version control system?
-
-Git, GitHub, and the ecosystems around them have eaten the software world,
-and for good reason. Beyond the usual advantages - branching, merging,
-and easy rollbacks, many of these hosted git servers now come with a whole slew
-of other productivity tooling.
-
-Especially notable are those that move your CI closer to your source code,
-such as GitHub Actions, and GitLab CI/CD.
-
-
-### 2. Do you practice [trunk-based development](https://trunkbaseddevelopment.com/)?
-
-Trunk-based development rolls up a bunch of good practices under one system.
-
-It forces you to have one good `main` branch that is always deployable (and by implication,
-always able to roll back), and encourages merging small features one at a time.
-
-Having an always-deployable `main` means that your deployment process becomes
-a lot more boring (which is great!), and is often indicative of a process
-that supports developers, rather than requiring long hours spent resolving
-merge conflicts and preparing release notes.
-
-
-### 3. Do you merge little and often?
-
-It's been known for some time now that large pull requests tend to get
-cursory reviews at best. On top of this, squirrelling away to work on a single
-feature for a long time means you are not spending adequate time making sure
-that you are building the right thing!
-
-This also means using small, ticket-scoped feature branches, where the changes
-have a very tight scope.
-
-
-### 4. Do two people read code before it is merged?
-
-You'll notice I was very careful to avoid mentioning how this should take place.
-For some, pull requests are the right tool, but pairing and mob programming are
-also sufficient.
-
-Nobody should be solely responsible for any code.
-
-
-### 5. Do you require changes to pass checks before they can be merged?
-
-One of the leading causes of outages is deployments. You should do your best to
-make sure that broken code never gets deployed in the first place.
-
-
-### 6. Can you test your changes in a production-like environment before you deploy them?
-
-There are many ways to go about this. You could spin up a small clone of your
-production env, run the tests, and then kill it. Where this is not possible,
-you can run tests against mirrored traffic or even run a canary.
-
-
-### 7. Do you deploy to production as soon as `main` is updated?
-
-Your deployments should be automated and happen right upon merge. This removes a burden
-from the team delivering a product and allows them to focus on features and reliability
-rather than long-winded release ceremonies. It can be hard to get here if you're currently releasing
-once a day, week, etc. -- a mature approach to managing the risks of deployment
-is required, as well as battle-tested deployment processes and tools.
-
-The earlier you start doing this, the easier it will be.
-
-
-### 8. Does your deploy process self-heal?
-
-When a deployment fails, can you roll back automatically? In addition to this, you should
-be able to roll back manually where necessary (though it's generally best to fail forwards!)
-
-This also necessitates the use of blue-green deploys, canarying, healthchecks, and myriad
-other strategies to mitigate outage risk. It's very powerful to be able to turn a
-possible outage into a mere degradation.
-
-
-### 9. Does your Infrastructure as Code live alongside the service it hosts?
-
-It's a good idea to colocate any IaC with the software it's running rather than keeping
-the two in separate repositories. This dodges any dependency management between the two,
-and in many cases will save you a pipeline or two.
-
-It's an antipattern (in the author's opinion) to have all infrastructure code completely
-separate from the application it supports. Common or shared infrastructure could be argued to
-have its own place, but a product should have all its pieces as close together as possible.
-
-[Monorepos](https://semaphoreci.com/blog/what-is-monorepo) are an extreme example of this philosophy.
-
-
-### 10. Do you use feature flags?
-
-Releases are a terrible mechanism for launching new features. You want to be able to
-run that shiny new feature in production as soon as possible, and you can't do that
-if you conflate a feature's launch with its release. This also means that you can
-preview and refine changes with flagship customers before rolling them out more widely.
-
-
-### 11. Do you include ticket IDs in your commits or branches?
-
-You need to be able to tie every commit back to the ticket that spawned it. It might not
-be you debugging a change that you merged, so it needs to be really easy to go back
-and find out *why* something was changed in order to make the right decision to
-fix it.
-
-
-### 12. Can you still deploy from your own machine?
-
-A key factor in developing software and infrastructure quickly is tight feedback
-loops. While it's probably not a great idea to be deploying directly to production
-from your own machine, having the ability to do so might be useful in a pinch.
-
-You should be able to deploy to a dev environment (shared or ephemeral) such that
-you can easily validate your work in situ.
-
-
-### 13. Can you show what will happen when a branch is merged?
-
-Most IaC tools offer the ability to diff the changes they want to make. It's
-vitally important that when you go to merge some changes in, you know what the
-changes are, why you're making them (see point 11), and the effect the changes
-will have on the product or system.
-
-This could be a `cdk diff`, or a visual diff if you're making a change to a
-frontend. Seldom is the code itself the interesting and crucial part of
-why you're making a change. The effect of your changeset should be evident
-not only to reviewers but also to bleary-eyed SREs at 4am.
-
-
----
-
-
-*Thanks to [Josh Finch](https://github.com/openfinch) for his valuable feedback on this post.*
diff --git a/_posts/2022-07-29-put-ticket-ids-in-your-commit-messages.md b/_posts/2022-07-29-put-ticket-ids-in-your-commit-messages.md
deleted file mode 100644
index 516b931..0000000
--- a/_posts/2022-07-29-put-ticket-ids-in-your-commit-messages.md
+++ /dev/null
@@ -1,89 +0,0 @@
----
-layout: post
-title: "Put Ticket IDs in your Commit Messages"
-date: 2022-07-29
-tags: [compliance, devops, continuous-delivery]
----
-
-In my previous post [The Continuous Delivery Test](https://sourceless.org/posts/the-continuous-delivery-test.html)
-I included a point:
-
-> Do you include ticket IDs in your commits or branches?
-
-[This sparked quite a lot of discussion on hacker news](https://news.ycombinator.com/item?id=32045651).
-I think it'd be useful to highlight some of my reasoning for including this
-point, and highlight some of the advice given by some of the commenters.
-
-## Why you should do it
-### Link work back to the ticket that spawned it
-If you're in anywhere but the smallest companies, you probably have some sort of
-compliance requirements that require you to be able to link work done back to
-the planning that caused it to happen.
-
-The reason that compliance frameworks want you to do this is mainly so there is
-an unbroken chain of culpability that can be used to identify the root cause
-of a potential issue.
-
-But it's also just good documentation. Do you really want to play archeologist
-every time you need to figure out why a certain commit was made, or why a
-certain line of code is a bit weird?
-
-As one lovely HN commenter puts it:
-> 'I can't tell you how many times I've dug into the commit history to find where
-> a change happened, then wanted to know the full context, and been glad I could
-> just pull up the years-old ticket directly and see a detailed description of
-> intent and requirements, discussions in the comments, mock-ups, etc etc'
-
-### Track the content of releases
-In some environments you might need to prepare release notes or a changelog.
-
-This is, of course, much easier if you can derive the list of tickets from the
-delta of what's going in to production.
-
-### Measure how well your team is delivering
-In [Accelerate](https://www.oreilly.com/library/view/accelerate/9781457191435/),
-Forsgren, Humble, and Kim pick out four metrics that seperate high performing
-teams from the rest - and they all have to do with deployment.
-
-The first of those metrics is **Lead Time**, which measures the amount of time
-it takes from starting a ticket (or the first commit made, depending how you
-want to measure) to when it's eventually released.
-
-Having some metadata available makes this much easier to do!
-
-### It's not that hard
-Seriously, it's just habit. It might sound annoying or bureaucratic to you if
-you've never done it before, but after a couple weeks it'll probably be an
-unconscious practice that you'll never think about again.
-
-And if typing <10 extra characters at the start of a commit message is really
-cramping your style, you can always write a `prepare-commit-msg` git hook.
-
-## Why you might not be doing it
-### Your company is small
-If you're starting to grow and compliance is on the horizon, it's much easier
-to start now than when the deadline looms.
-
-### It's a personal project
-Yep. Why would you? It's needless overhead.
-
-### Merging/branching strategy
-I'm probably not going to convince you otherwise, but I think long-lived feature
-branches **suck**. They require complex merge patterns, bundle risk, and often
-require a bureaucratic ritual to merge in to a production branch.
-
-[Trunk-based development](https://trunkbaseddevelopment.com/) all the way,
-and if you can have a branch per ticket *with the ticket ID in the branch name*,
-even better.
-
-### I need to make an ad-hoc change
-This usually falls in to one of two categories.
-
-The first is "I want to make a change off the books". Usually this is a culture
-problem; you should feel empowered to create small tickets as you see fit to
-fix small issues. It might also be that your team doesn't create time for care
-and feeding work.
-
-The second is "I don't know where this change belongs". Can you roll it in to
-another ticket you're already working on, e.g. one where you're already touching
-the code in question? Or maybe it's enough work that it should be its own item?
diff --git a/_posts/2022-08-16-make-your-own-free-password-manager.md b/_posts/2022-08-16-make-your-own-free-password-manager.md
deleted file mode 100644
index 292ac3a..0000000
--- a/_posts/2022-08-16-make-your-own-free-password-manager.md
+++ /dev/null
@@ -1,144 +0,0 @@
----
-layout: post
-title: "Make your own (free) password manager"
-date: 2022-08-16
-tags: [passwords, security, selfhost]
----
-
-> Before I say anything else, it should be noted that this solution requires
-> a little bit of background knowledge to set up and use. At the very least
-> you'll need to be comfortable on the command line in linux/osx, especially
-> with installing packages, git, ssh, and a little gpg. Don't worry if you don't
-> know all of that -- a little familiarity should be enough.
-
-### Why I rolled my own
-
-You might be wondering what the point is of setting up your own password
-manager is when so many good, paid alternatives exist -- and you'd be right!
-
-There is however, some advantage in knowing exactly how your passwords are
-encrypted and being able to integrate your password store with **EVERYTHING**.
-
-And it's run at no cost to me.
-
-Standard warnings apply; the UX will not be as good. If you do not back up
-your GPG keys, you could lose access to your passwords forever. The security
-of your password manager is only as good as the security of your system.
-
-### Features and composition
-
-Our password manager will have the following features:
-* Encrypted at rest
-* Command line, web browser, and mobile clientship
-* Per-device revocation
-* Cross-device sync
-
-We'll make it with these components:
-* [password-store](https://www.passwordstore.org/)
-* gpg
-* git
-
-
-**Make sure you have all the above installed before continuing!**
-
-### Steps
-#### 1. Generate a gpg key
-There's lots of guides on doing this, but here's an example (from 2022, if
-you're reading this in the future, google it first because best practice has
-probably changed!).
-
-```
-gpg --default-new-key-algo "ed25519/cert,sign+cv25519/encr" \
- --quick-generate-key "my@email.com"
-```
-
-This will generate a new multi-purpose ed25519 key with no expiration date.
-
-It's up to you if you want to use this to generate subkeys to use for the
-password store. I would, however, recommend that you make one key per device
-sou you can revoke access if a device is lost or stolen!
-
-If you've already got a gpg key and want to use it -- use that.
-
-**And before anything else - back up your key! On a storage drive, paper,
-obelisk, yubikey - and in more than one place!**
-
-#### 2. Init the password store
-```
-pass init
+
+
+
diff --git a/about.md b/about.md
deleted file mode 100644
index 502c2e7..0000000
--- a/about.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-layout: page
-title: about
-permalink: /about.html
-nav_order: 1
----
-Hi, I'm Laurence.
-
-I'm a Director of SRE in the AdTech industry.
-In previous lives I've done SRE, security, and software in Healthcare and Government.
-
-Notable past projects include:
-* [NHS Digital's API Platform](https://digital.nhs.uk/developer/api-catalogue)
-* [data.gov.uk](https://data.gov.uk)
-
-My topics of particular interest are:
-* Building and running high performing teams
-* User-first security, compliance, and governance
-* Logic programming
-* Type systems
-
-I've worked on a lot of projects in a lot of languages and frameworks; the best
-of those were backed by great teams that knew their tools inside out.
-
-If you want to talk, or just want to see a CV, [check out my linkedin](https://www.linkedin.com/in/laurenceps/).
diff --git a/assets/css/main.css b/assets/css/main.css
index 0b02226..138f692 100644
--- a/assets/css/main.css
+++ b/assets/css/main.css
@@ -1,292 +1,42 @@
-:root {
- --bg: #323F4B;
- --text: #CBD2D9;
- --link: #F5F7FA;
- --code-bg: #1F2933;
- --code-text: #E4E7DB;
- --border: white;
-}
-
-@media (prefers-color-scheme: light) {
- :root:not([data-theme="dark"]) {
- --bg: #F5F7FA;
- --text: #323F4B;
- --link: #1F2933;
- --code-bg: #E4E7EB;
- --code-text: #1F2933;
- --border: #323F4B;
- }
-}
-
-[data-theme="light"] {
- --bg: #F5F7FA;
- --text: #323F4B;
- --link: #1F2933;
- --code-bg: #E4E7EB;
- --code-text: #1F2933;
- --border: #323F4B;
-}
-
body {
height: 100vh;
- background: var(--bg);
- color: var(--text);
- font-family: 'IBM Plex Mono', monospace;
- font-size: 16px;
- line-height: 1.6;
- margin: 0;
+ background: #323F4B;
+ color: #CBD2D9;
}
a {
- color: var(--link);
+ color: #F5F7FA;
}
code {
- background: var(--code-bg);
- color: var(--code-text);
- font-family: 'IBM Plex Mono', monospace;
- font-size: 1.1em;
-}
-
-nav {
- font-family: 'IBM Plex Mono', monospace;
- font-size: 1.2em;
- margin-bottom: 2em;
+ background: #1F2933;
+ color: #E4E7DB;
}
#content {
- background: var(--bg);
+ background: #323F4B;
display: block;
width: 40em;
- margin: 3em auto;
- padding-bottom: 10em;
- position: relative;
-}
-
-h1, h2, h3, h4, h5, h6 {
- font-family: 'IBM Plex Mono', monospace;
+ margin: 10em auto;
}
-.site-title {
- font-family: 'IBM Plex Mono', monospace;
- font-size: 2.2em;
- margin: 0;
-}
-
-.site-title a {
- color: inherit;
- text-decoration: none;
-}
-
-.ascii-logo {
- display: block;
- white-space: pre;
- font-size: 0.28em;
- line-height: 1.2;
-}
-
-.sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- border: 0;
-}
-
-h1, h2 {
- font-size: 1.6em;
-}
-
-h1, h2, h3, h4, h5, h6 {
+h2, h3, h4, h5, h6 {
margin-top: 3em;
}
-/* Below the breakpoint the 40em column no longer fits, so let #content fill the
- viewport and carry its own gutters. Without them the text runs edge to edge. */
@media only screen and (max-width: 768px) {
#content {
- width: auto;
- margin: 2em 0;
- padding: 0 1.25em 6em;
- }
-
- blockquote {
- margin-left: 0;
- padding-left: 1em;
+ width: 100%;
}
}
pre {
- background: var(--code-bg);
- padding: 1em;
- overflow-x: auto;
+ background: #1F2933;
+ padding: 1em;
}
blockquote {
- border-left: 0.5em solid var(--border);
+ border-left: 0.5em solid white;
padding-left: 1.5em;
margin-left: 2em;
}
-
-hr {
- border-color: var(--border);
-}
-
-.skip-link {
- position: absolute;
- left: -9999px;
- top: auto;
- width: 1px;
- height: 1px;
- overflow: hidden;
-}
-
-.skip-link:focus {
- position: fixed;
- top: 0;
- left: 0;
- width: auto;
- height: auto;
- padding: 0.5em 1em;
- background: var(--code-bg);
- color: var(--link);
- z-index: 100;
-}
-
-main {
- margin-top: 3em;
-}
-
-main > h2:first-child {
- margin-top: 0;
-}
-
-.post-meta {
- margin-top: 2em;
-}
-
-.post-nav {
- margin-top: 1em;
-}
-
-.post-list {
- list-style: none;
- padding-left: 0;
- margin-top: 2em;
-}
-
-.post-list li {
- margin-bottom: 0.5em;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.post-date {
- font-family: 'IBM Plex Mono', monospace;
- margin-right: 1em;
-}
-
-/* Generated fractal artwork, one per post. Both theme variants ride on custom
- properties set inline per post, so the swap below is a single pair of rules
- rather than per-post CSS. */
-.post-banner {
- height: 12em;
- margin-bottom: 2em;
- background-image: var(--banner-dark);
- background-size: cover;
- background-position: center;
-}
-
-.post-banner + .page-title {
- margin-top: 0;
-}
-
-.post-thumb {
- display: inline-block;
- width: 2em;
- height: 2em;
- margin-right: 0.75em;
- vertical-align: -0.7em;
- background-image: var(--thumb-dark);
- background-size: cover;
- background-position: center;
-}
-
-@media (prefers-color-scheme: light) {
- :root:not([data-theme="dark"]) .post-banner {
- background-image: var(--banner-light);
- }
-
- :root:not([data-theme="dark"]) .post-thumb {
- background-image: var(--thumb-light);
- }
-}
-
-[data-theme="light"] .post-banner {
- background-image: var(--banner-light);
-}
-
-[data-theme="light"] .post-thumb {
- background-image: var(--thumb-light);
-}
-
-.header-row {
- display: flex;
- justify-content: space-between;
- align-items: baseline;
-}
-
-.header-row .site-title {
- margin: 0;
-}
-
-.theme-toggle {
- background: none;
- border: none;
- color: var(--text);
- font-family: 'IBM Plex Mono', monospace;
- font-size: 1.2em;
- cursor: pointer;
- padding: 0;
-}
-
-.theme-toggle:hover {
- opacity: 0.7;
-}
-
-.read-aloud {
- display: flex;
- align-items: center;
- gap: 0.75em;
- padding-bottom: 1em;
- margin-bottom: 2em;
- border-bottom: 1px solid var(--border);
- font-size: 0.9em;
-}
-
-.read-aloud button {
- background: none;
- border: none;
- color: var(--text);
- font-family: 'IBM Plex Mono', monospace;
- font-size: 1em;
- cursor: pointer;
- padding: 0;
-}
-
-.read-aloud button:hover {
- opacity: 0.7;
-}
-
-.read-aloud label {
- margin-left: 0.5em;
-}
-
-.read-aloud input[type="range"] {
- width: 6em;
- accent-color: var(--text);
-}
diff --git a/assets/og/default-light.png b/assets/og/default-light.png
deleted file mode 100644
index 29204cf..0000000
Binary files a/assets/og/default-light.png and /dev/null differ
diff --git a/assets/og/default-thumb-light.png b/assets/og/default-thumb-light.png
deleted file mode 100644
index da59c27..0000000
Binary files a/assets/og/default-thumb-light.png and /dev/null differ
diff --git a/assets/og/default-thumb.png b/assets/og/default-thumb.png
deleted file mode 100644
index 0874a12..0000000
Binary files a/assets/og/default-thumb.png and /dev/null differ
diff --git a/assets/og/default.png b/assets/og/default.png
deleted file mode 100644
index 60288f0..0000000
Binary files a/assets/og/default.png and /dev/null differ
diff --git a/assets/og/dont-read-the-code-light.png b/assets/og/dont-read-the-code-light.png
deleted file mode 100644
index d705d87..0000000
Binary files a/assets/og/dont-read-the-code-light.png and /dev/null differ
diff --git a/assets/og/dont-read-the-code-thumb-light.png b/assets/og/dont-read-the-code-thumb-light.png
deleted file mode 100644
index 5812866..0000000
Binary files a/assets/og/dont-read-the-code-thumb-light.png and /dev/null differ
diff --git a/assets/og/dont-read-the-code-thumb.png b/assets/og/dont-read-the-code-thumb.png
deleted file mode 100644
index b8f3488..0000000
Binary files a/assets/og/dont-read-the-code-thumb.png and /dev/null differ
diff --git a/assets/og/dont-read-the-code.png b/assets/og/dont-read-the-code.png
deleted file mode 100644
index d370aef..0000000
Binary files a/assets/og/dont-read-the-code.png and /dev/null differ
diff --git a/assets/og/how-i-built-this-site-light.png b/assets/og/how-i-built-this-site-light.png
deleted file mode 100644
index 4dd151e..0000000
Binary files a/assets/og/how-i-built-this-site-light.png and /dev/null differ
diff --git a/assets/og/how-i-built-this-site-thumb-light.png b/assets/og/how-i-built-this-site-thumb-light.png
deleted file mode 100644
index 94b9971..0000000
Binary files a/assets/og/how-i-built-this-site-thumb-light.png and /dev/null differ
diff --git a/assets/og/how-i-built-this-site-thumb.png b/assets/og/how-i-built-this-site-thumb.png
deleted file mode 100644
index b9f843f..0000000
Binary files a/assets/og/how-i-built-this-site-thumb.png and /dev/null differ
diff --git a/assets/og/how-i-built-this-site.png b/assets/og/how-i-built-this-site.png
deleted file mode 100644
index b9a593a..0000000
Binary files a/assets/og/how-i-built-this-site.png and /dev/null differ
diff --git a/assets/og/make-your-own-free-password-manager-light.png b/assets/og/make-your-own-free-password-manager-light.png
deleted file mode 100644
index a216bb6..0000000
Binary files a/assets/og/make-your-own-free-password-manager-light.png and /dev/null differ
diff --git a/assets/og/make-your-own-free-password-manager-thumb-light.png b/assets/og/make-your-own-free-password-manager-thumb-light.png
deleted file mode 100644
index bb4f047..0000000
Binary files a/assets/og/make-your-own-free-password-manager-thumb-light.png and /dev/null differ
diff --git a/assets/og/make-your-own-free-password-manager-thumb.png b/assets/og/make-your-own-free-password-manager-thumb.png
deleted file mode 100644
index 30d360e..0000000
Binary files a/assets/og/make-your-own-free-password-manager-thumb.png and /dev/null differ
diff --git a/assets/og/make-your-own-free-password-manager.png b/assets/og/make-your-own-free-password-manager.png
deleted file mode 100644
index 91c3aa8..0000000
Binary files a/assets/og/make-your-own-free-password-manager.png and /dev/null differ
diff --git a/assets/og/perfect-isnt-good-enough-light.png b/assets/og/perfect-isnt-good-enough-light.png
deleted file mode 100644
index 377a6a0..0000000
Binary files a/assets/og/perfect-isnt-good-enough-light.png and /dev/null differ
diff --git a/assets/og/perfect-isnt-good-enough-thumb-light.png b/assets/og/perfect-isnt-good-enough-thumb-light.png
deleted file mode 100644
index b70024e..0000000
Binary files a/assets/og/perfect-isnt-good-enough-thumb-light.png and /dev/null differ
diff --git a/assets/og/perfect-isnt-good-enough-thumb.png b/assets/og/perfect-isnt-good-enough-thumb.png
deleted file mode 100644
index b71ca52..0000000
Binary files a/assets/og/perfect-isnt-good-enough-thumb.png and /dev/null differ
diff --git a/assets/og/perfect-isnt-good-enough.png b/assets/og/perfect-isnt-good-enough.png
deleted file mode 100644
index 8d65d6f..0000000
Binary files a/assets/og/perfect-isnt-good-enough.png and /dev/null differ
diff --git a/assets/og/pifs-1-functions-light.png b/assets/og/pifs-1-functions-light.png
deleted file mode 100644
index df7481c..0000000
Binary files a/assets/og/pifs-1-functions-light.png and /dev/null differ
diff --git a/assets/og/pifs-1-functions-thumb-light.png b/assets/og/pifs-1-functions-thumb-light.png
deleted file mode 100644
index 7595105..0000000
Binary files a/assets/og/pifs-1-functions-thumb-light.png and /dev/null differ
diff --git a/assets/og/pifs-1-functions-thumb.png b/assets/og/pifs-1-functions-thumb.png
deleted file mode 100644
index e0e9a2c..0000000
Binary files a/assets/og/pifs-1-functions-thumb.png and /dev/null differ
diff --git a/assets/og/pifs-1-functions.png b/assets/og/pifs-1-functions.png
deleted file mode 100644
index 5619a9f..0000000
Binary files a/assets/og/pifs-1-functions.png and /dev/null differ
diff --git a/assets/og/put-ticket-ids-in-your-commit-messages-light.png b/assets/og/put-ticket-ids-in-your-commit-messages-light.png
deleted file mode 100644
index a348e54..0000000
Binary files a/assets/og/put-ticket-ids-in-your-commit-messages-light.png and /dev/null differ
diff --git a/assets/og/put-ticket-ids-in-your-commit-messages-thumb-light.png b/assets/og/put-ticket-ids-in-your-commit-messages-thumb-light.png
deleted file mode 100644
index 3bd7ffd..0000000
Binary files a/assets/og/put-ticket-ids-in-your-commit-messages-thumb-light.png and /dev/null differ
diff --git a/assets/og/put-ticket-ids-in-your-commit-messages-thumb.png b/assets/og/put-ticket-ids-in-your-commit-messages-thumb.png
deleted file mode 100644
index 09416fc..0000000
Binary files a/assets/og/put-ticket-ids-in-your-commit-messages-thumb.png and /dev/null differ
diff --git a/assets/og/put-ticket-ids-in-your-commit-messages.png b/assets/og/put-ticket-ids-in-your-commit-messages.png
deleted file mode 100644
index 7838244..0000000
Binary files a/assets/og/put-ticket-ids-in-your-commit-messages.png and /dev/null differ
diff --git a/assets/og/relearning-to-learn-light.png b/assets/og/relearning-to-learn-light.png
deleted file mode 100644
index 9f6d6f1..0000000
Binary files a/assets/og/relearning-to-learn-light.png and /dev/null differ
diff --git a/assets/og/relearning-to-learn-thumb-light.png b/assets/og/relearning-to-learn-thumb-light.png
deleted file mode 100644
index 1f79061..0000000
Binary files a/assets/og/relearning-to-learn-thumb-light.png and /dev/null differ
diff --git a/assets/og/relearning-to-learn-thumb.png b/assets/og/relearning-to-learn-thumb.png
deleted file mode 100644
index a40eb8c..0000000
Binary files a/assets/og/relearning-to-learn-thumb.png and /dev/null differ
diff --git a/assets/og/relearning-to-learn.png b/assets/og/relearning-to-learn.png
deleted file mode 100644
index a7b3315..0000000
Binary files a/assets/og/relearning-to-learn.png and /dev/null differ
diff --git a/assets/og/the-continuous-delivery-test-light.png b/assets/og/the-continuous-delivery-test-light.png
deleted file mode 100644
index 18f674e..0000000
Binary files a/assets/og/the-continuous-delivery-test-light.png and /dev/null differ
diff --git a/assets/og/the-continuous-delivery-test-thumb-light.png b/assets/og/the-continuous-delivery-test-thumb-light.png
deleted file mode 100644
index 5965f74..0000000
Binary files a/assets/og/the-continuous-delivery-test-thumb-light.png and /dev/null differ
diff --git a/assets/og/the-continuous-delivery-test-thumb.png b/assets/og/the-continuous-delivery-test-thumb.png
deleted file mode 100644
index cec26fa..0000000
Binary files a/assets/og/the-continuous-delivery-test-thumb.png and /dev/null differ
diff --git a/assets/og/the-continuous-delivery-test.png b/assets/og/the-continuous-delivery-test.png
deleted file mode 100644
index 37f84df..0000000
Binary files a/assets/og/the-continuous-delivery-test.png and /dev/null differ
diff --git a/assets/og/the-documentation-triangle-light.png b/assets/og/the-documentation-triangle-light.png
deleted file mode 100644
index 8e63753..0000000
Binary files a/assets/og/the-documentation-triangle-light.png and /dev/null differ
diff --git a/assets/og/the-documentation-triangle-thumb-light.png b/assets/og/the-documentation-triangle-thumb-light.png
deleted file mode 100644
index 0f27675..0000000
Binary files a/assets/og/the-documentation-triangle-thumb-light.png and /dev/null differ
diff --git a/assets/og/the-documentation-triangle-thumb.png b/assets/og/the-documentation-triangle-thumb.png
deleted file mode 100644
index 8e9acc2..0000000
Binary files a/assets/og/the-documentation-triangle-thumb.png and /dev/null differ
diff --git a/assets/og/the-documentation-triangle.png b/assets/og/the-documentation-triangle.png
deleted file mode 100644
index 6dfa131..0000000
Binary files a/assets/og/the-documentation-triangle.png and /dev/null differ
diff --git a/assets/og/the-phoenix-reborn-light.png b/assets/og/the-phoenix-reborn-light.png
deleted file mode 100644
index 2681472..0000000
Binary files a/assets/og/the-phoenix-reborn-light.png and /dev/null differ
diff --git a/assets/og/the-phoenix-reborn-thumb-light.png b/assets/og/the-phoenix-reborn-thumb-light.png
deleted file mode 100644
index c11cc96..0000000
Binary files a/assets/og/the-phoenix-reborn-thumb-light.png and /dev/null differ
diff --git a/assets/og/the-phoenix-reborn-thumb.png b/assets/og/the-phoenix-reborn-thumb.png
deleted file mode 100644
index 751a8f1..0000000
Binary files a/assets/og/the-phoenix-reborn-thumb.png and /dev/null differ
diff --git a/assets/og/the-phoenix-reborn.png b/assets/og/the-phoenix-reborn.png
deleted file mode 100644
index 8447fbc..0000000
Binary files a/assets/og/the-phoenix-reborn.png and /dev/null differ
diff --git a/contact.html b/contact.html
new file mode 100644
index 0000000..567a5c1
--- /dev/null
+++ b/contact.html
@@ -0,0 +1,34 @@
+
+
+
+
+ sourceless
+
+
+index
+
+ about
+
+ contact
+
+
+
+
+
+
diff --git a/contact.md b/contact.md
deleted file mode 100644
index f3e7cbb..0000000
--- a/contact.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-layout: page
-title: contact
-permalink: /contact.html
-nav_order: 2
----
-* email: laurence@[this website]
-* github: [sourceless](https://github.com/Sourceless)
-* hacker news: [sourceless](https://news.ycombinator.com/user?id=sourceless)
-* linkedin: [laurenceps](https://www.linkedin.com/in/laurenceps)
diff --git a/flake.lock b/flake.lock
deleted file mode 100644
index 89d7110..0000000
--- a/flake.lock
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "nodes": {
- "flake-utils": {
- "inputs": {
- "systems": "systems"
- },
- "locked": {
- "lastModified": 1731533236,
- "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "flake-utils",
- "type": "github"
- }
- },
- "nixpkgs": {
- "locked": {
- "lastModified": 1774701658,
- "narHash": "sha256-CIS/4AMUSwUyC8X5g+5JsMRvIUL3YUfewe8K4VrbsSQ=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "b63fe7f000adcfa269967eeff72c64cafecbbebe",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "flake-utils": "flake-utils",
- "nixpkgs": "nixpkgs"
- }
- },
- "systems": {
- "locked": {
- "lastModified": 1681028828,
- "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
- "owner": "nix-systems",
- "repo": "default",
- "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
- "type": "github"
- },
- "original": {
- "owner": "nix-systems",
- "repo": "default",
- "type": "github"
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/flake.nix b/flake.nix
deleted file mode 100644
index b3e25ff..0000000
--- a/flake.nix
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- description = "sourceless.org - Jekyll site";
-
- inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
- flake-utils.url = "github:numtide/flake-utils";
- };
-
- outputs = { self, nixpkgs, flake-utils }:
- flake-utils.lib.eachDefaultSystem (system:
- let
- pkgs = nixpkgs.legacyPackages.${system};
- ruby = pkgs.ruby_3_3;
- # Used by tools/generate_og_images.py to render per-post OG cards.
- python = pkgs.python3.withPackages (ps: [ ps.numpy ps.pillow ]);
- in {
- devShells.default = pkgs.mkShell {
- buildInputs = [
- ruby
- python
- pkgs.bundler
- pkgs.pkg-config
- pkgs.libffi
- pkgs.libxml2
- pkgs.libxslt
- pkgs.zlib
- pkgs.libyaml
- ];
-
- shellHook = ''
- export GEM_HOME="$PWD/.gem"
- export PATH="$GEM_HOME/bin:$PATH"
- bundle install --quiet 2>/dev/null
- echo "sourceless.org dev shell"
- echo "Run 'bundle exec jekyll serve' to start the dev server"
- '';
- };
- });
-}
diff --git a/index.html b/index.html
index 66a807c..6ae9318 100644
--- a/index.html
+++ b/index.html
@@ -1,3 +1,52 @@
----
-layout: landing
----
+
+
+
+
+ sourceless
+
+
+index
+
+ about
+
+ contact
+
+
+
+
+
+
+
+
+
+
diff --git a/posts.html b/posts.html
deleted file mode 100644
index 7b521df..0000000
--- a/posts.html
+++ /dev/null
@@ -1,4 +0,0 @@
----
-layout: home
-title: posts
----
diff --git a/posts/how-i-built-this-site.html b/posts/how-i-built-this-site.html
new file mode 100644
index 0000000..9ecc862
--- /dev/null
+++ b/posts/how-i-built-this-site.html
@@ -0,0 +1,40 @@
+
+
+
+
+ sourceless
+
+
+index
+
+ about
+
+ contact
+
+
+
+
+
+
+ 2026-03-30 The Phoenix Reborn
+
+ 2023-07-17 Programming in Functional Style: Functions
+
+ 2022-08-16 Make your own (free) password manager
+
+ 2022-07-29 Put Ticket IDs in your Commit Messages
+
+ 2022-07-07 The Continuous Delivery Test
+
+ 2022-06-22 Perfect isn't Good Enough
+
+ 2022-06-19 Relearning to Learn
+
+ 2021-03-19 The Documentation Triangle
+
+ 2021-01-17 Getting github pages to work with my custom site generator
+
+
+
+
+
diff --git a/posts/make-your-own-free-password-manager.html b/posts/make-your-own-free-password-manager.html
new file mode 100644
index 0000000..9333db0
--- /dev/null
+++ b/posts/make-your-own-free-password-manager.html
@@ -0,0 +1,54 @@
+
+
+
+
+ sourceless
+
+
+index
+
+ about
+
+ contact
+
+
+ Getting github pages to work with my custom site generator
git push.Getting Github actions to deploy from my own generator
www branch, which contains the built output served by GitHub pages (you can change the branch it watches in settings)www branch.The end result
👋
+ Posted 2021-01-17
+
+
+
+
+The Documentation Triangle →
+
+
+
+
+
+
diff --git a/posts/perfect-isnt-good-enough.html b/posts/perfect-isnt-good-enough.html
new file mode 100644
index 0000000..9e24331
--- /dev/null
+++ b/posts/perfect-isnt-good-enough.html
@@ -0,0 +1,42 @@
+
+
+
+
+ sourceless
+
+
+index
+
+ about
+
+ contact
+
+
+ Make your own (free) password manager
Why I rolled my own
Features and composition
Steps
1. Generate a gpg key
gpg --default-new-key-algo "ed25519/cert,sign+cv25519/encr" \
+ --quick-generate-key "my@email.com"
+2. Init the password store
pass init <your gpg key id from step 1>
+3. Add your first password
pass generate my-first-password 100
+my-first-password.-c flag to send the password directly to your clipboard instead.pass -c my-first-password
+pass edit:pass edit my-first password
+<generated password here>
+Url: login.website.com
+Username: my-username
+4. Set up sync
pass git init
+pass git remote add origin <repo git url>
+pass git push -u origin
+my-first-password.gpg, which can only be read if you have your gpg key to hand.post-commit hook to ~/.password-store/.git/hooks that pushs to your repo.5. Integrating with other devices
pass clients for your other devices. Most have pretty good docs on how to set them up, and most offer autofill and automatic syncing.6. Migrating from your old password manager
+ Posted 2022-08-16
+
+
+
+← Put Ticket IDs in your Commit Messages
+
+
+Programming in Functional Style: Functions →
+
+
+
+
+
+
diff --git a/posts/pifs-1-functions.html b/posts/pifs-1-functions.html
new file mode 100644
index 0000000..564cb25
--- /dev/null
+++ b/posts/pifs-1-functions.html
@@ -0,0 +1,42 @@
+
+
+
+
+ sourceless
+
+
+index
+
+ about
+
+ contact
+
+
+ Perfect isn't Good Enough
In pursuit of greatness
The worst thing I've ever built
The making of burdens
A successful mistake
Valueless perfection and glorious mediocrity
Doing the dumbest possible thing
+ Posted 2022-06-22
+
+
+
+← Relearning to Learn
+
+
+The Continuous Delivery Test →
+
+
+
+ sourceless
+
+
+index
+
+ about
+
+ contact
+
+
+ Programming in Functional Style: Functions
Functions
def or function().
This limited definition does fall apart somewhat quickly, unfortunately.
Or does it?
Functions in <favourite language> can take multiple arguments, but the definition given only allows a single input!
I won't go into detail in this post about the various ways we can deal with this, but a simple solution to this is lists.
Something that looks like a function with N arguments can be transformed into a function with one argument that is a list of length N
Functions in <favourite language> might behave differently depending on their context!
This means that for some input X, there might be a different output for f(X)!
Unless, that is...
When something looks like a function, but changes depending on its context, you can turn it into a function by including the context in the input.
An example of this is reading a file. Depending what's on disk, you'll get a different result! But if you consider what's on disk to be part of the input...
I lied to you with the earlier definition of a function. What I actually gave was the definition of a pure function.
Why do we care about a function being pure, though? Isn't it a lot of hassle to rewrite inputs and pass the entire world to the function every time? Well yes, okay, if you insist. I promise that sometimes there's a good reason to want it though.
The best reason is that **you can always tell what a pure function's output will be just from its outputs**.
Sound useful yet? No? Uh... well, look, what does that imply? What can you do with a pure function that your everday javascript function doesn't let you do?
Okay, don't let your eyes glaze over. I promise that I'll stop using five-dollar words.
Consider that in a pure function, you can always tell what the output will bu just by knowing the input.
So, it's easier to reason about! Especially when you've got a lot of these pure functions glued together. And to wind back to 'referential integrity', all it means is that you can tell what the output will be just by looking at the input. There's no outside context involved.
Some languages, like Haskell, enforce this. Most others don't care. But you'll find this idea of things being easy to reason about pop up everywhere.
You just have to look.
Think about dependency injection. What is it really? It's about making a core program that you can plug the context into, so that you can make it easier to reason about – usually with the goal of making it easier to test!
There are other reasons you might have to want a program to be easy to reason aboub, such as wanting to prove it works a certain way, or wanting to match some existing modeling method.
But the practical upshot for most is that it makes building and testing code WAY easier.
Sorry! I can't help myself! Does it help if I just say 'caching' instead?
Because that's all memoization really is. Caching at a micro level. And if you think about it, purity is really important to caching. If you want to load an image from a webserver quick, you want to cache it, right? But you can only do that if you're reasonably sure that the image is going to be the same one every time you ask for it.
So purity really matters when you want to cache something and avoid extra work.
So, what did we learn?
A pure function f tranforms some input X to an output f(X)
Pure functions:
I plan on doing a few more of these, so please reach out if this post helped you!
+
+ sourceless
+
+
+index
+
+ about
+
+ contact
+
+
+ Put Ticket IDs in your Commit Messages
In my previous post The Continuous Delivery Test I included a point:
Do you include ticket IDs in your commits or branches?
This sparked quite a lot of discussion on hacker news. I think it'd be useful to highlight some of my reasoning for including this point, and highlight some of the advice given by some of the commenters.
Why you should do it
Link work back to the ticket that spawned it
If you're in anywhere but the smallest companies, you probably have some sort of compliance requirements that require you to be able to link work done back to the planning that caused it to happen.
The reason that compliance frameworks want you to do this is mainly so there is an unbroken chain of culpability that can be used to identify the root cause of a potential issue.
But it's also just good documentation. Do you really want to play archeologist every time you need to figure out why a certain commit was made, or why a certain line of code is a bit weird?
As one lovely HN commenter puts it:
'I can't tell you how many times I've dug into the commit history to find where a change happened, then wanted to know the full context, and been glad I could just pull up the years-old ticket directly and see a detailed description of intent and requirements, discussions in the comments, mock-ups, etc etc'
Track the content of releases
In some environments you might need to prepare release notes or a changelog.This is, of course, much easier if you can derive the list of tickets from the delta of what's going in to production.
Measure how well your team is delivering
In Accelerate, Forsgren, Humble, and Kim pick out four metrics that seperate high performing teams from the rest - and they all have to do with deployment.
The first of those metrics is Lead Time, which measures the amount of time it takes from starting a ticket (or the first commit made, depending how you want to measure) to when it's eventually released.
Having some metadata available makes this much easier to do!
It's not that hard
Seriously, it's just habit. It might sound annoying or bureaucratic to you if you've never done it before, but after a couple weeks it'll probably be an unconscious practice that you'll never think about again.
And if typing <10 extra characters at the start of a commit message is really cramping your style, you can always write a prepare-commit-msg git hook.
Why you might not be doing it
Your company is small
If you're starting to grow and compliance is on the horizon, it's much easier to start now than when the deadline looms.
It's a personal project
Yep. Why would you? It's needless overhead.
Merging/branching strategy
I'm probably not going to convince you otherwise, but I think long-lived feature branches suck. They require complex merge patterns, bundle risk, and often require a bureaucratic ritual to merge in to a production branch.
Trunk-based development all the way, and if you can have a branch per ticket with the ticket ID in the branch name, even better.
I need to make an ad-hoc change
This usually falls in to one of two categories.
The first is "I want to make a change off the books". Usually this is a culture problem; you should feel empowered to create small tickets as you see fit to fix small issues. It might also be that your team doesn't create time for care and feeding work.
The second is "I don't know where this change belongs". Can you roll it in to another ticket you're already working on, e.g. one where you're already touching the code in question? Or maybe it's enough work that it should be its own item?
+
+ Posted 2022-07-29
+
+
+
+← The Continuous Delivery Test
+
+
+Make your own (free) password manager →
+
+
+
+
+
diff --git a/posts/relearning-to-learn.html b/posts/relearning-to-learn.html
new file mode 100644
index 0000000..7f1215a
--- /dev/null
+++ b/posts/relearning-to-learn.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+ sourceless
+
+
+index
+
+ about
+
+ contact
+
+
+ Relearning to Learn
Many years ago, I took a test. It was the first step of our "Learning to Learn" class. After answering a series of multiple choice question, the form issued forth a proclomation:
YOU ARE A VISUAL LEARNER.
Over the course of my years at school I would learn this piece of information to be on the whole useless and entirely innefectual.
To cut a long story short, school and university didn't go particularly well for me.
Learning by accident
It's somewhat embarassing to admit, but I don't think I really learned how to teach myself things effectively until at least the age of 26.
When I look back, I realise much of my learning was a result of accidental chance; for example, I learned to program mostly by virtue of complete immersion and stumbling upon 'the right things'. I just followed my nose (and to be fair it worked out).
But now I'm an adult with a job and a family, and my time is far more precious. If I want to learn something new, I can't count on stumbling on the right approach anymore. Simply creating the conditions in which learning is possible is not sufficient, since I lack the time that approach requires.
Enter Mentor
How I learned to learn is, surprisingly, not at all related to my area of study or profession (software), but a hobby.
I met my whistle teacher Becky in 2020 on a video call. It was near the start of the pandemic, and I had some extra time on my hands. Becky is somewhat unlike other teachers I've had, in that she could mechanically break down and explain music to me in a way that allowed me not just to play it, but understand what I was playing.
The whistle is a small, cheap, end-blown flute – somewhat like a budget recorder. It has six tone holes that you cover with three fingers of either hand, and a mouthpiece that you blow in to. Out of this deceptively simple instrument, you can get two full octaves of range in two major keys and all the keys that come along with that. That is to say, it's small, but surprisingly versatile.
Irish traditional music, along with other traditional music of the British isles, is generally learned and played by ear, though sheet music is available. What sets this folk music apart from most classical and contemporary music is that the sheet music is essentially a suggestion. Using your skill, wits, and guile, you have to take a couple of parts of a repetitive tune and make it interesting.
This is where Becky comes in, with two really solid pieces of advice: one about how to practice, and one about how to learn.
Mindful Practice
The first of the things Becky taught me was how to practice; how to gain the mechanical skill required to execute what I knew, and to transform my knowledge into understanding.
"Know what you're going to practice before you pick up the whistle"
In retrospect, startlingly obvious. Noodling around randomly does very little but ingrain what you've already learned; if you want to learn something new then you have to have a goal. But, as with many things in life, having a plan (even if you have no idea what you're getting in to) is half the battle.
"Five minutes every day is better than an hour once per week"
This subtly implies something else; you should make your practice not just habit and routine, but low-friction and easy to access. You will do much better chipping away at small, easy problems, than trying to tackle a hard problem you are not ready for (and all hard problems are just n easy problems in a trenchcoat anyway).
"Do it slow until you can do it right"
This one has a very "slow is smooth, smooth is fast" kind of vibe, and it's right. Doing something slowly and accurately is hard. In a programming context, it's very easy to whack together some code that delivers a feature, but the end result is of limited use if it's buggy, doesn't actually deliver the desired feature, or is riddled with security or structural issues.
Learning to Learn
The second thing Becky taught me was how to learn. Specifically, how to learn an instrument, or a tune – the approach is broadly the same.
It's a model, and as my colleague Craig likes to say; all models are wrong, but some are useful. Hopefully this is one of the latter.
"First, learn the notes"
You have to be able to play a tune through in its entirety. Obvious, right? But there's a lot here – some tunes are difficult. Some tunes don't have a written form, and you'll have to learn them by ear. Worse, some tunes have very poorly transcribed sheet music that will lead you astray! Not all learning material is created equal.
Beyond that, you will also have to contend with your own (lack of) mechanical skill. You will likely get stumped by a strange combination of notes. Perhaps you're not accustomed to switching octaves so quickly, or you accidentally bang out the wrong note because of some pattern you've learned in a previous tune.
"Second, make it more interesting"
Boy, isn't that 'interesting' a really load-bearing word? In this context, it means adding ornamentation, variation, dynamics, and a plethora of other tools in the folk musician's toolbox.
Making the piece flow better - the folk musician's version of 'clean code' - is key to turning a roughly shaped tune into refined birdsong.
"Third, play the tune"
Taking all you have learned, play the tune. Not the notes. Not the parts. The whole thing. Feel the rhythm, lean in to the swing, and add to it with your performance.
This is not just the culmination of learning how to play the tune - it's the start of learning the meta-skill of picking up and playing the tunes you come across.
In code terms; release it. You must make mistakes to improve, and more often than not you will be startled how others see something that is so mundane to you as so magical.
Some observations
This is not a new topic, or a new field. People much smarter and more experienced than me have studied this for a long time, but I share it in hopes that for someone out there, the same realisations I had will click.
If you want to read more on this topic, take a look at:
And if I've somehow convinced you to listen to whistle music:
- Flow, In The Year of Wu Wei - Brian Finnegan
- Slide from Grace/The Queen of Rangoon - Kan
- Farewall to Uist/The Lochaber Badger/RIP the Calico - Fred Morrison, Michael McGoldrick and Donal Lunny
Thanks readers, and thanks Becky!
Discussion of this article on Hacker News
+
+ Posted 2022-06-19
+
+
+
+← The Documentation Triangle
+
+
+Perfect isn't Good Enough →
+
+
+
+
+
diff --git a/posts/the-continuous-delivery-test.html b/posts/the-continuous-delivery-test.html
new file mode 100644
index 0000000..d293722
--- /dev/null
+++ b/posts/the-continuous-delivery-test.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+ sourceless
+
+
+index
+
+ about
+
+ contact
+
+
+ The Continuous Delivery Test
In 2000, Joel Spolsky published a piece on what he believed were the key factors that separated high-performing teams from the rest, in a post titled The Joel Test: 12 Steps to Better Code. It contains many insights that are still useful to this day; however, the world, software, and the teams that make it have moved on in the nearly 22 years since that post.
In 2015, John Kodumal of LaunchDarkly published an updated Joel Test for Continuous Delivery. While being far more up-to-date, it's still slipped a little in relevance.
If this post were to have an alternate title, it'd be 'Front-load Your Risks'. All of the advice below seeks to move risks away from the act of deployment and move them to earlier steps of the development process.
So, here's my best shot at a CI/CD Joel Test for the 2020s.
1. Do you use a distributed version control system?
Git, GitHub, and the ecosystems around them have eaten the software world, and for good reason. Beyond the usual advantages - branching, merging, and easy rollbacks, many of these hosted git servers now come with a whole slew of other productivity tooling.
Especially notable are those that move your CI closer to your source code, such as GitHub Actions, and GitLab CI/CD.
2. Do you practice trunk-based development?
Trunk-based development rolls up a bunch of good practices under one system.
It forces you to have one good main branch that is always deployable (and by implication, always able to roll back), and encourages merging small features one at a time.
Having an always-deployable main means that your deployment process becomes a lot more boring (which is great!), and is often indicative of a process that supports developers, rather than requiring long hours spent resolving merge conflicts and preparing release notes.
3. Do you merge little and often?
It's been known for some time now that large pull requests tend to get cursory reviews at best. On top of this, squirrelling away to work on a single feature for a long time means you are not spending adequate time making sure that you are building the right thing!
This also means using small, ticket-scoped feature branches, where the changes have a very tight scope.
4. Do two people read code before it is merged?
You'll notice I was very careful to avoid mentioning how this should take place. For some, pull requests are the right tool, but pairing and mob programming are also sufficient.
Nobody should be solely responsible for any code.
5. Do you require changes to pass checks before they can be merged?
One of the leading causes of outages is deployments. You should do your best to make sure that broken code never gets deployed in the first place.
6. Can you test your changes in a production-like environment before you deploy them?
There are many ways to go about this. You could spin up a small clone of your production env, run the tests, and then kill it. Where this is not possible, you can run tests against mirrored traffic or even run a canary.
7. Do you deploy to production as soon as main is updated?
Your deployments should be automated and happen right upon merge. This removes a burden from the team delivering a product and allows them to focus on features and reliability rather than long-winded release ceremonies. It can be hard to get here if you're currently releasing once a day, week, etc. – a mature approach to managing the risks of deployment is required, as well as battle-tested deployment processes and tools.
The earlier you start doing this, the easier it will be.
8. Does your deploy process self-heal?
When a deployment fails, can you roll back automatically? In addition to this, you should be able to roll back manually where necessary (though it's generally best to fail forwards!)
This also necessitates the use of blue-green deploys, canarying, healthchecks, and myriad other strategies to mitigate outage risk. It's very powerful to be able to turn a possible outage into a mere degradation.
9. Does your Infrastructure as Code live alongside the service it hosts?
It's a good idea to colocate any IaC with the software it's running rather than keeping the two in separate repositories. This dodges any dependency management between the two, and in many cases will save you a pipeline or two.
It's an antipattern (in the author's opinion) to have all infrastructure code completely separate from the application it supports. Common or shared infrastructure could be argued to have its own place, but a product should have all its pieces as close together as possible.
Monorepos are an extreme example of this philosophy.
10. Do you use feature flags?
Releases are a terrible mechanism for launching new features. You want to be able to run that shiny new feature in production as soon as possible, and you can't do that if you conflate a feature's launch with its release. This also means that you can preview and refine changes with flagship customers before rolling them out more widely.
11. Do you include ticket IDs in your commits or branches?
You need to be able to tie every commit back to the ticket that spawned it. It might not be you debugging a change that you merged, so it needs to be really easy to go back and find out why something was changed in order to make the right decision to fix it.
12. Can you still deploy from your own machine?
A key factor in developing software and infrastructure quickly is tight feedback loops. While it's probably not a great idea to be deploying directly to production from your own machine, having the ability to do so might be useful in a pinch.
You should be able to deploy to a dev environment (shared or ephemeral) such that you can easily validate your work in situ.
13. Can you show what will happen when a branch is merged?
Most IaC tools offer the ability to diff the changes they want to make. It's vitally important that when you go to merge some changes in, you know what the changes are, why you're making them (see point 11), and the effect the changes will have on the product or system.
This could be a cdk diff, or a visual diff if you're making a change to a frontend. Seldom is the code itself the interesting and crucial part of why you're making a change. The effect of your changeset should be evident not only to reviewers but also to bleary-eyed SREs at 4am.
Thanks to Josh Finch for his valuable feedback on this post.
+
+ Posted 2022-07-07
+
+
+
+← Perfect isn't Good Enough
+
+
+Put Ticket IDs in your Commit Messages →
+
+
+
+
+
diff --git a/posts/the-documentation-triangle.html b/posts/the-documentation-triangle.html
new file mode 100644
index 0000000..1c1370c
--- /dev/null
+++ b/posts/the-documentation-triangle.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+ sourceless
+
+
+index
+
+ about
+
+ contact
+
+
+ The Documentation Triangle (or, why code isn't self documenting)
At some point in your coding career, you've probably heard something like:
"My code is self documenting"
or
"Code IS documentation"
These statements can be hard to argue against, especially if they come from someone who is more experienced than you. A lot of the trouble is that they are not wrong – well, they're not completely wrong.
The goal of documentation
Why do we document things? If the code was enough, we would never need tutorials, API documentation, or anything of the sort, right? What user need is being fulfilled by having more than just code to communicate what our programs and services do?
It sounds obvious, but documentation exists so that code can be easily used.
This is incredibly easy to forget when the program is all in your head and you have an intuitive understanding – but to fresh eyes, is your code really all that easy to approach?
What, Why, How: The Documentation Triangle
What
+ /\
+ / \
+ / \
+ / \
+ / \
+ /__________\
+ Why How
+
Here's a rule of thumb I've seen repeated in a few places regarding how to ensure that your documentation is up to scratch.
Why a triangle? Well, triangles are a strong shape, and any missing side renders it entirely structurally unsound - just as missing out vital documentation can make your code as good as unusable to others.
What (code)
This is generally what people are going for when they say "my code is self documenting". Clear, concise, and well-written code is a form of documentation in itself.
It is the most honest account of what actually happens when you run the code, what the most important data structures are, and what interfaces you'll be able to interact with. Some languages are better than this for others, and supplementary information such as type signatures can be incredibly helpful in understanding some code that's new to you. However, there are some things this alone leaves out...
Why (comments)
Every program has a history - whether in the very mundane sense of having a history in version control, or a long and detailed history of changes it has undergone to meet various challenges.
It's this latter case that is the most crucial to note why certain pieces of code exist in their current state.
Comments are most often the vehicle for these sort of margin notes. Noting that this thing was done in this way for optimization reasons, or another thing was a dirty hack leaves signposts for the next maintainer to know what compromises have been made in building this software.
Maintaining (legacy) code without any comments is less like being a mechanic, and much more like being an archaeologist.
How (context)
The last side of the triangle is often the most neglected. It might live in docstrings, or in confluence, be automatically generated, or be in a carefully crafted README, but it's all information on the context in which the code will be executed.
No program exists in a vacuum. There is always some environment, some organisation, some process that it lives in or serves, and taken out of said context, it is entirely useless.
Failing to recognise this is the number one leading cause of new users and junior devs being unable to do 'this simple task' (don't quote me on this, I have no data).
There's a great litmus test for this. Take the new process or pipeline or whatever that you've implemented (be it setting up a repo, running a job, etc.) and find yourself someone that's technically competent but unfamiliar with what you're working on. Plonk them in front of it and shut the fuck up. Watch them struggle through it and only help them if they are genuinely, truly stumped. This will very quickly tell you where you need to spend effort on documentation.
Heck - make it part of your code review.
Discussion of this article on Hacker News
+
+ Posted 2021-03-19
+
+
+
+← Getting github pages to work with my custom site generator
+
+
+Relearning to Learn →
+
+
+
+
+
diff --git a/posts/the-phoenix-reborn.html b/posts/the-phoenix-reborn.html
new file mode 100644
index 0000000..7402dd0
--- /dev/null
+++ b/posts/the-phoenix-reborn.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+ sourceless
+
+
+index
+
+ about
+
+ contact
+
+
+ The Phoenix Reborn
If you haven't read "The Phoenix Project" yet, this is your sign from the universe telling you that it's time. Go on, this post will still be here when you get back.
Still here? Cool.
A very quick Phoenix Project Recap
An extremely condensed take on the central point of "The Phoenix Project" is:
Shorten feedback loops as much as possible
In the book and day-to-day DevOps, that's achieved in several ways:
- Colocating ops resources on dev teams, instead of siloing them in separate orgs
- Making use of CI to provide fast feedback in a dev-like environment
- Making use of tests in the development flow
- Having prod-like environments available for testing
Each of these seeks to reduce the amount of time a problem sits waiting before it can be addressed by someone with the knowledge to fix it.
Code is free but specification is still expensive
The step in quality of output from top-tier LLMs in late 2025 changed the game. Code is now easy to produce in good quality and high quantity. The new gap is one that has always existed: understand whole problems and write a plan detailed enough to make a solution.
Spec-driven development is a particularly promising output of this; structured input that captures every possible problem, solution, and decision in a small problem space.
But specs alone aren't enough; fortunately, however, Claude and co are very, VERY good at writing tests, pipelines, and just about any other verification tooling you could ever want, including lightweight (or heavyweight!) formal methods.
Give the AI the tools it needs to succeed
This is the only takeaway that you need from this post. *You must shorten the feedback loops for the AI as much as possible*. You need to make it possible for it to test its own work if you want high quality outputs.
At a minimum:
- Linter
- Autoformatter
- Tests
- CI/CD pipelines
This is how you provide fast feedback and get good results; it's exactly the same principle that devops was built on.
Make your AI aware of them, make it use them, and make it constantly review its own work (or get another model to do it).
+
+ Posted 2026-03-30
+
+
+
+← Programming in Functional Style: Functions
+
+
+
+
+
+
diff --git a/tools/generate_og_images.py b/tools/generate_og_images.py
deleted file mode 100644
index dd376ac..0000000
--- a/tools/generate_og_images.py
+++ /dev/null
@@ -1,210 +0,0 @@
-#!/usr/bin/env python3
-"""Generate per-post fractal artwork in the site palette.
-
-Each post gets a deterministic Julia set derived from a SHA-256 of its body text
-(front matter excluded, so retagging a post doesn't churn its art). The hash
-picks an angle on the boundary of the Mandelbrot set's main cardioid. Sampling
-the boundary rather than an arbitrary disc matters: a c drawn from open space
-outside the set gives a Cantor dust Julia set, which renders as an almost empty
-frame. Every point on the cardioid borders the set, so every post gets structure.
-
-Four files per post, because the site has both a dark and a light theme and the
-post index needs something far smaller than a social card:
-
-