Giving blog posts a project identity
Up until today, every blog post on this site was implicitly a portfolio post. There was no way to distinguish a post about building the vault from a post about migrating to React Router. They all lived in the same flat list with the same visual treatment.
That was fine when the blog only covered one project. But now there's the vault, and as of today, project_void is joining the roster with three engine-build posts ready to publish. The flat list doesn't cut it anymore.
The problem
I want themed blog cards on the index page. Each project gets a distinct visual identity: a background image, an overlay treatment, maybe an accent colour. When you hover over a vault post, you see the vault. When you hover over a project_void post, you see something darker and more atmospheric.
But the frontend can't theme what it can't identify. The blog_posts table had no concept of which project a post belonged to. Step one was always going to be the data model.
The migration
The change itself was straightforward. A new project column on the blog_posts table with a CHECK constraint limiting values to a known set: portfolio, vault, project_void, ironiq, drewbrew, engineering_gym. The default is portfolio, which meant every existing row got backfilled automatically by the ALTER TABLE.
ALTER TABLE drew_portfolio.blog_posts
ADD COLUMN project text NOT NULL DEFAULT 'portfolio';
ALTER TABLE drew_portfolio.blog_posts
ADD CONSTRAINT blog_posts_project_check
CHECK (project IN (
'portfolio', 'vault', 'project_void',
'ironiq', 'drewbrew', 'engineering_gym'
));Then a single UPDATE to tag the one published vault post, and a NOTIFY pgrst, 'reload schema' to make PostgREST pick up the new column.
I went with a CHECK constraint rather than a foreign key to a lookup table. The set of projects is small, known, and unlikely to change often. A whole table for six strings felt like premature normalisation. If the list grows significantly or needs metadata (display names, colours, image paths), that's a future migration. Earn the complexity when it's needed.
The admin panel changes
The frontend needed to know about the new field in three places: reading posts, writing posts, and importing posts.
For the admin panel, I added a PROJECT_OPTIONS array as a single source of truth for the dropdown values and their display labels. The editor form got a new <select> dropdown sitting alongside the Date and Tags fields. Nothing fancy, just a controlled select that feeds into the same payload object that gets sent to the edge function.
The edge function itself needed zero changes. It already passes the metadata payload straight through to Supabase with a spread operator, so any new column that exists in the table just works. That's the benefit of keeping the API layer thin and transparent.
The import flow defaults new posts to portfolio since the Obsidian frontmatter doesn't carry a project field. You pick the project from the dropdown after importing. Could I have added project detection from frontmatter? Sure. But importing is a manual process anyway, and adding one dropdown click is simpler than building and maintaining a frontmatter-to-project mapping.
The build pipeline
The fetch-content.js pre-build script got project added to its Supabase select query. This means posts.json (the static metadata index that the blog index page reads at build time) now carries the project field for every published post. The frontend has everything it needs to theme cards by project whenever that feature gets built.
What comes next
This was deliberately scoped as a data-model-only change. The blog index still looks exactly the same. The project field flows through the whole pipeline (database, admin panel, edge function, build script, static JSON) but nothing on the public site consumes it yet.
The next branch will restyle the blog index into themed cards. Each post gets a card container with a project-specific background image, an overlay for text legibility, and a hover treatment. The project_void cards will use a dark, atmospheric texture. Vault cards will reuse the vault background. Portfolio posts will get something cleaner and more technical.
But that's a separate PR. This one did one thing: gave blog posts a project identity. The plumbing is in place. Now we make it look good.