Skip to content

Plan: Projects as rows + views (implementability research)

URL: https://mkdocs.justinsforge.com/memory/plans/creatortrack-projects-as-linked-views-2026-07-09/

Date: 2026-07-09 (research pass 2)
Status: design + implementability (no code until Justin says go)
Templates: out of scope (Justin 2026-07-09: not needed)
Branch note: feat/session-work Parent/status prototypes = experiments only; absorb only what matches this plan.


What we are laying (product)

Keep the Apps approach (Projects app, Tasks app). Under them, behave like Notion:

Layer User experience
A. Top-level Organize projects: board/table of project rows; custom project statuses; drag projects across columns; saved views (filter/sort/group/layout). Scoped to the active workspace (and optional multi-ws visibility prefs).
B. Project surface Click a project → side peek (default). Explicit control → full page (/n/{id}). Properties + page body (same anatomy as task peek).
C. Inside project Task views filtered to that project: list/board/timeline; task statuses separate and customizable; drag tasks.

Not folders. Two collections + a relation + views + peeks.

One-liner:
A project is a database row. You organize projects with views. You open one in a peek (or full page). Its tasks are filtered views of the Tasks database.

Multi-stream work in one workspace (generic product, not brand-specific)

CreatorTrack is for many people. Do not ship a built-in "Brand" field.

Primitive Generic meaning When people use it
Workspace Separate life/org (home vs company, client A firm vs personal) Hard isolation + members
Project Status Pipeline stage of a project Active / On hold / Done by default; users add their own (Ideation, Review, …)
Task Status Stage of a work item To do / Doing / Done; users add their own
List Lightweight bucket for tasks Groceries, Weekly, Inbox-adjacent queues
Tag Cross-cutting labels #urgent, #client-x, multi-label
Custom select (user-named) Whatever they need (Area, Client, Brand, …) Power users via properties; not a hardcoded product concept
Saved views Filter/sort/group presets "Only Active", "My work", etc.

Same-workspace "two streams" (e.g. two brands): user choice, not product hardcoding: tags, a custom select they create, or separate projects. Defaults stay generic.

Project create = task-like properties

When creating/editing a project, support the same kinds of fields tasks already have (collection props on tasks.projects), not a second schema:

Field family On tasks today On projects (target)
Status yes (task pipeline) yes (project pipeline; independent options)
Priority yes yes (add/use Priority prop on projects)
People / Assignee yes yes
Tags yes (multiselect) yes if useful (or skip v1 if Status + Priority enough)
Dates Due / Start Start / Target (already)
Description / page notes + body Description + page body (already)
List link task → list N/A (project is not filed in a list)

Create dialog / peek properties: same Cell editors as row page where possible.

Task composer: Add to List needs “+ New list”

Parity with “+ New project” on the project picker:

  • Add to List popover includes + New list → same create-list modal/path as sidebar (CreateItemModal kind=list / POST /api/tasks/projects without kind=project).
  • After create, auto-select the new list on the draft task.

Composer already has onCreateProject for projects; add onCreateList (or reuse createList) the same way.


Research: what already exists (do not rebuild)

Audited on /home/justinwieb/creatortrack (main) + prior session notes.

1. Collection engine (real)

Piece Location Status
Props / values / relations / views tables db/schema.ts Live
View types: table, board, gallery, list, calendar, chart, timeline, feed, form lib/collection.ts Live
Filter + multi-sort + filter groups lib/collection.ts, viewModel.ts ExtViewConfig Live
Board grouped by select/status; card drag sets cell components/workspace/database/Views.tsx BoardView Live
View CRUD API app/api/collection/view Live
Snapshot for full DB UI GET /api/collection/[id]/snapshot Live
Property option edits updateProp in lib/mutations.ts Live

Seed: every system DB including tasks.projects and tasks.db gets a default Table view via seedCollection (lib/system-apps.ts).

2. Row = page + properties + peek (real for tasks; works for any database_row)

Piece Location Status
database_row full page: title, RowPropertiesHeader, block body PageView.tsxPageBody.tsx Live
Workspace SidePeek ?peek= for docs/rows/DBs components/workspace/SidePeek.tsx Live
Tasks peek reuses PageBody inside apps SidePeek components/apps/tasks/Peek.tsx + apps/shared/SidePeek.tsx Live, intentional pattern
Project rows are already database_row under tasks.projects system-apps + create paths Live

Implication: A project already is a row page. Opening /n/{projectId} should already show properties + body. The gap is the Projects app not using the same peek pattern Tasks uses, and not putting a task view on that surface.

