Reading List

The most recent articles from a list of feeds I subscribe to.

2022 redesign

This blog’s design has remained roughly the same the past two years. I tweaked the style a lot, but changes were incremental and stay true to the neutral black and white style. Codebases rot over time, and small changes slowly but surely introduce technical debt. I started cleaning house, and before I knew it I was embarked in a full redesign.

Desk research & design inspiration

Before I design a website, I like to set up a primary goal to fall back to. For my blog, I went with “minimal enough to not get bored of it, but with enough personality”.

I’ve had designs with exotic typography or a bunch of flair. But after a few weeks, I tend to get bored of them and fall back to a minimal design. This time I wanted more than a neutral style. I wanted some elements that add personality to the site without overdoing it, without getting tired of them after a few weeks.

First I went through my bookmarks to dig up some inspiration. Here are some that stood out the most.

  • Elise Hein — Love the typography here. I really like how some elements are pulled out of the single column layout. This turned out to be a big source of inspiration for this site’s grid.
  • Rasmus — Great content structure, great typography, lovely colors. Completely different style than what I ended up with, but I love this design.
  • Robin Rendle — Interesting home page, and I like the split between “notes” and “essays”. I didn’t end up splitting mine up, but I might in the future.
  • Paco Coursey — Perfect example of a minimal design. Clean, good typography, and a few elements to keep it interesting. However, a bit too minimal for my redesign goal.
  • Daring Fireball — I always liked the simplicity of Daring Fireball’s content structure. Just one big stream of content, some links, some posts.
  • Dries Depoorter — Dries Depoorter is a digital artist. I recently saw him speak at Full Stack Europe, and he reignited a spark to be more creative in digital projects.

Now, on to design and implementation.

Still Hugo

This site is built on Hugo, and I have no reason to change that. I adore Hugo. I’m still at awe of how fast it is, the entire site builds from source in 300ms. That includes parsing 100 markdown files to generate over 350 pages and concatenating and minifying the CSS. Hugo doesn’t offer a lot of configuration and has no plugin system, which keeps me in check from overengineering things.

New typography

Since text is the most important aspect of a blog, I started shopping for fonts. My final choices were Karla and Berkeley Mono.

  • Karla — I’ve used Karla here in the past, it strikes a great balance between readability and character.
  • Berkeley Mono — A new discovery for me. What a lovely mono font! Works for both code snippets and a display font for titles.

The chaotic neutral grid

On first sight, it’s a typical sidebar + content layout. But there are few items that break out and make things interesting. Code blocks expand from the start of the sidebar to the end of the page. Blockquotes remain in line with the content, but run until the end of the page. Images take in as much space as possible.

This is all possible thanks to some fun with CSS grid. Named tracks make it easy to assign elements to columns.

.grid {
display: grid;
grid-template-columns:
[left-edge] minmax(calc(var(--spacing) * 2), 1fr)
[sidebar-start] auto
[sidebar-end content-start] minmax(20rem, 32rem)
[content-end] minmax(calc(var(--spacing) * 2), 1fr)
[right-edge];
column-gap: calc(var(--spacing) * 3);
}
.grid > .sidebar {
grid-column: sidebar-start / sidebar-end;
grid-row: 1 / 999;
}

Posts and page content are wrapped in an <article> tag, so adding display: contents allows me to specify which grid tracks the underlying content should snap to.

.grid > article {
display: contents;
}
.grid > *,
.grid > article > * {
grid-column: content-start / content-end;
}
.grid > article > pre {
grid-column: sidebar-start / right-edge;
}
.grid > article > blockquote {
grid-column: content-start / right-edge;
}
.grid > article > p:has(img) {
grid-column: left-edge / right-edge;
}
.grid > .footer {
grid-column: left-edge / right-edge;
}

I like the way the grid creates organized chaos with odd islands of whitespace like here.

The divider between the sidebar and the content also adds a randomness factor to the page. It stops running when it comes across a piece of breakout content. Sometimes it stops after the sidebar, sometimes it nearly reaches the end of the page.

This is achieved with a large end value for grid-row. The day I have a post with more than 999 blocks, the line will stop. I could increase the number, but why fight its quirkiness?

.grid > .sidebar {
grid-column: sidebar-start / sidebar-end;
grid-row: 1 / 999;
}

145 colors to my disposal

Limiting options is the best way to induce creativity. I challenged my self to only use named CSS colors. Browsers support 145 named colors, that’s a complementary bag of 145 variables. My final color pallet consists of blue, magenta, cyan, gray, gainsboro, black, and white.

I also dug up the old a:visited selector which makes the site more colorful for some.

There’s no dark mode support for now, but I’ll most likely set that up later.

Better syntax highlighting

I ditched Hugo’s native highlighting engine in favor of Torchlight. I’ll write a post on setting up Torchlight with a static site generator in the coming days.

