The courage to delete
This blog post will be a recap of my UX lessons, and also a observation about vibe coding.
How it started
LifeLab started as a hybrid between note taking apps and a jupyter notebook. Both notes and code are stored in the same database. The code blocks can read and modify the database to create dashboards and mini apps. The LifeLab was backed with PostgreSQL and I spent a lot of time on the database schema, because I wanted something that can scale (stay performant) to a lifetime and I did not want to rely on filesystem without any sort of indexing. I succeeded in some things and learned from the rest.
229
Show code
blocks = nb.query_all()
print(len(blocks))No like: Jupyter-style blocks take too much space
The most (in retrospective) obvious learning is: Jupyter notebook style note taking kinda sucks, for the same reason why I hate actual jupyter notebooks. A major issue is as you add more blocks you need to scroll too much. Even as you separate it into pages and daily journal, it is quite cluttered. I minimized the UI and removed all buttons except "run" to avoid the busy feeling I get when I open vscode, but it still was too busy. Whenever I made a code block to run as dashboard, it would take up valuable screen real estate. I battled with this feeling the most and ended up with a org-mode-inspired headline-first approach. More about that in a future post.
Like: Dedicated analysis pane
I eventually moved all static and recurring content (like dashboards, buttons, or pinned blocks as bookmarks) to a configurable side panel. It kinda makes sense where the main panel is used for adding content and reading, and the analysis panel is used to check for upcoming tasks, time tracking or spending. In mobile mode, it is a single button in the middle of the page that swaps between the views.
No like: AI
I am a ML engineer, so I really thought I can make interesting or fun use cases using traditional or prompt based approaches. I think of myself as fairly creative and multidisicplinary coder, but after 1400 commits into LifeLab I might need to rethink that. I tried a ton of things but none of it stuck to me. I (vibe-)impemented a MCP server that can query blocks and I thought that would be more powerful that claude reading text, but turns out claude code with grep is probably a better note assistant than anything I could've made.
I also fully committed to PostgreSQL for the PGVector extension. I thought I can experiment with different embedding models (Looking at you, Jina embeddings) to see if I can make interesting connections, but turns out I mostly write down I94 entry dates and dog names and I have not actually found any interesting use cases for semantic search.
I also fully intended to do a numind based LLM extraction. The idea was, I define a schema as a block, and use numind to extract information based on this schema from blocks. But it turned out easier to just write blocks following that schema in the first place without automation.
Like: Scripting
What I did really like was being able to run arbitrary code with an API to query other blocks. It is definitely something that felt off in Notion and Tana, because you had to learn their curated query language. With arbitrary code, it is way more flexible and I do sometimes open LifeLab just to run a tiny script, or just write code for Advent of Code. My favorite feature is smart ingest. It is a text field you can paste things into, with hooks to run arbitrary code. For example I have a hook to read a semantic scholar link and paste the abstract of an article in a new page.
As I developed LifeLab, I kept adding more and more features that:
- Might be useful at same point
- Cost me almost nothing to implement
Even though I did not actually need them, I felt they might be useful for a future experiment and kept leaving it there. The codebase kept growing (despite my occasional weekend claude-please-cleanup-everything sessions) and the True User Experience I chased kept getting murkier. I also generally feel developers often have a strong attachment to their code (despite, you know, vibe coding it) and less willingness to delete stuff. I also feel, if I delete a particular thing, am I just pivoting constantly to some other program? Do we constantly evolve a single code base (which completely changes it's behavior and expecitations), or is it better to make single-shot (non-maintained) repositories? It seems neither is desirable. Maybe the only good repository is the one you fully commit to and slave away on a single design?
After 1.6k commits, I definitely feel the urge to vibe code a subset of LifeLab as a easier-to-use-app, but will I finish with that one? Or will I vibe code another app after that? It seems the dopamine treadmill has no real end. That being said, once I do find the courage to delete a bunch of stuff, here is a short list:
- PostgreSQL. I liked the experience (it is incredibly performant software), but setting up a database on hetzner was a bit too much effort. I would switch to sqlite despite losing json indexing. I don't care about PGVector anymore
- Offline-PWA. I think any future version will need native mobile app with some sync layer. I really like it as a website with authentication, but working with indexeddb was not a good experience and I feel that is a dead-end for offline mode. It works great as wiki! But the offline-indexeddb-postgresql sync was a huge pain.
- Rhai and Python scripting language. Right now, LifeLab supports Python and Rhai, which doubles the changes I need to implement after every API update. This was a huge waste of effort and I did not learn Rhai that well. If I want to make a app, I think I will make the scripting language typescript to use JS engines on devices. Python is quite hard to embed (and if we DO embed, there are no ML libraries to import). I feel the ergonomics of scripting languages matters a lot here and is worth exploring further.