3. Task ↔ project relation (real)

Piece Location Status
ProjectRel task → projects system-apps.ts Live
List link Project (Kind=list) same Live
listTasks({ project }) / app filters lib/tasks.ts, ProjectsClient Live
Progress rollups via relations lib/projects.ts Live

4. Custom project statuses (partial)

Piece Status
Status select on projects DB; default Active / On hold / Done Seeded
addProjectStatus + Tasks sidebar "Add status/view" Exists
Board columns = full Status options in Projects app Weak / prototype only; app boards hard-code task columns To do/Doing/Done
Content pipeline defaults (Ideation…Cancelled) Not seeded
Reorder/rename options UX Incomplete vs Notion

5. What is stubby or wrong for this product

Gap Detail Impact
Relation filter in applyView Relation values live in collection_relations, not collection_values. cellFor / matchesFilter for relation props do not see chips, so a saved view cannot reliably say "ProjectRel contains this project id" inside the engine. Blocks true Notion-style linked Tasks view until fixed.
LinkedView.tsx Title list + search only; comments admit full filter/cells need more API. Cannot host in-project task board yet.
Projects app dual stack Custom ProjectBoardView / list / timeline over listTasks, separate from DatabaseClient. View tabs in user prefs (projects_app.views), not always collection_views. Duplication; harder to get full view customization.
Projects app open Mode switch to full main pane, not task-style side peek. Misses "stay on board, peek in" UX.
Parent Project Only on experimental branch; not canon. Optional later.
Notion audit (2026-07-01) Noted board drag gaps historically; current BoardView does drag for collection engine. App-level boards are separate. Prefer engine board when possible.

6. Templates

Out of scope. No gallery, no template stamp work. Existing task packs can remain as optional later; they are not required for this plan.


Architecture we will implement (concrete)

                    ┌──────────────────────────────────────┐
                    │  tasks.projects collection           │
                    │  Kind=project rows                   │
                    │  Status options = project pipeline   │
                    │  collection_views = top-level views  │
                    └───────────────┬──────────────────────┘
         Projects app shell ────────┤
         (Layer A: host views)      │
              ┌─────────────────────────────────────┐
              │ Board/table of PROJECTS             │
              │ group by Status, drag, filter/sort  │
              └───────────────┬─────────────────────┘
                              │ click
              ┌─────────────────────────────────────┐
              │ Layer B: ProjectPeek (SidePeek)     │
              │ = PageBody (props + page) like Task │
              │ expand → /n/{id} full page          │
              └───────────────┬─────────────────────┘
              ┌─────────────────────────────────────┐
              │ Layer C: Tasks for this project     │
              │ filtered ProjectRel = this.id       │
              │ list/board/timeline; task Status    │
              └─────────────────────────────────────┘
                    ┌─────────┴──────────┐
                    │  tasks.db          │
                    │  ProjectRel → proj │
                    └────────────────────┘

Implementation strategy (the load-bearing choice)

Do not rewrite the collection engine or the Apps model.
Host and connect existing pieces.

Layer Prefer Fallback if blocked
A Top-level projects Embed DatabaseClient on projectsId with filter Kind = project (or app board fed by same Status options + PATCH status) Keep polished app board, but Status columns from live options + drag PATCH (session-work direction)
B Peek / page Clone Tasks Peek pattern: PageBody + apps/shared/SidePeek for project row id Full page only via /n/{id} already works
C Tasks inside Phase C1: existing Projects task boards + listTasks({project}) (works today) with saved layout prefs Phase C2: after relation-filter fix, embed DatabaseClient on tasks DB with locked filter

Why C is two-step: shipping Layer A+B does not depend on fixing relation filters. Layer C already works via app data layer; engine-native linked views are the upgrade path, not the MVP gate.

Status model (implementable)

Collection Prop Default options (seed, then user-owned)
Projects Status Start with content-friendly seed: Ideation, Planning, Scripting, Shooting, Editing, Posted, Completed, Cancelled (replace or sit beside Active/On hold/Done; migration decision below)
Tasks Status Keep To do / Doing / Done; UI to add options; board columns follow options

D-status-migrate: either
- (a) map Active→Planning, Done→Completed, On hold stays, add rest empty, or
- (b) keep Active/On hold/Done and let user add pipeline statuses (safest, no data rewrite).

Recommend (b) for first ship; offer "Apply content pipeline statuses" as a one-click option append.

Views model (implementable)