The biggest tradeoff is build speed. Hugo is inhumanly fast (300ms, remember?). Adding a tool that relies on network request is going to slow things down a lot. A full build takes about 16 seconds now, but the improved syntax highlighting is worth it. I only run Torchlight in production, so it doesn’t slow down the writing and dev experience.

Besides highlighting, Torchlight has fun features like code collapsing. And I don’t need to start every PHP snippet with a <?php tag anymore!

class User
{ // [tl! collapse:start]
public function __construct(
public int $id,
public string $name,
public string $email,
) {
} // [tl! collapse:end]
}

Knowing myself, I’ll be tweaking the design every know and then the coming months. I still want to restore dark mode, and maybe reintroduce webmentions (I used to have them, but got rid of them in my crusade for minimalism in the previous design). I hope the new design will be a good baseline for the next few months or years, only time will tell. But for now, time to get writing again.

Named arguments

I shiver at the sight of a function packed with too-many-to-read-at-a-glance arguments without a description. Especially boolean flags. Boolean flags lack context. Look at this code, I have no idea what false, false, true conveys.

$page->render(false, false, true);

A pattern I often see in older code is an associative array as the single parameter. At least the intent is clear, but arrays lack IDE completion and type safety.

$page->render([
'footer' => false,
'header' => false,
'include_assets' => true,
]);

After another round of refactoring, we might end up with a bunch of fluent methods on the object.

$page
->showFooter(false)
->showHeader(false)
->includeAssets()
->render();

The fluent builder removes our gripes and provides IDE completion and type safety, but I’m not content yet. Fluent builders explode the size of an object’s public API surface.

class Page
{
private bool $showFooter = true;
private bool $showHeader = true;
private bool $includeAssets = false;
public function showFooter(bool $showFooter): self
{
$this->showFooter = $showFooter;
return $this;
}
public function showHeader(bool $showHeader): self
{
$this->showHeader = $showHeader;
return $this;
}
public function includeAssets(bool $includeAssets): self
{
$this->includeAssets = $includeAssets;
return $this;
}
//
}

That’s all boilerplate, and doesn’t even include alternative methods like withoutFooter(). It also introduces a bunch of internal (mutable) state.

$firstRender = $page
->showFooter(false)
->showHeader(false)
->includeAssets()
->render();
$secondRender = $page
->showFooter(false)
->render();

The second render wil also exclude the header and include the assets because we mutated the state earlier. We could rewrite our object to be immutable, that would lead to a more consistent implementation but introduce even more boilerplate code.

The year is now 2020 2022, and another option has presented itself: named arguments.

$page->render(
footer: false,
header: false,
includeAssets: true,
);

To me, named arguments provide the best of all worlds. Proper IDE support, type safety, no internal state required, keeps the object’s public methods to a minimum.

I didn’t expect to appreciate them so much, but I think they’re my favorite addition to PHP in a while.

Named arguments

I shiver at the sight of a function packed with too-many-to-read-at-a-glance arguments without a description. Especially boolean flags. Boolean flags lack context. Look at this code, I have no idea what false, false, true conveys.

$page->render(false, false, true);

A pattern I often see in older code is an associative array as the single parameter. At least the intent is clear, but arrays lack IDE completion and type safety.

$page->render([
'footer' => false,
'header' => false,
'include_assets' => true,
]);

After another round of refactoring, we might end up with a bunch of fluent methods on the object.

$page
->showFooter(false)
->showHeader(false)
->includeAssets()
->render();

The fluent builder removes our gripes and provides IDE completion and type safety, but I’m not content yet. Fluent builders explode the size of an object’s public API surface.

class Page
{
private bool $showFooter = true;
private bool $showHeader = true;
private bool $includeAssets = false;
public function showFooter(bool $showFooter): self
{
$this->showFooter = $showFooter;
return $this;
}
public function showHeader(bool $showHeader): self
{
$this->showHeader = $showHeader;
return $this;
}
public function includeAssets(bool $includeAssets): self
{
$this->includeAssets = $includeAssets;
return $this;
}
//
}

That’s all boilerplate, and doesn’t even include alternative methods like withoutFooter(). It also introduces a bunch of internal (mutable) state.

$firstRender = $page
->showFooter(false)
->showHeader(false)
->includeAssets()
->render();
$secondRender = $page
->showFooter(false)
->render();

The second render wil also exclude the header and include the assets because we mutated the state earlier. We could rewrite our object to be immutable, that would lead to a more consistent implementation but introduce even more boilerplate code.

The year is now 2020 2022, and another option has presented itself: named arguments.

$page->render(
footer: false,
header: false,
includeAssets: true,
);

To me, named arguments provide the best of all worlds. Proper IDE support, type safety, no internal state required, keeps the object’s public methods to a minimum.

I didn’t expect to appreciate them so much, but I think they’re my favorite addition to PHP in a while.

Named arguments

I shiver at the sight of a function packed with too-many-to-read-at-a-glance arguments without a description. Especially boolean flags. Boolean flags lack context. Look at this code, I have no idea what false, false, true conveys.

$page->render(false, false, true);

