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 (
CreateItemModalkind=list /POST /api/tasks/projectswithout 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.tsx → PageBody.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(mirrortasks/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
addProjectStatuswith 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 Listpicker gets + New list (mirror+ New project); wirecreateList/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/cellOfto resolve relation props fromrelationsmap (chips), so filterProjectRel 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
Decisions locked / recommended (2026-07-09 chat)¶
| # | 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:
- Top-level board of projects with your statuses and drag.
- Side peek into project: properties + notes page.
- In that project: task board/list you can drag, filtered to the project.
- 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¶
- No second "project container" model.
- Project open path shares PageBody with tasks (one row anatomy).
- Project Status and Task Status are independent option lists.
- Top-level organizes projects; inner views organize tasks.
- Side peek default; full page available.
- Implementable with existing SidePeek, PageBody, collection board, listTasks, status APIs, without templates.
After approval¶
- Confirm D2–D4 (especially app board vs engine embed for Phase 2).
- Say go → Phase 0 then Phase 1 on a clean branch (or continue session-work after stripping template/parent noise).
- 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