cmd/projax/main.go boots a pgxpool against PROJAX_DB_URL (falls back to
SUPABASE_DATABASE_URL), auto-applies embedded migrations on start
(disable with PROJAX_AUTO_MIGRATE=off), and serves on PROJAX_LISTEN_ADDR
(default :8080).
store package wraps the unified view + projax.items writes. Item has
helper methods for templates: IsArea, Editable, SourceRefDeref. The
Promote() flow runs the insert + item_links link inside a single
transaction so the source row drops out of items_unified atomically.
web package: per-page html/template instances parsed against a shared
layout.tmpl, embedded static/style.css, HTMX from CDN. Pages:
GET / tree of items_unified
GET /i/{path} detail (editable for projax, read-only +
promote form for mai.projects)
POST /i/{path} update projax-native item
POST /i/{path}/promote one-page promote (HTMX-aware fragment for
inline classify)
GET /new?parent={path} create form
POST /new create projax-native item
GET /admin/classify orphan list with inline HTMX promote
GET /healthz DB ping
GET /static/* embedded assets
Auth is intentionally out of scope for v1 — service binds to whatever
PROJAX_LISTEN_ADDR points at, deploy guidance pins it to the Tailscale
interface (covered in 1d README).
Tests (skip when DB env is unset):
TestTreeRenders, TestHealthz,
TestDetailProjaxNativeEditable, TestDetailMaiProjectsReadOnly,
TestClassifyListsOrphans, TestPromoteRoundTrip.
projax.items_unified joins projax.items (deleted_at IS NULL) with
mai.projects so a single query feeds the tree UI. mai.projects.id is a
text key, so a deterministic placeholder UUID is derived from md5(p.id);
projax-native rows keep their gen_random_uuid().
When a projax item is created with an item_links row pointing back to a
mai.projects id (ref_type='mai-project'), the corresponding mai.projects
row drops out of the view — that's how the "Promote to projax" flow
makes the duplicate disappear without ever touching mai.projects.
Test coverage:
- both sources appear in the view
- promotion link hides the mai source row and surfaces the projax row
- 0001_init.sql: projax.items + projax.item_links tables with indices,
partial-unique root slug, updated_at trigger, schema grants to the
application role.
- 0002_path_trigger.sql: BEFORE-write trigger maintains items.path via
recursive parent walk; rejects cycles and structural-rule violations
(areas at root, projects not at root). AFTER trigger rewrites
descendant paths on slug rename or re-parent.
- 0003_seed_areas.sql: dev, sports, home, work, health, finances, social.
- db/migrate.go: embed.FS-backed sequential runner.
- db/migrate_test.go: integration suite covering idempotency, nest,
rename propagation, re-parent propagation, cycle rejection, and
structural rules. Skips when no DB env var is set.
Also ignores .m/events.log and .m/locks (per-worker scratch).
Copy the design PRD, .claude config, .m config, .mcp.json, and AGENTS.md
symlink from m's main working tree so the worker has the full project
context before starting Phase 1 implementation.