Work log: March 22, 2026
What shipped today
Content model refactor (v2.1)
The entire content data model was rebuilt from scratch. The old stage_data JSONB blob — a nested structure where every field lived inside a stage-specific bucket — was flattened into proper columns on the contents table: outline, script, interview_qa, draft, intro, featured_image_url, guidance, idea_summary. Eight new columns, all backfilled from existing data, dual-written by the engine, and read by the web with stage_data fallback. This unlocks Supabase realtime subscriptions on individual fields, which the JSONB approach couldn’t support.
The stage machinery was then completely replaced. The old system used stage + stage_status columns, stage configs from content_types, sequential field chaining (sequential_remaining), auto-approve logic, and stage_start commands — a complex state machine that caused cascade bugs and hung commands throughout the session. All of it was replaced with a single status column with four values: generating, interview, review, final. Three new engine handlers (content.generate_outline, content.generate_draft, content.redo_field) replaced the old content.stage_start and content.field_generate chain. The redo handler is deliberately non-cascading — it regenerates one field and stops.
The content detail page was restructured to render based on status instead of looping over stage config field definitions. Four clean branches: a progress indicator for generating, Q&A display for interview, editable field cards for review, read-only display for final. Stage config dependency fully removed — no more fetching or parsing content_type.config.stages.
Content creation and metadata
The content creation form was simplified — content type picker replaced with length presets (short ~300, medium ~800, long ~1500 words) with format guidelines, plus a style dropdown (professional, conversational, social, provocative, analytical, neutral). Both stored as proper columns. A meta info block was added to the content detail page showing length, style, guidance, idea summary, tags, categories, and SEO keywords. AI suggest buttons for categories and tags were split into separate per-section actions.
Engine prompts were updated to use target_length tiers and style instead of content type slugs. Intro generation capped at 75 words. Guidepost prompt iterated multiple times — went from too vague to overcorrected (every sentence on its own line) to a concrete example-based approach that shows the AI exactly what good formatting looks like.
Cleanup and bug fixes
Stale copy referencing removed features (feeds, scans, articles) was updated across 6 files. Unused nav icons, orphaned bookmarks actions, stale column selections, and the content type header label were all cleaned up. Notification emails and onboarding flows updated for the new pipeline. Stale content stuck in generating status was triaged via migration. Multiple deploy failures from parallel worktree merges were diagnosed and fixed — root causes were a TypeScript type error in the length preset function and a missing npm dependency.
A critical engine bug was found and fixed: two parallel agents (#1552 and #1554) both added dual-write logic to update_stage_field, causing double UPDATEs that created database locks and hung the engine. Removed the duplicate.
Completed
v2.1 — Content model refactor
- #1551 — Add content columns and backfill from stage_data
- #1552 — Update engine handlers to write content columns instead of stage_data
- #1553 — Update web UI to read content from columns instead of stage_data
- #1554 — Update field edit/save to write columns instead of stage_data RPC
- #1555 — Update content actions (copy, export, publish) to use columns
- #1561 — Add status column and backfill from stage/stage_status
- #1563 — Update web UI to use status column instead of stage/stage_status
- #1565 — Update content creation to trigger new pipeline commands (done by #1570)
- #1566 — Refactor notification emails for new status-based pipeline
- #1567 — Extract shared generation service + create generate_outline handler
- #1568 — Create generate_draft handler
- #1569 — Create redo_field handler + deprecate old handlers
- #1570 — Web: switch to new pipeline commands
- #1577 — Status-driven content detail layout
- #1578 — Status-driven content actions
- #1579 — Remove stage config dependency
v2.0 — Product simplification (continued from March 21)
- #1533 — Update stale copy referencing removed features
- #1534 — Remove unused nav icons
- #1535 — Remove stale sources/bibliography column selections
- #1536 — Remove content type name from content detail header
- #1537 — Remove orphaned bookmarks server actions
- #1540-1542 — Deploy failures resolved
- #1544 — Add length and style to each row on /contents
- #1545 — Clean up/restart all old “working on it” content
- #1546 — Add meta info block to content detail page
- #1547 — Show 3 rows of idea content on each row in /ideas
- #1549 — Clean up idea detail page (remove strength, voting, articles, content type dropdown)
- #1550 — Backfill style and target_length from old content types
Release progress
- v2.0 — Product simplification: 0/25 open (complete)
- v2.1 — Content model refactor: 0/15 open (complete)
- v1.5: 1/50 open (#743 dashboard redesign, backlog)
Carry-over
- Guidepost prompt quality — iterated 3 times, needs real-world testing on fresh content to confirm the example-based approach works
- Three pending Supabase migrations that may not have been pushed: style column, auto-approve stages, reaction soft-delete (confirmed pushed during session)
- #1543 (Refactor API, MCP, and Apple app) — needs clarification, waiting on Paul’s reply
- Engine deployment on Railway — verify the latest handlers (generate_outline, generate_draft, redo_field) are live and processing commands correctly
Risks
- The old
content.field_generateandcontent.stage_starthandlers are deprecated but still registered. In-flight commands from before the refactor could trigger them. Monitor for a few days before removing. - Parallel worktree merges caused multiple conflicts and deploy failures today. The duplicate dual-write bug in content.py was a direct result of two agents modifying the same file independently.
- The content detail page was significantly restructured — visual QA needed across all 4 status states with real content.
Flags and watch-outs
- 38 commits in one day across engine and web — high volume increases risk of subtle breakage
- The guidepost prompt went through 3 iterations and still needs validation on fresh content generation
- Content stuck in
generatingstatus will be a recurring issue until the old handlers are fully removed — any code path that sets status to generating but queues an old-style command will hang
Why customer tools are organized wrong
This article reveals a fundamental flaw in how customer support tools are designed—organizing by interaction type instead of by customer—and explains why this fragmentation wastes time and obscures the full picture you need to help users effectively.
Infrastructure shapes thought
The tools you build determine what kinds of thinking become possible. On infrastructure, friction, and building deliberately for thought rather than just throughput.
Server-side dashboard architecture: Why moving data fetching off the browser changes everything
How choosing server-side rendering solved security, CORS, and credential management problems I didn't know I had.
The work of being available now
A book on AI, judgment, and staying human at work.
The practice of work in progress
Practical essays on how work actually gets done.
When your agents start breaking each other's code
Two agents modified the same file independently and created database locks. The fleet hit 135 issues in one day — and the coordination problem that comes with it.
The removal tax
The most productive thing you can do with a product is take features away. Eighty-nine issues closed across eight projects, and the hardest lesson came from a pipeline that ran perfectly and produced nothing.
The product changed its mind
A product pivoted its entire philosophy mid-session — from 'here's your list' to 'here's your next thing.' The code shipped in the same conversation as the idea. That's not iteration. That's something else.