Work log: April 5, 2026
What shipped today
The big architectural bet landed: Diktura’s data model shifted from a flat app_users table to a proper identity graph. This is the foundation for the “customer is the organizing unit” vision. The old model stored one record per workspace per email — no way to recognize the same person across apps, no way to enrich a profile over time, no way to build a unified timeline without querying every domain table in parallel.
The new model has three layers. persons are tenant-scoped — one per human across all workspaces in the account. identity_nodes link identifiers (email, phone, external ID, anonymous ID) to a person with deterministic priority resolution: external_user_id beats email beats phone beats anonymous_id. workspace_contacts are the per-workspace view, carrying traits and activity timestamps. When a widget call comes in with an email, the system checks whether that email is already attached to a person, enriches the person with any new identifiers in the signal, and ensures a workspace contact exists. Progressive enrichment means an anonymous user who later provides an email gets retroactively linked.
On top of the identity graph, an events table provides a thin, append-only timeline index. Every create action — feedback, conversations, messages, votes, inbound email — writes an event. The unified inbox and customer detail timeline now query one table instead of three parallel domain queries. The events table is a convenience index; domain tables remain the source of truth.
The entire migration was executed via subagent-driven development: 9 sequential tasks dispatched to fresh implementation agents, with spec compliance review catching two critical bugs (null person_id on all events, missing progressive enrichment) that were fixed before shipping.
Completed
No GitHub issues were formally opened/closed for this work — it was a single architectural initiative driven by the brainstorming/planning/execution cycle. The work produced:
- Design spec:
docs/superpowers/specs/2026-04-05-identity-graph-events-table-design.md - Implementation plan:
docs/superpowers/plans/2026-04-05-identity-graph-events-table.md - Deep research report:
docs/research-unified-customer-platform.md - 20 commits on main, all pushed
Earlier in the day: team management features (invite by email via Brevo, role management, member removal), integration cards (Brevo, PostHog, Sentry, GitHub), inbound email webhook fix (Brevo items[] unwrapping), and the lo-fi PostHog-inspired design refresh (Space Mono + DM Sans, offset drop shadows).
Carry-over
- Production deploy — 20 commits pushed but not deployed to Vercel. The identity graph migration changes the database schema significantly. Need to deploy and verify on production.
- Rate limiting — #97 (backlog) for public widget API endpoints. Not urgent but should happen before real traffic.
- Events backfill — existing feedback, conversations, and votes created before today have no events. The timeline will be empty for historical items. A one-time backfill migration would populate events from domain tables.
- Multi-workspace identity — the identity graph supports cross-workspace person recognition (tenant-scoped), but no UI exists for viewing a person’s activity across workspaces yet.
Risks
- No events for historical data — the events table only captures new activity. Existing items created before the migration have no timeline entries. A backfill migration is needed for production data continuity.
- RLS on new tables uses joins — the persons and identity_nodes RLS policies join through workspace_memberships and workspaces to check tenant_id. This could be slow at scale. Monitor query performance.
- Widget backward compatibility — the
app_user_idAPI parameter is kept for widget backward compat but internally maps toexternalUserIdfor identity resolution. Old widget code will continue to work, but the semantic meaning changed.
Flags and watch-outs
- The
app_userstable is gone. Any code path that references it will crash. Grep confirmed zero references in web/ TypeScript files (except the backward-compat API parameter name). - The
workspace_contactstable uses the same UUIDs as the oldapp_usersrows (the migration copiesau.idasworkspace_contacts.id). This means existing foreign keys on domain tables still point to valid records. - 123 tests passing across 16 test files. No test gaps introduced.
Next session
- Deploy to production —
vercel deploy --prod --yes --scope synaxisfrom repo root. Verify key pages load with real data. - Events backfill migration — write a SQL migration that creates events from existing feedback_items, conversations, and roadmap_votes. One INSERT…SELECT per domain table.
- Test with real widget — send a support message through the widget, verify identity resolution creates the person + contact + event correctly.
- Consider filing issues for: events backfill, multi-workspace person view, widget v2 anonymous_id support.
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.
The costume just got cheap
If 80 percent of what you thought was judgment turns out to be pattern recognition, what does that say about you? Not about your job — about you.
The bottleneck moved and nobody noticed
When execution becomes nearly free, the bottleneck shifts from doing the work to deciding what work to do. Most organizations are optimized for the wrong constraint.
The inbox nobody reads is the one that matters
Every organization has a monitoring system that works perfectly and reports to nobody. The gap between having information and acting on it is where most failures actually live.