Add connected pages: hierarchy, backlinks, and live embeds
release gate / check (push) Failing after 7m7s

Implement Notion-like upgrade phase 3: owner-scoped nested pages with cycle rejection and child promotion on delete; Markdown-derived internal links and backlinks; live collection embeds resolved in the content-save transaction. Add migration 008, API/docs, integration coverage, and page/sidebar UI.
This commit is contained in:
Andrew S Erwin
2026-10-19 22:37:21 -05:00
parent b9721ff13f
commit 9db69efe4b
12 changed files with 938 additions and 28 deletions
+7 -1
View File
@@ -85,11 +85,17 @@ Composite PK (`tagId`, `userId`), `createdAt`; both Cascade.
`id`, `label`, `color` (default `#64748b`), `position` int, `key?` (`INBOX`/`TODO`/`IN_PROGRESS`/`DONE` for the default set, null for custom columns), `userId` → User (Cascade). Index (`userId`, `position`).
### 2.12 `Page` (Markdown workspace)
`id`, `title`, `content` (text, default empty), `icon?`, `pinned` bool, `position` int, `version` int (default 1; optimistic-concurrency revision — content saves bump it, stale base versions get 409), `ownerId` → User (Cascade). Index (`ownerId`, `position`).
`id`, `title`, `content` (text, default empty), `icon?`, `pinned` bool, `position` int, `parentId?` → Page (SetNull; nested-page tree, cycles rejected by the API), `version` int (default 1; optimistic-concurrency revision — content saves bump it, stale base versions get 409), `ownerId` → User (Cascade). Indexes (`ownerId`, `position`) and (`parentId`).
### 2.13 `PageBookmark` (link a bookmark onto a page)
Composite PK (`pageId`, `bookmarkId`), `position` int, `createdAt`; both Cascade.
### 2.13b `PageLink` (resolved internal page link)
Composite PK (`sourcePageId`, `targetPageId`), `createdAt`; both Cascade, with a no-self check. Rebuilt from Markdown (`computer:page/<id>`) on every content save and only for pages owned by the author, so backlinks never cross accounts.
### 2.13c `PageEmbed` (live collection view inside a page)
Composite PK (`pageId`, `collectionId`), `viewId?` → CollectionView (SetNull), `position` int, `createdAt`; page and collection Cascade. Rebuilt from `{{collection:<id>|view=<viewId>}}` on every content save; only owned collections are stored. Item reads still go through the collection endpoint, so an embed displays, never publishes.
### 2.14 `SignalSource` (a watcher attached to a bookmark)
`id`, `type` (e.g. `email`), `config` (JSON, default `{}`), `enabled` (default true), `lastCheckedAt?`, `lastStatus?`, `bookmarkId` → Bookmark (Cascade), `ownerId` → User (Cascade).