Skip to content
Rishabh Shukla

Proof of work, in progress

Building

What was attempted, what broke, and what was understood afterwards. The entries about things that did not work are the ones worth reading — a build log with no dead ends is not a build log.

Currently buildingStage 1B complete · Stage 2 next

Life OS

A system for running a life, with two surfaces over one data model: a private application where everything is recorded, and this public site. A fact is written once and appears wherever it belongs — the portfolio you are reading is not a copy of a résumé, it is the same records rendered for a different reader.

The design rests on one decision. There is a single entities table with tags, links and events beside it, rather than a schema for each feature. A module is a type registered against that spine — it declares its own shape and its own rules, and inherits routing, visibility, search indexing, publishing and the weekly review from having declared them. That is what makes cost-per-wear, a backlink between two unrelated modules, and the weekly roll-up a query rather than an impossibility. It is also what keeps the thirty-eighth module as cheap as the second.

The two surfaces are two separate Postgres databases, and the separation is the point. The private one is the source of truth. The public one holds only what an explicit owner action promoted into it, and this site holds no credential that reaches the private database — so a mistake in a visibility filter here cannot expose a private record, because the record is not in the database being queried. Publication is deliberately not automatable: no agent, webhook or scheduled job can perform it. It is enforced in the grants rather than in code — service_role has no SELECT on entities at all.

The rules the system is not allowed to break are written as tests rather than as intentions. Events are append-only, enforced by row-level security for the application and by a trigger for anything privileged. Unpublishing purges rather than hides. A module marked never-public is refused at three separate layers, one of them a trigger inside the public database itself. And a snapshot alone has to be able to rebuild a working instance — which was not taken on trust: it was executed live in September 2026 into a scratch project created for the drill and deleted afterwards.

What does not exist yet is as much a part of an honest description as what does. Lenses — named curations over the spine — can be planned and published by the core library, and no screen anywhere calls either function, so the only publishing possible today is one record at a time. The public pages fall back to reading the store directly because of it. That gap is the next thing.

Built with

  • TypeScript
  • Next.js
  • React
  • Tailwind CSS
  • PostgreSQL
  • Supabase
  • Row Level Security
  • Zod
  • Vitest
  • Model Context Protocol
  • WebGL
  • Vercel
Repository

The log

17 Sept 2026Public site

A menu that never covered the page

Rebuilding this site, the mobile menu opened and the page stayed visible behind it. The overlay's background computed as 98% opaque black. Its z-index was above everything. Nothing in the markup or in the computed styles was wrong.

The overlay is rendered inside the header, and the header has a backdrop blur on it. A filter — and backdrop-filter counts — makes an element the containing block for every fixed-position descendant. So position: fixed with inset: 0 was not resolving against the viewport at all. It was resolving against the header: a full-width strip about fifty pixels tall. The background was painted correctly, into a box the size of a navigation bar. The links, which overflowed, still landed roughly where they were expected, which is what made it look like a styling problem rather than a layout one.

Two things worth keeping. The bug was invisible to every tool that reports what an element looks like, because the element looked exactly right — the question nobody thought to ask was how big it was. And it could not reproduce at desktop width, where the overlay never opens at all.

Rendering it into the document body instead puts it back on the viewport's coordinate system, where a full-screen overlay belongs.

16 Sept 2026Stage 1B

The front page was empty after publishing to it

A lens is a named curation over the spine — which records, under which heading, in which order. The public pages were written to read from one, which is correct, and it meant that publishing records and then reloading the site produced exactly the page you had before: a name, and nothing under it.

Nothing was broken. publishLens exists in the core library, it is tested, and no screen calls it. The lenses table is empty and there is no way to put anything into it. Both public pages were behaving correctly with respect to a thing that cannot yet be created.

The fix is a fallback: prefer the owner's arrangement wherever there is one, and fall back to everything published of that type where there is not. What makes it worth writing down is the failure it prevents, which is not a blank page. It is a blank page shown to somebody who has just published their whole résumé and now believes the system did not save it.

10 Sept 2026Stage 1B

A hole found while creating the first bucket

Assets are entities on the spine, so a file inherits visibility, row-level security, the event log, undo and the publish pipeline, instead of getting a second and weaker access model written beside the real one.

Creating the first storage bucket surfaced something in a schema nobody had read, because the platform had created those tables rather than this repository. Supabase grants the anonymous and authenticated roles full DML on storage.objects and storage.buckets — including TRUNCATE — and then narrows access with policies. TRUNCATE is not subject to row-level security. The policies were never going to see it.

It was the same shape as a hole found earlier in the public schema, present in both projects, and found before there was anything in either bucket to lose. The lesson taken from it was not about storage. It was that a schema the platform creates is still a schema in your database, and "we did not write those tables" is precisely the reason nobody had looked.

5 Sept 2026Stage 0

Refusing to restore into a live database

A backup is not a backup until it has been restored, so the requirement was written as an invariant: a snapshot alone rebuilds a working instance. In September 2026 it was executed rather than asserted — a scratch project created for the drill, restored into from a snapshot, verified, and deleted afterwards. The source was read from and never written to.

The drill's real output was not the restore. It was the guard. Restoring is a destructive operation aimed by an environment variable, and the failure mode is a tired person pointing it at the live database. So the restore now refuses to run against either real project by name, and the drill's checklist records those refusals as passes alongside the successful restore.

Writing down that a thing is safe is not the same as watching it refuse.