Reading List
The most recent articles from a list of feeds I subscribe to.
Optimize for optionality and build towards checkpoints
Optimize for optionality and build towards checkpoints
In a project management-themed Hackers Incorporated episode, Adam Wathan introduced derisking projects with save points. The entire episode is definitely worth your time, but that specific piece of advice has changed the way I work as a developer and make decisions as a project manager.
In practice, it has taught me to optimize for optionality, not efficiency.
The monolithic branch
To illustrate what it means, we'll build a user account section as an example. The new account section will be shipped to users as one big launch. A user can…
- Update their basic profile information
- Add their address with Google Maps autocomplete
- Upload a profile picture
- Connect social login accounts
- Reset their password
Let's make an estimate:
Task | Estimate |
---|---|
Basic information | 6h |
Address autocomplete | 12h |
Profile picture | 4h |
Social login | 8h |
Reset password | 4h |
Summing up: 6 + 12 + 4 + 4 + 8 = 34
, that's a 40-hour estimate. (Gotta add that margin!) We'll checkout a new feature/user-account
branch, implement, and ship. The final logs in our time tracker:
Task | Estimate | Actual |
---|---|---|
Basic information | 6h | 3h |
Address autocomplete | 12h | 14h |
Profile picture | 4h | 6h |
Social login | 8h | 18h |
Reset password | 4h | 2h |
3 + 14 + 6 + 18 + 2 = 43
. Not too far off of our (padded) estimate! Some tasks were easier to implement than expected, some took a lot longer. But we didn't ship 43 working hours later, it took 4 weeks to reach production. Why?
- We realized our application needed to be approved by our social login provider, that took a week. After that it took a lot longer than expected to implement.
- When we started to implement the new reset password flow we found a few issues in the design, so the design team had to update the Figma file. The implementation went smoother than expected, but we had to wait for the changes.
- Of course, the usual bugs and small feature requests that creep into our schedule pushed this project further down the road.
That users had to wait longer than estimated for the new account feature isn't a problem. This post isn't a manifesto to ship incremental changes over big bang launches—I'll leave that decision to the product managers. Shoving everything into one branch is plain easy. Because we weren't going to ship anything individually, we were allowed to! What I care about, is how it affects everything else. Because meanwhile…
- Another developer needed an address autocompletion on the shipping page. Now we have two implementations and need to trash one.
- Someone else refactored the user model and related code, causing a bunch of merge conflicts throughout the 4 weeks of development.
- Another timeline-like feature design relies on profile pictures, but we wanted to wait until this branch was merged so we had access to the profile picture components that are already set up.
Checkpoints
Let's treat each task as an individual project. It will probably take longer—at least on our timesheet. We need to create a separate branch for every task and can't implement changes in bulk. Instead of one big feature/user-account
merge, we created, reviewed, and merged a bunch of smaller branches over the weeks.
feature/user-basic-information-viewsrefactor/share-google-maps-api-credentialsfeature/google-maps-autocompletefeature/profile-picturerefactor/user-authentication-changesfeature/social-loginfix/profile-picture-upload-bugfeature/reset-password-updatefeature/reset-password-update-2
But what have we gained from this approach?
- If the address autocompletion was merged earlier, we wouldn't have ended up with two implementations. Even if the first wasn't shipped to production yet, it would have been a usable component in the codebase.
- We wouldn't have spent as much time on merge conflicts because frequent merges keep them small or non-existent.
- We could have decided postpone social login if we knew it was such a big investment. We didn't because it was too entangled in the monolithic branch.
Working towards mergeable chunks doesn't only give the developer more optionality, it makes the entire organization more flexible.
Hindsight is 20/20. It could have gone differently: maybe working in the dark for a prolonged time didn't affect anyone. But you never know this ahead of time. Priorities change, and you don't have control of the external factors that force them to. If you were halfway the monolithic user account branch and a critical bug or competitor forces your hand, all of your code is held hostage until you get back to it. Even if you don't release it to your users, it's more valuable to have in your main branch.
When code is in a feature branch it doesn't contribute to the rest of the codebase. This 40-hour project is an innocent example. We often fall into the same trap for 250-hour projects. That's a month or two of work—a month or two of work in progress code dilly-dallying. Code rots over time. Work in progress code rots significantly faster.
To paraphrase Eliyahu M. Goldratt (I'm a sucker for the theory of constraints): large amounts of work in progress masks inefficiencies and bottlenecks in the production process. Reducing work in progress can improve cash flow. As work in progress is converted to finished goods and sold more quickly, it accelerates the cash conversion cycle. In our language: code only becomes an asset after it's merged.
Action items
To make this plea actionable: treat each chunk of work as something that should be merged by the end of the week. That doesn't mean it needs to be "done" or available to the end user, it needs to become a citizen of The Codebase.
The hard part is finding your checkpoints. Identify the critical path. What can you strip from a feature while keeping it useful? Do that last. take a step back every few hours, ask yourself what the least amount of work would be to make what you're doing mergeable. Have a bug-free staging environment from day one and keep it that way to ensure frequent merges don't affect quality (read about the broken window theory).
It takes a while to get used to, and will feel uncomfortable at first. But do this enough, and you'll see checkpoints all over the place. Working in small chunks means individual tasks may take longer. But in the long term you and the team as a whole will see gains in flexibility, optionality, and efficiency. These benefits vastly outweigh the time you would win from a bulk discount.
Some desert wisdom to close:
Arrakis teaches the attitude of the knife—chopping off what's incomplete and saying: "Now, it's complete because it's ended here."
How take notes + my Obsidian setup
For the past year, Obsidian has been my note-taking companion. I don't care about fancy features like backlinks, canvas… I like Obsidian because it's fast, minimal (up to you!), customizable, works with Markdown files, and has a good enough mobile app.
I've slowly grown towards a more consistent way of taking and organizing notes. While I'm using Obsidian, it's a system that can easily be ported to any other tool as long as you have something that resembles a filesystem.
My first attempt at Obsidian was years ago, but didn't last long. It was a clunky Electron app that looked out of place between polished native apps. A few years later, I was consistently drawn towards Kepano's philosophy on apps and other things. Kepano is Obsidian's CEO. How could I feel so aligned with the maker but not with the tool itself? I gave it another shot, and it turned out Obsidian had come a long way.
Despite having read a lot (too much) about second brains, I'm not a second brain maximizer. I want a note taking tool to keep me on track in the moment. I don't need to remember what I ate on the second Thursday of last September. The most important takeaway from Tiago Forte's (the second brain guy) PARA organization is to keep notes organized by agency, not category. That's why I use my Now, Next, Notes setup for organization.
Back to Obsidian. Text editing and performance are great. What sets Obsidian apart from other tools is extensibility. I don't want to bloat Obsidian with a bunch of plugins, its customizability allows me to subtract everything I don't need. I've mainly used iA Writer in the past, and have dabbled with other writer/notes apps like Bear and Ulysses. iA has a lovely editing experience, but its library makes it difficult to have an overview of your notes. If iA had an option to display your files in a tree instead of one folder at a time, it would be a lot more useful to me. I want to easily move files between folders. (Apparently iA is working on a new file browser that might improve this.) Now I treat iA Writer as an editing tool, after the ideas have emerged in Obsidian.
Obsidian isn't great on mobile, but I have no quarrel with that. My laptop is for work. I rarely need notes on my phone, as long as I can reference them when necessary.
How I organize files in Obsidian
I use Obsidian for notes, not tasks. My task manager (currently Things) keeps me on track across my responsibilities. When I get to work in the morning, the first thing I do is check my task manager. It helps me decide what I should work on.
Once I get started, it's time to dig up my existing notes or create a new file. I have a file in Obsidian for most projects I'm working on. A note could be broad if it's something I don't work on often (for example, Flare.md
or Mailcoach.md
) or more specific if it's worth tracking in its own scope (for example, Spatie.be Guidelines Redesign.md
). Sometimes I'll create a note for a small task to clarify my thoughts and make it easy to refer back to in the future (for example, TypeScript Transformer null undefined issue.md
).
My notes live in Now, Next, Notes folders. I've written about this in detail, but tl;dr: once a week I review my task manager to determine what I'll likely be working on. Things I'll be working on go to Now, things I won't be working on go to Next. Now is like having a stack of files on my desk for quick access, while the rest is stored in a drawer. Notes contains non-actionably resources like React performance tricks.md
—a separate file cabinet near the library.
Once I'm done with a note, it gets moved to the Archive folder with a date. For example, Archive/2024/08/240830 Spatie.be Guidelines Redesign.md
. Having a date in the filename ensures I don't have any duplicates, and having them in the system in a separate folder means they're still easily searchable.
Here's an slice of my notes in Obsidian. I prefix the folder names with numbers to keep them sorted by actionability.
01 Now/ Flare Client JS.md Href 002.md Laravel Comments v2.md02 Next/ Flare.md Mailcoach.md Ray Redesign.md Svelte by Example.md03 Notes/ Asana.md Hiring.md Leads.md Quotes.md04 Archive/ 2024/ 08/ 240812 Href.md 240823 TypeScript Transformer Null Undefined.md 240827 Beyond CRUD 4 Year Anniversary.md Attachments/
I have Obsidian configured to store any file attachments in Archive/Attachments
to keep them out of view.
Anatomy of a note
I take notes for multiple reasons: to aid my thinking process, to remember what was said during a meeting, to write down feedback when someone's running me through a demo or codebase, to have a log of what I worked on…
Most notes have two sections separated by a horizontal rule. The first section acts as a scratchpad and is in constant movement. The second section is a chronologically ordered log.
# Ray Redesign - Look for more components to memoize- Add menu bar options for view - Update React- Consider Tauri https://tauri.appPR: https://github.com/spatie/ray-app/pull/… ## Goals- Polish design- Fix performance issues for large amounts of messages --- ## 240607 Performance fixes- Replaced virtualization library- Added `React.memo` to `Message` to avoid unnecessary rerendering ## 240605 Kickoff & Tauri research- Meeting notes - …- Tauri pro's & cons - …
Things that might go in the scratch pad:
- Reminders/todos: Things I come across while working on a project I don't want to forget about. Sometimes I split these into multiple lists to group them by priority
- Notes/resources/…: Random notes or links to things of relevance
- Goals: I like the occasional reminder of what the goals of a project are so I don't stray off track
The scratch pad is a freeform section and can look different in every note.
The second part of the note is the log. The log contains sections per date (latest on top) with some brief keywords on the entry. Then a bulleted list on notes for that day. The log is often updated when I complete something from the scratchpad. For example, after I complete "Add menu bar options for view", I'll move to the log and jot down some more notes. Meeting notes are also a prime candidate for the log, as they're bound to a date.
The log is especially useful in the short–mid term. For example, when I'm working on a feature for a few days straight and want to open a pull request, the log is already a rough draft for the summary.
Having a log can serve as a motivation boost too. On days that feel like a rat race seemingly getting nothing done, you might have made a few log entries. Reviewing these at the end of the day can give clarity that you actually did get quite some things done.
In a way, my project notes use a very similar structure as Now, Next, Notes: sorting things by actionability and keeping the important bits floating on top.
Freeform writing with daily notes
Sometimes I just want to write for myself. Not with the intent to publish, unrelated to a project, as a way to clear my mind. I do freeform writing in daily notes—auto generated files using the current date as the filename—which live in 00 Journal.md
.
00 Journal/ 2024/ 08/ 240808.md
This is inspired by Peter Suhm's Writing Habit. Most of what I write in the journal won't see the light of day, but that low bar is exactly what promotes more consistent writing. This is the only place I use tags, because the only metadata on daily notes is the date, I like to have a way to rediscover snippets in the future. When I decide to write a blog post on Obsidian, it can be useful to review my ramblings from the past month by filtering on an #topic/obsidian
tag.
Here's an example daily note. If I write about multiple topics on the same day, I'll separate them with a horizontal rule.
Memorize the map #topic/programming #idea/blog Since I've started programming, the mantra I'd often see passed around is memorization is a waste of time as we always have Google, documentation, and source code to refer to. What's the point of learning which methods are available on an a Laravel collection when we have a great reference is front of us. … --- Absolute values in Tailwind #topic/css #idea/blog While I like to keep my Tailwind configuration as vanilla as possible—besides colors—I've recently started to overwrite a bunch of font-related settings. The default utility classes for font size and line height never really felt good. While a few named sizes would be managable (xs–xl), they don't provide enough variation for an application's font scale. And while extending the scale to something like 4xs–4xl is possible, choosing the right font size becomes a guessing game. The same applies to leading classes between tight and loose. …
My Obsidian setup: themes and plugins
I use the minimal theme with SF Compact Display as my interface font, SF Pro Display for editing, and Berkely Mono for code blocks. I keep the UI as stripped down as possible with the Minimal Theme Settings and Hider plugins. Most of my custom CSS (I'll just dump it here) is about hiding things and making things as visually uninteresting as possible.
I have most of the core plugins disabled to keep the UI footprint small and startup as fast as possible. Here are the ones I kept:
Backlinks | I rarely use them, but nice to have at hand at times. |
Command palette | Keyboard access to everything. |
Daily notes | I use these for freeform writing. |
File recovery | Safety first. |
Files | Wouldn't be able to do much without. |
Quick switcher | More keyboard access. |
Search | To find things. |
Sync | Synchronize across devices. I pay for Obsidian sync as iCloud wasn't always reliable. |
Tags view | I only use tags in daily notes to bring topics to the surface. |
I also sparsely use community plugins. The ones I use are small quality of life improvements.
Better Word Count | When writing a draft I occasionally like to check the word or character count of what I've selected. |
Excalidraw | When I want to store a quick sketch in my vault. |
Hider | Critical! Hide all unwanted bits of UI from Obsidian. |
Minimal Theme Settings | Hide even more things from Obsidian. |
Outlines | Useful to move around items in bulleted lists using keyboard shortcuts. |
Sort & Permute Lines | For occasionally sorting a list alphabetical, or randomizing a long list of ideas in search of inspiration. |
I often read posts of Obsidian scaring people off because of its complexity. I disagree: with a few tweaks, you can set up a beautiful, minimal note-taking environment.
Other organization-related posts I've written in the past:
Introducing href.email
Next week we're sending out the first issue of a new newsletter: href.email.
Content on the internet is at a tipping point. Social media actively discourages sharing links, while AI-generated content is gaining prominence. We want to double down on human-created and curated content. Authors and creators put a lot of effort into their work, which we want to bring to the surface with our own touch.
I'm looking forward to help curate write these the coming months! (And I'm stoked about the domain name, how was that not taken?) Expect a healthy mix of dev/design/product content. Head over to href.email if you want to subscribe.
Great work requires a portion of luck
I thought this image from Christoph Niemann in Sunday Sketching was worth sharing.
We can force ourselves to do good work. But for great work, we need a portion of luck.
PS: I highly recommend Christoph's Abstract episode which you can watch for free on YouTube.
TailwindMerge for Laravel
I've been writing more Blade + Tailwind the past few weeks. Coming from React + Tailwind I really missed tailwind-merge
and clsx
, but luckily came across this great package from Sandro Gehri.
{{-- components/button.blade.php --}}<button {{ $attributes ->merge(['type' => 'submit']) ->twMerge('bg-green-500') }}> {{ $slot }}</button> {{-- home/index.blade.php --}}<x-button class="bg-red-500"> A red button</x-button>
Source code and installation instructions on GitHub.