I asked my own database how I use my app — then rebuilt it around the answer
A data-driven redesign of Life OS: querying my real usage to find the one feature I actually use, then rebuilding the board, killing the features I never touched, and giving the whole app a fresh teal identity with light and dark themes.
A few months ago I wrote about building Life OS — a personal operating system to replace my Notion setup, organized around a single loop: Capture → Process → Plan → Execute → Review → Repeat. Ten modules. A strict architecture. A warm, hand-tuned design system I decided on before writing a single component.
I was proud of it. I still am. But I have now used it every day for months, and I noticed something uncomfortable: I open it constantly, and I only ever touch one screen.
So instead of guessing what to change, I did something I should have done a long time ago. I asked the database.
Stop guessing, run the query
The nice thing about owning your own app is that the truth is sitting right there in Postgres. I ran a single query across every feature table — total rows, rows in the last 30 days, last 7 days, and the date each was last touched.
The result was almost funny:
- Tasks: 47 total, 40 in the last month, 27 in the last week, last used today.
- Everything else: last touched around launch day in May and never again.
- Goals, resources, daily-plan items: zero rows. Ever.
I had built an Inbox, a Calendar, Habits, Learning paths, Weekly reviews, and a Goals system with success criteria — and the data said I used approximately none of it. The elegant Capture → Process → Plan → Execute → Review loop had, in practice, collapsed down to a single verb: Execute.
Then I looked at how I use tasks, and it got more specific. 81% were Work. 87% lived inside a project. 87% had a scheduled day — but exactly one had ever been given a time. Priority was almost always left on the default. I created tasks directly; nothing ever came through the Inbox.
I wasn't using a personal operating system. I was using a day-scheduled task list that happened to have nine other tabs bolted onto it.
Doubling down instead of adding on
The instinct with a personal project is always to build more. The data was telling me to build less.
So the nav went from eleven items to four: Today, Tasks, Projects, Settings. Inbox, Calendar, Habits, Learning, Review, and the per-area pages all came out of the sidebar.
The important detail: I hid them, I didn't delete them. Every route still works if you type the URL, every table still holds its data, and the columns I stopped using — due_date, parent_task_id, priority — stayed in the schema, dormant. This is the whole point of building your own tool. Nothing is a one-way door. I can delete a feature from my daily life without deleting a single row, and un-hide it just as easily if I'm wrong.
The board I actually wanted
The old Home was a flat list of everything due today. When you're juggling five things at once, a flat list is just a wall of pressure. I wanted a board.
The new Today is a small kanban:
- To Do and In Progress lanes for today — which meant adding a real
in_progressstatus, since the app only hadtodoanddone. - Below it, an Upcoming strip: one column per day for the next week, plus an Unscheduled tray.
- You drag a card from In Progress onto Tomorrow, or pull something two days out into In Progress today — the exact motions I do in my head every morning.
- Anything overdue auto-rolls into today when the board loads, so guilt doesn't pile up in the past.
The usage data also quietly deleted two features while I was in there. Since I move tasks around constantly — and moving a task sets its scheduled date — a separate due_date was pure noise. I collapsed to a single date. And subtasks and priority, which the numbers said I never really used, came out too. Every deletion made the thing I do use faster.
A new coat of paint that had to earn its place
I have this app open sixteen hours a day. After months, the original warm parchment palette — lovely as it was — started to feel like the same room I'd been staring at forever. I wanted something fresh, but I refused to make it a random reskin. So I built a proposal page and looked at real options on the real layout, in light and dark, before committing.
I landed on Slate first — cool blue-grey neutrals, a confident slate accent — and then, because I wanted fresh more than safe, evolved it into Teal: #0d9488 on a cool neutral with a faint teal bias. Warm enough to be calm, cool enough to feel like a tool.
Two things made this a real upgrade rather than a hex swap:
A proper light and dark theme. Because the entire app already ran on CSS variables, re-theming was mostly re-defining tokens. The trick was doing it correctly across all three viewer states — an explicit choice, and the un-stamped "follow the OS" default:
:root { /* full light palette */ }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) { /* dark palette */ }
}
:root[data-theme="dark"] { /* dark palette again, for the manual toggle */ }A tiny inline script applies the saved choice before first paint so there's no flash, and a toggle in the sidebar remembers it. It respects your system by default and lets you override — which is exactly how a theme toggle should behave.
A new layout with an editorial voice. The header became a small uppercase date eyebrow over a large Today title; the stats became big tabular numbers instead of boxed tiles; and the tasks became dense hairline rows with the meta pushed to the right and a slim area-colored rail on each card. It reads less like a demo and more like a serious tool you'd trust with your day.
The bugs a redesign always drags into the light
Re-themes are great at exposing the shortcuts you took the first time.
The moment I switched to teal, the menus didn't follow. Turns out tailwind.config.ts still had the original slate #4f6b8a hardcoded for primary and accent — so every shadcn dropdown, ghost button and focus ring was quietly ignoring the new palette. Pointing those tokens at the CSS variables re-themed every menu in the app at once.
The date picker had a subtler bug: "today" was styled with teal text, and the selected day with a teal background and white text. On the one day where today is also the selected day, teal text landed on a teal background and the number simply vanished. The fix was to stop using color to mark "today" and use an inset ring instead — a good reminder that two independent states will eventually collide on the same element, and you should design for the overlap.
And finally, reordering
The last thing that had been quietly annoying me: I could move tasks between lanes, but I couldn't reorder within a lane. Switching the board from plain draggables to dnd-kit's sortable — each lane a SortableContext, each card a useSortable — fixed it, and a single server action now persists both the new lane and the new order in one shot.
What I actually learned
The best decision in this whole redesign wasn't a color or a layout. It was letting a SQL query overrule my own memory of how I work.
I remembered using habits and reviews and goals. The data politely disagreed. Building for yourself makes it tempting to keep every feature you were once excited about — but the freedom of owning the whole stack is that you can look at the evidence and act on it without a roadmap meeting. Nobody gets stuck with a feature they don't use. There's no migration to fear. It's not Notion; I can just change it.
So the app does less than it did in May, wears a fresher face, and fits the way I actually work down to the pixel. That's the entire reason I built it from scratch in the first place.