CANON UPDATE (2026-06-18) , renamed to CreatorTrack. The product formerly called "The Suite" / "Workspace" is now CreatorTrack, built and previewed at
dev.creatortrack.ai. This database research feeds the database/collection engine, which is the foundation other apps plug into. Build order: reusable UI components first, then the Notion-class page framework + database engine as the foundation, then apps on top as configs/modules; iterate back-and-forth until features work. The data layer formerly "lifeos" is now the core database (core); "workspace" survives only as the internal RLS data-tenant term. Full canon (with tech-stack overview): creator-suite-vision-strategy.
Databases for workspace.justinsforge.com — top 15 first-principles takeaways + integration¶
Source: Justin's uploaded "Databases in Notion, Documentation" (83KB). Goal: recreate Notion databases from first principles on our stack — Postgres spine (forge-data CT 109), Finn (Proxmox host), Console (dev VM), the existing node/RLS/sharing model, and agents. The thesis: a Notion database is a normalized relational dataset; on us it becomes real Postgres tables, which is strictly better because agents get native SQL access.
The 15 things to know (Notion concept → how we build it)¶
-
Every item is a page. A database row is a full Notion page (icon, blocks, sub-pages). → A row IS a node.
app.nodes(type='database_row', parent_id=<db node>). It inherits blocks, icon/cover, sharing, RLS for free. Never build a "row" entity divorced from pages. -
A database is a node, not a blob. type
databaseonapp.nodes, with side tables keyed by(workspace_id, node_id)soapply_node_rlswalls them off exactly like pages. Sharing the database page shares its data automatically (vianode_shared_to_me). -
Properties are typed columns. Title, Text, Number, Select, Multi-select, Date, Checkbox, Person, Files & media, URL/Email/Phone, Status, Unique ID, Created/Edited by/time, Relation, Rollup, Formula. →
app.collection_props(node_id, name, type, config jsonb, position).configholds select options, number format, date format, etc. -
Values are typed cells, queryable. →
app.collection_values(row_id, prop_id, value jsonb)(EAV) — flexible + lets us GIN/expression-index per property for filter/sort. The cell SQL is the single source of truth the AI reads. -
Many views over ONE dataset (table, board, calendar, gallery, list, timeline, chart). →
app.collection_views(node_id, type, name, config jsonb). Views are pure presentation (visible props, layout, group-by). Same rows underneath. Build table view first. -
Filters / sorts / grouping are per-view and compile to SQL. Store as jsonb in
view.config; translate to WHERE / ORDER BY / GROUP BY at query time. This is where we beat Notion — real indexed SQL, not a client-side filter. -
Relations link items across databases (many-to-many). → a
relationprop + a join tableapp.collection_relations(prop_id, from_row, to_row), bidirectional, RLS-scoped. This is the CRM / projects↔tasks↔OKRs power Justin wants across brands. -
Rollups aggregate over related items (count/sum/avg/min/max/etc.). → computed at read time via SQL aggregates over the relation join; config (target relation, target prop, function) lives in
prop.config. No stored value; materialize later only if slow. -
Formulas are computed properties with a function language (
prop()refs, math, date funcs, strings,length/filter/map, ternaryX?Y:Z). → biggest sub-build. Define a small formula AST + a safe evaluator (Python, server-side) — do NOT eval untrusted code. Agents can author formulas. Phase it last; ship non-formula props first. -
Sub-items = hierarchical rows. → free: rows are nodes, so
parent_idalready nests them. Render the table with indentation. -
Full-page vs inline databases. Inline = a
databaseblock that points at a database node (block.props.db_node,block.props.view). The DB node lives once in the tree; the inline block is a pointer (reuses our block model). Drag-to-sidebar = re-parent the node. -
Calc/aggregation row (count, sum at a column's bottom) → SQL aggregates per view, which column + function stored in
view.config. -
Lock + permissions already exist for us. Lock =
node.props.locked(read-only). Sharing/visibility = per-page sharing (node_shares+ RLS, editor vs viewer enforced). A shared database shares its rows/props/views through the same ancestor check. -
Templates + "Build with AI". A DB template = a saved set of props + starter rows; instantiate by copy. "Build with AI" is our differentiator: an agent on Console reads a prompt and emits
collection_props+viewsthrough the same write API the UI uses. -
Data is the asset → agent-native. Because every cell is a real Postgres row (not a proprietary blob), the Finn/Console agents query and mutate databases directly: "all overdue tasks across every brand", auto-fill a Status, generate a view, recompute a rollup. Design ONE write API (the UI + agents share it) and expose read via SQL views. This is the whole reason to rebuild instead of using Notion.
Schema sketch (all RLS-applied via core.apply_node_rls)¶
app.nodes(type='database' | 'database_row') -- the DB and its rows (rows = pages)
app.collection_props(id, workspace_id, node_id, name, type, config jsonb, position)
app.collection_values(workspace_id, row_id, prop_id, value jsonb) -- one cell
app.collection_views(id, workspace_id, node_id, type, name, config jsonb, position)
app.collection_relations(workspace_id, prop_id, from_row, to_row) -- relations
type='database', props.db_node, props.view.
Integration phases (each shippable)¶
- P1 — table view MVP: schema + RLS; create database (node + default Table view + Title
prop); add/edit typed props (text/number/select/multiselect/date/checkbox/url); rows as
nodes; inline cell edit; the
/databaseslash command + full-page databases. - P2 — views + query: board / calendar / gallery / list; filters, sorts, group-by compiled to SQL; the calc/aggregation row; show/hide props per view.
- P3 — relations + rollups: relation prop + join table + bidirectional sync; rollups via SQL aggregates.
- P4 — formulas: AST + safe evaluator; the documented function set.
- P5 — power: inline databases, linked data sources (a view over another DB), templates, and the agent "Build with AI" endpoint.
Stack notes¶
- Lives in the lifeos Postgres (forge-data CT 109 on Finn); served by the Flask app on Console; backed up nightly (now includes uploaded images). RLS + per-page sharing already enforce who sees a database. No new infra needed — it's more tables + routes on the spine.
[Claude Code]