Surface Persistence Notes
Top-level project views Prefer collection_views on projects DB node Aligns with engine; DatabaseClient already understands them
In-project task layouts Until C2: keep per-scope prefs (projects_app.views / localStorage) After C2: views on tasks DB with locked filter fragment in config
Filter/sort Engine ExtViewConfig for collection UI; app filter bar for C1 Do not invent a third prefs schema

Parent / "nested"

Optional later phase. Self-relation only; tree is presentation. Not required for A/B/C. Do not block the big UX on Parent.


Phased build (ship-shaped, no templates)

Each phase is independently demoable on a worktree slot. Order is dependency order.

Phase 0: Canon (docs only)

  • Write memory/general/reference_creatortrack_projects_rows_and_views.md (this model).
  • MEMORY index line.
  • Explicit: templates out of scope; Parent optional; Apps host engine.
  • Verify: mkdocs link loads.
  • Commit: docs: projects rows+views implementability canon

Phase 1: Project peek = Task peek pattern (Layer B MVP)

Goal: Click project → side peek with properties + page; Open full page always available.

  • Files:
  • New components/apps/projects/ProjectPeek.tsx (mirror tasks/Peek.tsx: fetch /api/node/{id}?include=page, render PageBody, shared SidePeek).
  • Wire ProjectsClient (and Tasks project rows) to open ProjectPeek on click (not only full mode-switch).
  • Header actions: Open full page/n/{id} (and optional “expand” icon like tasks). Full page is first-class, not a fallback after missing peek.
  • Depends on: nothing new in data layer (row page already exists).
  • Verify:
  • Click project on board → peek; board still visible.
  • Edit Status/properties; persists.
  • Page body edits persist.
  • Open full page navigates to full screen row page.
  • Esc/X closes peek.
  • Commit: feat(projects): side peek default; full page open

Phase 2: Top-level project organization (Layer A MVP)

Goal: See all project statuses; drag projects; custom statuses.

2a Status options

  • Ensure Status prop options are editable in Projects UI (reuse collection prop config / PropertyMenu patterns, or extend addProjectStatus with list + reorder).
  • Board columns = options array order (not hard-coded Active/On hold/Done only).
  • Files: lib/projects.ts, API statuses route, Projects overview/status board, Tasks sidebar status menu if shared.
  • Verify: Add "Scripting"; column appears; drag project into it; GET project shows Status=Scripting.

2b Top board

  • Ship project Status board as primary Overview experience (cards remain a view).
  • Prefer: if embedding DatabaseClient is clean with Kind filter, use it; else app board that PATCHes /api/projects/{id} status on drop (already sketched on session-work).
  • Verify: Multi-column pipeline; reorder within column only if position prop exists (optional; skip position v1 if costly).

Decision for 2b implementation path:
- Path Engine: DatabaseClient on projects snapshot + filter Kind=project + board group Status.
- Pro: free table/filter/sort views.
- Con: need Kind filter on snapshot client; Apps chrome integration work.
- Path App: ProjectStatusBoardView + status option API.
- Pro: fits ProjectsClient fast; known HMR surface.
- Con: re-add filter/sort later.

Recommend Path App for Phase 2, Path Engine for Phase 4 once peek is solid (avoid boiling two refactors at once).

  • Commit: feat(projects): custom status board at top level

Phase 3: In-project task views + composer list create (Layer C1)

Goal: Inside peek and full project mode, task views work; task Status customizable; Add to List has + New list.

  • Keep listTasks / ProjectBoardView / List / Timeline path.
  • Peek tabs or split: Page | Tasks (tasks section under props if room).
  • Task Status columns from live options.
  • Composer: Add to List picker gets + New list (mirror + New project); wire createList / onCreateList; auto-select new list on draft.
  • Files: ProjectPeek.tsx, ProjectsClient.tsx, ProjectBoardView.tsx, Composer.tsx, ListView.tsx / TasksClient createList wiring, tasks Status option edit.
  • Verify:
  • In project peek, drag task between statuses; only that project’s tasks.
  • Composer → Add to List → + New list → create → list selected.
  • New list appears in sidebar Lists.
  • Commit: feat(projects,tasks): task views in peek; + new list on composer

Phase 4: Saved customizable views (Layer A depth)

Goal: Multiple named top-level views with filter/sort/layout, not only one board.

  • Path App: extend view prefs to store filter/sort/group + type (upgrade ProjectAppView).
  • Or Path Engine: user manages views on projects collection via DatabaseClient hosted in app.
  • Recommend: start extending existing Projects view tabs + filter bar persistence; migrate to collection_views when embedding engine.
  • Verify: "Pipeline board" vs "Table by target date"; reload keeps both.
  • Commit: feat(projects): multi view config for top-level projects

