Should we build it ourselves, headless, on Supabase, and let Claude keep it tidy? A researched gut-check before committing.
Go. Build it yourself on Next.js + Supabase, draw the screen with a headless toolkit (TanStack Table + Virtual), and let Claude maintain it through the Supabase connection. Nothing the research verified argues against this. Two things to prove out early: (1) buttery scrolling when rows are nested, different heights, and expanding; (2) guardrails so the AI can't quietly break the data.
Use a toolkit that does the heavy lifting (smooth scrolling through huge lists) but lets you control every pixel of how a row looks. The makers' own demo scrolls 50,000 rows of different heights smoothly, so the engine is proven.
The honest catch: that demo is a flat list. Our list is nested and folds open/closed, and rows change height when you expand them — and that exact combination isn't proven anywhere. So we build a quick throwaway test of it first, before betting on it.
We looked at the popular ready-made grids and they don't fit: one hides the feature we need behind a paywall (and can't do our shared-item trick anyway); another is blazing fast but draws cells in a way that can't show a foldable outline.
Update — the third path (Checkvist follow-up): there's a simpler option than any scrolling-engine: just draw the whole list and keep each row cheap. That's essentially what your board already does. The research says this is the right, lower-risk choice up to a few thousand rows, and it dodges the scrolling-engine's height-measuring bugs entirely. Only switch to the heavy scrolling-engine if a list ever grows into the tens of thousands. Honest caveat: public sources could not confirm how Checkvist actually works — that "flat, cheap, hand-updated" picture comes from our own earlier teardown, not verified evidence.
TanStack Table (logic-only, no built-in virtualization) + TanStack Virtual (headless windowing). Official example renders makeData(50_000) with estimateSize + measureElement. Variable heights mean dropping native table layout: keep semantic tags but use display:grid/flex.
Risk: 50k demo is flat. Nested + variable-height + expand/collapse is the unverified regime; open dynamic-measure bugs (#235/#425/#659/#832/#896) cause upward-scroll stutter. Mitigate: good estimateSize, overscan, "block translation" layout. Prototype before committing.
Rejected: AG Grid Community Tree Data = Enterprise-only + single-parent. Glide = canvas (millions of rows) but all cells via Canvas API, no tree/expand.
Follow-up — flat DOM as a 3rd option. content-visibility:auto = flat DOM + paint-skip (your current board), not virtualization — nodes stay in DOM + a11y tree. Comfortable to low-thousands; Lighthouse flags ~800 (warn)/~1,400 (excessive); verified painful ~20k (content-visibility can't match a virtual list there — Nolan Lawson trace). TanStack Virtual's variable-height cost is real & primary-sourced: runtime measureElement, scroll-up jank (#659/#832), and a now-fixed bug (#1133) triggered by exactly expand/collapse/delete. Recommendation: stay flat for low-thousands; adopt TanStack Virtual only at ~10k+. Checkvist internals UNVERIFIED publicly; Logseq does virtualize (DataScript in-memory + virtualized-list). Shared real pattern across outliners = in-memory store + adjacency list, not "render flat everything."
The standard way to store "things inside things" is for each item to remember its parent. Its one classic weakness — "show me everything underneath this" — is basically solved on our database: it can walk the whole tree in one fast query (200k items in under a tenth of a second).
The twist is your mirrors — one item living under several parents. A plain tree can't do that without making copies (which we refuse). The fix is to store an item's identity separately from where it's placed, so one real item can appear in many spots. That part is sound in principle but we should prove the exact design (and how to stop loops) before locking the schema.
Adjacency list (self-ref parent_id) is the conventional choice; Karwin flags it only for descendant queries — neutralized by recursive CTEs (WITH RECURSIVE, PG 8.4+): ~200k rows <0.1s with a parent_id index.
Mirrors → DAG, not a tree. Decouple identity from placement with an edge/junction table (node_id, parent_id, position); one record, many parents, shared state. Closure-table / materialized-path / ltree are heavier (unvalidated). Open: cycle-prevention strategy + DAG query patterns not source-verified — spike before schema.
When a big thing's progress is the sum of its parts, a mirror creates a trap: if one shared item sits under two parents, you must not count it twice. The research found no proven recipe for this — it's the single most important thing to figure out before building progress bars. It needs its own focused research before we rely on it.
Zero verified claims. Roll-up over a DAG must count each distinct node once per ancestor set (no double-count). Decide client-compute vs Postgres recursive CTE, and on-read vs incremental (triggers/materialized). Dedicated spike required — likely an on-read recursive CTE counting distinct descendants; optimize only if slow.
The trick for "drop an item between two others" is the same one Figma uses, and it's the approach you already picked — so that's confirmed. Because of mirrors, an item's position is remembered per-spot (the same item can sit 2nd under one parent and 5th under another).
The drag-and-drop details for a nested, foldable list — and moving an item from one parent to another — weren't pinned down by the research and need a look before building.
Fractional indexing (Figma): positions as arbitrary-precision base-95 strings, not floats (floats exhaust precision ~50 between-inserts); insert = average of neighbors; collisions resolved server-side. Matches the existing fractional-indexing dep. Order is per-edge for mirrors.
Open: dnd-kit nested/tree drag + cross-parent move patterns not verified — research before building.
The database can push changes to the screen instantly — perfect for a one-person tool. The known downsides of that feature only bite at big multi-user scale, which doesn't apply to you.
The remaining backend choices (how saves and instant-undo are wired) are standard, well-trodden territory — low risk, just not separately fact-checked here.
Supabase Realtime postgres_changes (INSERT/UPDATE/DELETE/*) over WebSocket — ideal single-user. Its limits (no DELETE filter, single-threaded, auth fan-out) are multi-user concerns, irrelevant here.
Not separately verified: Server Actions vs route handlers, RLS-for-single-user, optimistic-update patterns, ltree. Plan: Server Actions + optimistic UI (standard).
You want the same items shown two ways — the foldable outline and a "what I'm doing now" board. The research didn't pin down the best way to keep both in sync. The safe principle: both are just different windows onto the same single set of items, never separate copies. Confirm the approach before building the second view.
Zero verified claims. Treat outline (TanStack) and kanban (dnd-kit) as projections of one row set (client selectors or SQL views) — single source of truth, no state divergence. Validate the projection pattern before the second view.
This is the deciding reason to build your own: the database connection lets Claude actually read and rewrite the data to keep it tidy — something the off-the-shelf apps won't allow. Confirmed.
The catch, stated bluntly: AI agents reliably make safety mistakes on databases unless you give them written rules — and the early version of this connection had no "are you sure?" on destructive changes. The good news: when the rules are written down as a checklist, the AI follows them correctly. So a guardrail layer — safe-by-default, an audit log, an undo, and a "confirm before deleting" — isn't polish, it's required.
Supabase MCP: execute_sql, apply_migration (WRITE default; read_only opt-in). Dev flow: iterate via SQL → formalize migrations after advisors; scoped to local/staging, not prod. Satisfies the read+write requirement.
Non-negotiable: agents skip RLS, ship RLS-bypassing views (missing security_invoker=true), hallucinate CLI cmds; destructive-op confirm was a roadmap item at the Apr-2025 launch. Build a guardrail/"tidy-rules" skill: read_only default, project_ref scoping, audit log + undo, idempotent fixes, human-confirm on destructive writes. Never user_metadata for authz; never ship service_role to the browser.
(Plus two smaller open items: stopping loops in the nesting, and nested drag-and-drop.)
content-visibility approach, or reconsider a canvas grid (losing React-component cells + easy tree).