A pattern I often see in older code is an associative array as the single parameter. At least the intent is clear, but arrays lack IDE completion and type safety.

$page->render([
'footer' => false,
'header' => false,
'include_assets' => true,
]);

After another round of refactoring, we might end up with a bunch of fluent methods on the object.

$page
->showFooter(false)
->showHeader(false)
->includeAssets()
->render();

The fluent builder removes our gripes and provides IDE completion and type safety, but I’m not content yet. Fluent builders explode the size of an object’s public API surface.

class Page
{
private bool $showFooter = true;
private bool $showHeader = true;
private bool $includeAssets = false;
public function showFooter(bool $showFooter): self
{
$this->showFooter = $showFooter;
return $this;
}
public function showHeader(bool $showHeader): self
{
$this->showHeader = $showHeader;
return $this;
}
public function includeAssets(bool $includeAssets): self
{
$this->includeAssets = $includeAssets;
return $this;
}
//
}

That’s all boilerplate, and doesn’t even include alternative methods like withoutFooter(). It also introduces a bunch of internal (mutable) state.

$firstRender = $page
->showFooter(false)
->showHeader(false)
->includeAssets()
->render();
$secondRender = $page
->showFooter(false)
->render();

The second render wil also exclude the header and include the assets because we mutated the state earlier. We could rewrite our object to be immutable, that would lead to a more consistent implementation but introduce even more boilerplate code.

The year is now 2020 2022, and another option has presented itself: named arguments.

$page->render(
footer: false,
header: false,
includeAssets: true,
);

To me, named arguments provide the best of all worlds. Proper IDE support, type safety, no internal state required, keeps the object’s public methods to a minimum.

I didn’t expect to appreciate them so much, but I think they’re my favorite addition to PHP in a while.

Granular interfaces

A few weeks ago a spec change for an application we’re working on forced us to refactor part of the codebase. It was food for thought about the flexibility granular interfaces provide, and choosing the right abstraction at the right time. This is a short writeup on the thought process we went through as we updated our logic to support a new feature now and allow more options in the future.


Imagine we were hired to build a course platform. We’re working on the code that grants access to a course after a user purchased it.

First, we’ll create a course object with a Purchasable interface to indicate it can be purchased, and a Registrable interface to indicate a registration should be created after the customer has paid.

class Course implements Purchasable, Registrable
{
}

Somewhere down the line in our checkout code, we can loop over the order items and create registrations for each registrable product we come across.

foreach ($order->items as $orderItem) {
if ($orderItem->purchasable instanceof Registrable) {
createRegistration($orderItem->purchasable);
}
}

Later on, the client asks how they can sell a bundle of courses. Purchasing a bundle would register a customer for a bunch of courses at once.

Since a bundle can be purchased, it will implement the Purchasable interface. However, implementing the Registrable interface wouldn’t make sense. After the bundle is purchased, registrations need to be created for the underlying courses, not the bundle itself.

class Bundle implements Purchasable
{
public function __construct(
public array $courses
) {
}
}

This shows that the Registrable interface has not one but two responsibilities. It indicates that something can be tied to a registration, and it tells the system to create registrations after payment.

A course fulfills both responsibilities, but a bundle only needs to provision. Let’s introduce a new CreatesRegistrations interface only to create registrations.

class Bundle implements Product, CreatesRegistrations
{
public function __construct(
public array $courses
) {
}
public function createsRegistrationsFor(): array
{
return $this->courses;
}
}

In our checkout code, we add another check for the new interface.

foreach ($order->items as $orderItem) {
if ($orderItem->purchasable instanceof Registrable) {
createRegistration($cartItem->purchasable);
}
if ($orderItem->purchasable instanceof CreatesRegistrations) {
foreach ($orderItem->purchasable->createsRegistrationsFor() as $registrable) {
createRegistration($registrable);
}
}
}

That solves our problem, but our checkout code is getting bloated. Even worse, there are now two ways to tell our system a product will create registrations. If we want to know wether a product will create a registration, we have to check for both the Registrable and CreatesRegistrations interfaces.

We can consolidate the behavior to always use our CreatesRegistrations interface. The Course object can return a reference to itself.

class Course implements Product, Registrable, CreatesRegistrations
{
public function providesRegistrationsFor(): array
{
return [$this];
}
}

And we can revert our checkout code is back to one createRegistration call.

foreach ($order->items as $orderItem) {
if ($orderItem->product instanceof ProvidesRegistrations) {
foreach ($orderItem->product->providesRegistrationsFor() as $registrable) {
createRegistration($registrable);
}
}
}

After refactoring to a granular interface, our system became more flexible and composable. Small interfaces communicate intent more clearly, making it easier to understand the flow of a system.

That doesn’t mean we should be paralyzed to find the perfect abstraction before we start. We only realized our interface wasn’t granular enough after it grew out of its original use case. As a system evolves, abstractions should arise from current needs, not future possibilities.


As mentioned on Twitter, this guideline is formalized in the “I” in SOLID: the interface segregation principle.