Phase 5: Engine relation filter + linked tasks view (Layer C2)

Goal: True Notion linked view (optional upgrade, unblocks generic path).

  • Fix applyView / applyViewEx / cellOf to resolve relation props from relations map (chips), so filter ProjectRel contains <id> works.
  • Optionally config.lockFilter / source + current-row substitution for linked views.
  • Upgrade LinkedView or embed DatabaseClient on project page with locked filter.
  • Verify: Collection UI on tasks DB, filter ProjectRel = project X, board matches listTasks count; drag still works.
  • Commit: feat(collections): relation-aware filters; linked project tasks view

Phase 6 (optional): Parent self-relation tree

  • Only after A/B/C feel good.
  • Parent prop + tree indent; no auto-include child tasks.
  • Commit: feat(projects): optional parent relation presentation

Phase 7: Cleanup + register

  • Drop unused session-work code or merge chosen pieces.
  • MEMORY + reference topic.
  • Commit: chore: register projects rows+views

What we are NOT building in this plan

  • Template gallery / Notion template catalog
  • Auto sub-project task rollup into parent
  • New storage tables for projects
  • Replacing Tasks app
  • Full formula engine
  • Comments (still stub on row page)

Risk register (implementation)

Risk Likelihood Mitigation
Peek + task board too cramped Med Tabs in peek: Properties/Page vs Tasks; widen SidePeek storageKey for projects
Dual stack forever (app vs engine) Med Phase 5 is the convergence; Phase 2–4 must not invent a third prefs format without a migration note
Relation filter fix breaks other views Low Only changes cell resolution for type=relation; add unit tests on matchesFilter with chip ids
Status option rename orphans cells Med Rename updates option string in config; cells still store string labels (same as today); document "rename = new label, old cells stay until edited" or migrate strings
Kind=list vs project pollution on engine board Med Always filter Kind=project in Projects app surfaces
Scope creep into Parent/nesting High Parent is Phase 6 only

Phase 2.5: Project properties parity (create + edit)

Goal: Creating/editing projects can set the same kinds of fields as tasks where it makes sense.

  • Ensure projects DB has (or lazily gets): Status, Priority, Assignee (person), optional Tags, Start, Target, Description, Color, Icon.
  • Create dialog + peek RowPropertiesHeader use those props (Cell editors).
  • Files: ensureProjectsProps, NewProjectDialog / CreateItemModal, project peek.
  • Verify: Create project with Priority=High + Assignee; visible on board card and peek.
  • Commit: feat(projects): priority assignee tags on project rows

# Decision Answer
D1 Parent task inclusion No
D2 Default status set Keep Active/On hold/Done; optional content pipeline options
D3 Click project Side peek default; always Open full page
D4 Phase 2 board path App board first; engine embed later
D5 Templates Out
D6 MVP Phases 1–3 (+ 2.5 properties); 4–5 deepen
D7 Multi-stream in one workspace No Brand field. Tags, user-defined props, or projects + views. Defaults stay generic.
D8 Within-ws organization Status + views + lists + tags as today
D9 Project fields like tasks Yes: Priority, people, tags (as project props)
D10 Add to List + New list in task composer list picker

MVP definition (shippable)

Done enough to use daily:

  1. Top-level board of projects with your statuses and drag.
  2. Side peek into project: properties + notes page.
  3. In that project: task board/list you can drag, filtered to the project.
  4. Add custom project and task statuses without code.

Phases 1–3 deliver that. Phases 4–5 make it more Notion-native (saved views, engine linked views).


Success criteria

  1. No second "project container" model.
  2. Project open path shares PageBody with tasks (one row anatomy).
  3. Project Status and Task Status are independent option lists.
  4. Top-level organizes projects; inner views organize tasks.
  5. Side peek default; full page available.
  6. Implementable with existing SidePeek, PageBody, collection board, listTasks, status APIs, without templates.

After approval

  1. Confirm D2–D4 (especially app board vs engine embed for Phase 2).
  2. Say go → Phase 0 then Phase 1 on a clean branch (or continue session-work after stripping template/parent noise).
  3. Each phase ends with the verification steps above on forge_creatortrack_agent_bootstrap.sh.

Plan path: memory/plans/creatortrack-projects-as-linked-views-2026-07-09.md