blog
Writing about software, systems I build, and the work behind them.
What building it corrected
The specification survived contact with the compiler better than I expected and worse than I claimed. Here are the corrections, because the corrections are the part worth reading. Watching a directory does not close the race. The plan was to add an inotify watch when a new directory appears. A watch can only be added when the event is drained, up to fifty milliseconds later, and anything written in that window lands in a directory nobody is watching. Creating a folder and writing index.md in...
The horrors persist, but so do we
Landing.Jobs and Damia published the Tech Talent Trends Report & Salary Benchmark 2026 in May, combining a survey of 1,115 tech professionals with salary expectations from 6,500-plus candidates gathered inside live recruitment processes. It is the most complete public picture of the Portuguese tech market anyone has put out, and reading it end to end put a number on something I had only been feeling. Average tech gross annual salary is 53.671€, up 0.9% year on year after 2025 delivered 14%....
Locking the decisions
A design is not finished when there is nothing left to add. It is finished when the open questions have been closed, including the ones you would rather leave open. What got locked. Runtime. OpenResty with no framework. Lapis is the obvious choice and I am not using it, because what it offers is a Postgres ORM, migrations and etlua templating, and this design uses none of the three. A router over path segments is a couple of hundred lines. Storage. Markdown with YAML front matter, a folder per...
Five borrowed ideas, and where they collide
Nothing in the design is mine. The interesting work is in the seams, because the ideas contradict each other and the contradictions are not obvious until you write them down together. The borrowings, with attribution, since none of this is original: Flat files as the source of truth, from Kirby and Statamic. A folder per document holding its Markdown and its images, for the reasons I wrote about in May last year. The body as structured blocks, from Sanity and Ghost. An ordered array of typed...
"AI" - A Retrospective, Part III: Where the Leverage Is Now
The May 2026 landscape Three things happened in the last month that materially change how a working developer should think about model choice. DeepSeek released DeepSeek V4 on April 24 at €0.13 input and €3.20 output per million tokens for the Pro variant, with a launch promo running 75% below list through the end of May. It posts 80.6% on SWE-Bench Verified, supports a 1M-token context, and ships under an open-weights licence. At those numbers the model sits roughly one-sixth the price of...
State of WordPress 2026
As I am missing this year's WordCamp Porto, I wanted to recap the current state of WordPress and what I think actually matters for people building things with it right now. I first touched WordPress in 2007 and have worked with it professionally for about a decade, which is long enough to watch the platform cycle through irrelevance, reinvention, and irrelevance again, sometimes in the same calendar year. WordPress survives every cycle because it occupies a position nothing else has dislodged:...
I Want to Get Off Mr. LinkedIn's Wild Ride
LinkedIn, in 2026, is the de facto standard for employment. Opting out costs more than staying, especially if your career crosses a border or your next job needs to land within a few months. So most of us keep an account, post the appropriate amount, and never quite say what we mean. The effect of writing on the platform is that of a glass office. You can be seen at any time by people who might one day hire you, so you write as if you can be seen at any time by people who might one day hire...
"AI" - A Retrospective, Part II: The Confidence Game
The artefact does not know what is true Two findings worth stating up front, because the rest of this part assumes them. First, the systems we are discussing do not reason in the way the marketing implies. Bender and colleagues' 2021 "stochastic parrots" paper named the core observation that large language models manipulate statistical regularities in their training data rather than meaning, and generate fluent output without any internal model of what is true. Apple researchers sharpened that...
"AI" - A Retrospective, Part I: Genealogy
Before ChatGPT became the story I was already poking at GPT-3 through the OpenAI API before ChatGPT arrived. That period now gets flattened into prehistory, but it is worth remembering properly. GPT-3 was not useless. It could summarise, continue text, draft boilerplate, and make enough strange mistakes to remind you that the thing was still fundamentally a completion engine. The barrier was not only capability. It was ergonomics. You needed an API key, a playground, a sense for prompts, and...
Picking a runtime: OpenResty and LuaJIT
The runtime decision came before anything else, because it decides what is cheap. OpenResty is nginx with LuaJIT compiled in and a set of libraries for writing request handlers in Lua. It has been around since about 2011 and is widely deployed as a gateway sitting in front of applications. Building the application in it is unusual. The property I wanted is that there is no per request startup. Code loads once when the master process initialises, the worker inherits it after the fork, and a...
Model Context Protocol
The Model Context Protocol has been a hot topic for a moment now, so I figured I'd write a bit about it and the experiments I've been running with different MCP servers. I've used them as part of my actual job for months. They are genuinely useful. They are also worth understanding properly before pointing them at your work accounts. What the protocol actually does MCP stands for Model Context Protocol. Anthropic released it as an open-source standard in late 2024. Google, Microsoft, OpenAI,...
Why I am building another one
There is no shortage of content management systems, so the burden is on me to say what is missing. Two years of writing these has left me with a short list of things I believe, and no system that holds all of them at once. Content should be files. Readable, diffable, greppable, in version control, still openable in fifteen years. The flat file people are right about this and have been for over a decade. Files should not mean giving up querying. Filtering, sorting and paginating a few thousand...
State of the Browsers in 2026
I wanted to stay a Firefox person. That sentence is the entire argument, written as a confession. Chrome is the web now, which is annoying to put in writing because it sounds like surrender and also happens to be true. It holds roughly 70% of global usage. Every other mainstream browser except Firefox and Safari runs on Chromium. Google controls Blink. Google controls V8. Google sets the pace for what the web is allowed to do and how quickly it is allowed to do it. The remaining counterweight...
Replacing Spotify
I will assume that most people have a story of how they fell in love with music. This one is mine. My dad had a shelf of CDs in the living room. Motown compilations, Deep Purple's Burn, Queen's A Night at the Opera. I would pull out a disc, flip it over, read the liner notes, and put it on. That was the interface. When I got old enough to have my own taste and no money to fund it, I did what every teenager with a broadband connection did in the early 2000s. Kazaa first, then Morpheus, then DC++...
What actually makes a CMS slow
The received answer is the database, and it is usually wrong. A well indexed query for one post takes under a millisecond. If that were the bottleneck, the page would be fast. Sit with a profiler on a typical install and the time is somewhere else entirely. Bootstrap. Before a single line of your page runs, the application has loaded its core, enumerated the plugin directory, loaded each plugin, and registered several thousand hooks. That work happens on every request and produces nothing a...
Hooks, plugins, and the price of extensibility
WordPress has about sixty thousand plugins and that is the reason it has sixty percent of the web, not the editor and not the themes. The mechanism is simple enough to describe in a paragraph. Core calls doaction at named points and applyfilters when it has a value that might be modified. Anyone can register a callback against a name with a priority. Actions run for effect, filters transform a value and pass it on. That is the whole system. What makes it work is that the names are stable. A...
200,000 Hours of Your Life
Two hundred thousand hours of your life. That is roughly how long you are likely to spend sitting by a computer, looking at a screen, if you are the sort of person who started young and then accidentally built a life around it. The arithmetic is not sacred. Start around thirteen, average six to nine hours a day, keep going into old age, and the range gets stupid quickly. A conservative estimate lands somewhere around 150,000 hours. A less conservative one clears 250,000. Two hundred thousand is...
The API first generation
Between roughly 2013 and 2022 a set of systems appeared that treated the API as the product. They are lumped together as headless and they disagree with each other more than the label suggests. Contentful is hosted, and the model is edited through their web UI. Everything is someone else's problem, including whether you can still get your content out on a Tuesday when their pricing changes. It made the category commercially real and demonstrated that companies will happily pay per API call for...
Flat file CMSes and what they got right
There is a small category of CMS that skipped the database entirely, and it has been quietly correct about several things for over a decade. Kirby has been going since around 2012. Statamic started about the same time and later rebuilt itself on Laravel. Grav came a couple of years after. All three store content as files in a directory tree, all three have paying customers, and none of them are toys. The layout is the part I keep coming back to. In Kirby a page is a folder. The folder holds a...
Cognitive Load Theory
Unproductive days are rarely about laziness or discipline. You slept fine, you ate, nothing went visibly wrong, but the brain will not cooperate. I used to treat those days as a character flaw and try to bully my way through them. That mostly produced worse work and a worse mood. "Push through it" misidentifies the problem. The problem is capacity, and capacity is a real, finite, measurable constraint. Your brain is a battery. That is most of the thesis, and the rest is housekeeping. Cognitive...
The static site generator detour
Jekyll landed in 2008 and the pitch was that your blog is a git repository. Write Markdown, commit, a program turns it into HTML, the HTML goes on a server that does nothing but serve files. No database, no application, nothing to patch on a Tuesday because of a vulnerability disclosure. GitHub Pages made it free and the idea spread through developers who were tired of maintaining WordPress installs for their personal sites. Hugo arrived in 2013 with the same model and a build fast enough to be...
Headless, decoupled, and what the words are doing
Headless means the CMS does not render pages. That is the whole definition and it is worth saying plainly, because the word has been used to sell a lot of things that have nothing to do with it. The term caught on around 2015 and 2016, largely through Contentful, and it named something that already existed. People had been putting APIs in front of CMSes for years. Giving it a name turned an architecture into a category, and a category can be sold. What headless specifies is narrow. There is no...
Templates, themes, and the coupling nobody questioned
The thing that made WordPress win was not the editor. It was that you could download a theme and have a site that looked like something. A theme is a bundle of templates plus assets, living inside the CMS, executed by the CMS, with access to everything the CMS knows. single.php renders a post. archive.php renders a list. The loop pulls the current query into scope and you write markup around it. It is enormously effective. It is also the tightest coupling in the entire system and almost nobody...
Who is allowed to publish
A CMS for one person needs no permission system at all. Everything after that is politics encoded as software. WordPress ships five roles and they are a decent map of a small publication. Subscriber reads. Contributor writes but cannot publish. Author publishes their own. Editor publishes anyone's. Administrator changes the site. The important one is contributor, because it encodes the idea that writing and publishing are separate acts performed by different people. That single distinction is...
The body problem
Everything except the body is easy. A title is a string. A date is a date. Tags are a list. Then there is the body, which is prose with structure inside it, and every CMS makes a different bet about how to store that. As a blob of HTML. WordPress does this. The post_content column holds markup and the editor writes into it. It is honest about the medium and it is the fastest path to shipping. The problem is that you now have a database column containing arbitrary HTML written partly by a person...
The content model, and why schemas turned up
The first version of any CMS has one content type and it is called post. It has a title, a body and a date, because that is what a blog is. Then somebody needs a page that is not a post, so there are two types. Then somebody needs an author with a photo and a bio. Then events, with a start time and a venue, and the venue turns out to be its own thing because three events happen at the same place. Nobody sits down and designs this. It arrives one requirement at a time and the question is only...
Where content lives, and the database assumption
Open the top twenty CMSes by installs and nineteen of them put your writing in MySQL or Postgres. Nobody chose this recently. It was chosen once, around 2003, when the alternative was writing files with PHP and the shared hosting of the day made a database the reliable option. Everything since has inherited it. It is worth being fair about what the database buys, because it is not nothing. Querying. Show me posts in this category, published, newest first, twenty at a time. Doing that over a...
The smallest thing that is still a CMS
Take WordPress and start deleting. Remove the themes. It still manages content, you just have to render it yourself. Remove the plugin system. Poorer, still a CMS. Remove comments, media handling, multisite, the REST API, the block editor. Keep going until something breaks in a way that means the word no longer applies. I make it four things. Somewhere content lives. Files, rows, objects in a bucket. There has to be a durable place, and something has to be authoritative when two copies...
Where the term CMS actually came from
The phrase shows up in trade press around 1995 and 1996, and it is not describing anything you would recognise. Vignette StoryServer is the usual reference point. It came out of CNET, which had a problem most companies did not have yet: a newsroom producing a large volume of pieces every day that had to appear on a website, with the writers not being the people who knew HTML. StoryServer separated the writing from the page. Editors filled in a form, templates turned the result into files, and...
PHP in 2024
PHP has a reputation problem that no longer matches the language. The jokes are mostly from the PHP 5 era. The language moved on a long time ago. A lot of the people still making those jokes never bothered to check. I have been writing PHP for years, through the rough parts and into the current era. I still understand why people flinch at the name. I also think modern PHP, and Laravel in particular, is one of the most productive web stacks available right now. The interesting part is how...
Content management before anyone said content management
Long before a CMS meant a website, it meant a filing cabinet you could not lift. FileNet shipped document imaging systems in the mid eighties. The problem was banks and insurers drowning in paper: a claim file that existed in one physical folder, in one building, that one person could hold at a time. Scanning it solved storage. It did not solve the interesting part, which was that a document has a life. Someone drafts it, someone approves it, it supersedes an earlier version, it becomes legally...
The Case for a Commonplace Notebook
In the previous post I made the case for a Markdown-based journal as the spine of a personal system. That setup still holds. It has one weak point though, and no plugin fixes it: capture. The seconds between "I just had a thought" and "I have opened the right note in the right vault" are where most thoughts get lost. A physical notebook closes that gap in a way digital tools cannot. I keep one within arm's reach at all times. The format has a long, slightly pretentious history under the name...
Structured Notes for Developers
Markdown is deceptively ingenious. It looks like almost nothing: asterisks, hashes, backticks, dashes, brackets. A few marks around otherwise plain text. That is the trick. It keeps the writing readable before anything renders it. John Gruber introduced Markdown in 2004 as a way for web writers to write in plain text and turn that text into HTML. The part I always liked is that the source text still reads like the thing it represents. A heading looks like a heading. A list looks like a list. A...