wiki-viewer 1.14.1 → 1.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/agent/activity/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/move/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/internal/span/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/assets/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/share/[token]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/share/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/set-root/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/refresh/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/workspaces/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/upload/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/app/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/backlinks/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/content/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/download/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/file/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/folder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-branches/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-checkout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-diff/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-file-info/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-history/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/git-pull/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/move/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/new-file/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/outlinks/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/page/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/presence/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/slugs/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/watch/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/package.json +1 -1
- package/bin/postbuild.js +21 -0
- package/package.json +1 -1
- package/.next/standalone/.github/workflows/publish.yml +0 -37
- package/.next/standalone/.handoffs/archive/handoff-20260612-113332.md +0 -98
- package/.next/standalone/.handoffs/archive/handoff-20260612-141032.md +0 -73
- package/.next/standalone/.pi/ownership.json +0 -3
- package/.next/standalone/AGENTS.md +0 -114
- package/.next/standalone/DESIGN.md +0 -521
- package/.next/standalone/README.md +0 -821
- package/.next/standalone/TODO.md +0 -20
- package/.next/standalone/agents/bootstrap-prompt.md +0 -14
- package/.next/standalone/agents/wiki-viewer-skill/SKILL.md +0 -456
- package/.next/standalone/bin/postbuild.js +0 -78
- package/.next/standalone/bin/wiki-viewer.js +0 -922
- package/.next/standalone/certificates/localhost-key.pem +0 -28
- package/.next/standalone/certificates/localhost.pem +0 -24
- package/.next/standalone/docs/agent-collab-plan.md +0 -1615
- package/.next/standalone/docs/agent-collab-v2-plan.md +0 -771
- package/.next/standalone/docs/agent-fs-plan.md +0 -232
- package/.next/standalone/docs/editorial-skin-spec.md +0 -242
- package/.next/standalone/docs/file-vs-collab-authority.md +0 -124
- package/.next/standalone/docs/mobile-readiness-spec.md +0 -229
- package/.next/standalone/docs/workspacing-plan.md +0 -364
- package/.next/standalone/packages/wiki-viewer-mcp/README.md +0 -195
- package/.next/standalone/packages/wiki-viewer-mcp/bench-e2e.mjs +0 -166
- package/.next/standalone/packages/wiki-viewer-mcp/bench-latency.mjs +0 -87
- package/.next/standalone/packages/wiki-viewer-mcp/bench-payload.mjs +0 -45
- package/.next/standalone/packages/wiki-viewer-mcp/dist/http-client.js +0 -331
- package/.next/standalone/packages/wiki-viewer-mcp/dist/index.js +0 -531
- package/.next/standalone/packages/wiki-viewer-mcp/dist/register.js +0 -94
- package/.next/standalone/packages/wiki-viewer-mcp/dist/state-cache.js +0 -28
- package/.next/standalone/packages/wiki-viewer-mcp/package-lock.json +0 -1716
- package/.next/standalone/packages/wiki-viewer-mcp/package.json +0 -41
- package/.next/standalone/packages/wiki-viewer-mcp/src/http-client.ts +0 -459
- package/.next/standalone/packages/wiki-viewer-mcp/src/index.ts +0 -638
- package/.next/standalone/packages/wiki-viewer-mcp/src/register.ts +0 -144
- package/.next/standalone/packages/wiki-viewer-mcp/src/state-cache.ts +0 -55
- package/.next/standalone/packages/wiki-viewer-mcp/tsconfig.json +0 -19
- package/.next/standalone/pnpm-lock.yaml +0 -6697
- package/.next/standalone/pnpm-workspace.yaml +0 -8
- package/.next/standalone/postcss.config.mjs +0 -2
- package/.next/standalone/src/app/api/agent/activity/route.ts +0 -59
- package/.next/standalone/src/app/api/agent/admin/agents/[agentId]/revoke/route.ts +0 -40
- package/.next/standalone/src/app/api/agent/admin/agents/route.ts +0 -33
- package/.next/standalone/src/app/api/agent/admin/registrations/[regId]/approve/route.ts +0 -83
- package/.next/standalone/src/app/api/agent/admin/registrations/[regId]/deny/route.ts +0 -36
- package/.next/standalone/src/app/api/agent/admin/registrations/route.ts +0 -27
- package/.next/standalone/src/app/api/agent/events/[...path]/route.ts +0 -148
- package/.next/standalone/src/app/api/agent/files/[...path]/route.ts +0 -219
- package/.next/standalone/src/app/api/agent/fs/file/[...path]/route.ts +0 -446
- package/.next/standalone/src/app/api/agent/fs/ls/[[...path]]/route.ts +0 -152
- package/.next/standalone/src/app/api/agent/fs/move/route.ts +0 -130
- package/.next/standalone/src/app/api/agent/fs/search/route.ts +0 -272
- package/.next/standalone/src/app/api/agent/internal/span/route.ts +0 -121
- package/.next/standalone/src/app/api/agent/register/[regId]/route.ts +0 -50
- package/.next/standalone/src/app/api/agent/register/route.ts +0 -118
- package/.next/standalone/src/app/api/agent/settings/route.ts +0 -34
- package/.next/standalone/src/app/api/agent/settings/token/regenerate/route.ts +0 -20
- package/.next/standalone/src/app/api/agent/sidecar/[...path]/route.ts +0 -53
- package/.next/standalone/src/app/api/agents/install/route.ts +0 -143
- package/.next/standalone/src/app/api/agents/skill/route.ts +0 -20
- package/.next/standalone/src/app/api/agents/skill.tar.gz/route.ts +0 -87
- package/.next/standalone/src/app/api/app-proxy/[...path]/route.ts +0 -247
- package/.next/standalone/src/app/api/assets/[...path]/route.ts +0 -75
- package/.next/standalone/src/app/api/auth/[...all]/route.ts +0 -7
- package/.next/standalone/src/app/api/owner/init/route.ts +0 -14
- package/.next/standalone/src/app/api/share/[token]/route.ts +0 -269
- package/.next/standalone/src/app/api/share/route.ts +0 -127
- package/.next/standalone/src/app/api/system/admins/route.ts +0 -103
- package/.next/standalone/src/app/api/system/auth-config/route.ts +0 -22
- package/.next/standalone/src/app/api/system/auth-settings/route.ts +0 -86
- package/.next/standalone/src/app/api/system/browse/route.ts +0 -82
- package/.next/standalone/src/app/api/system/clear-root/route.ts +0 -16
- package/.next/standalone/src/app/api/system/config/route.ts +0 -14
- package/.next/standalone/src/app/api/system/pins/route.ts +0 -38
- package/.next/standalone/src/app/api/system/reveal/route.ts +0 -36
- package/.next/standalone/src/app/api/system/root-status/route.ts +0 -13
- package/.next/standalone/src/app/api/system/set-root/route.ts +0 -53
- package/.next/standalone/src/app/api/system/users/route.ts +0 -120
- package/.next/standalone/src/app/api/system/workspaces/[id]/open/route.ts +0 -34
- package/.next/standalone/src/app/api/system/workspaces/[id]/refresh/route.ts +0 -32
- package/.next/standalone/src/app/api/system/workspaces/[id]/route.ts +0 -78
- package/.next/standalone/src/app/api/system/workspaces/route.ts +0 -128
- package/.next/standalone/src/app/api/upload/[...path]/route.ts +0 -85
- package/.next/standalone/src/app/api/wiki/app/route.ts +0 -60
- package/.next/standalone/src/app/api/wiki/backlinks/route.ts +0 -29
- package/.next/standalone/src/app/api/wiki/content/route.ts +0 -159
- package/.next/standalone/src/app/api/wiki/download/route.ts +0 -100
- package/.next/standalone/src/app/api/wiki/file/route.ts +0 -57
- package/.next/standalone/src/app/api/wiki/folder/route.ts +0 -34
- package/.next/standalone/src/app/api/wiki/git-branches/route.ts +0 -32
- package/.next/standalone/src/app/api/wiki/git-checkout/route.ts +0 -45
- package/.next/standalone/src/app/api/wiki/git-diff/route.ts +0 -33
- package/.next/standalone/src/app/api/wiki/git-file-info/route.ts +0 -27
- package/.next/standalone/src/app/api/wiki/git-history/route.ts +0 -27
- package/.next/standalone/src/app/api/wiki/git-pull/route.ts +0 -48
- package/.next/standalone/src/app/api/wiki/move/route.ts +0 -61
- package/.next/standalone/src/app/api/wiki/new-file/route.ts +0 -56
- package/.next/standalone/src/app/api/wiki/outlinks/route.ts +0 -76
- package/.next/standalone/src/app/api/wiki/page/route.ts +0 -92
- package/.next/standalone/src/app/api/wiki/presence/route.ts +0 -56
- package/.next/standalone/src/app/api/wiki/route.ts +0 -116
- package/.next/standalone/src/app/api/wiki/search/route.ts +0 -44
- package/.next/standalone/src/app/api/wiki/slugs/route.ts +0 -54
- package/.next/standalone/src/app/api/wiki/upload/route.ts +0 -95
- package/.next/standalone/src/app/api/wiki/watch/route.ts +0 -67
- package/.next/standalone/src/app/globals.css +0 -1249
- package/.next/standalone/src/app/icon.svg +0 -11
- package/.next/standalone/src/app/layout.tsx +0 -78
- package/.next/standalone/src/app/manifest.ts +0 -30
- package/.next/standalone/src/app/page.tsx +0 -3260
- package/.next/standalone/src/app/s/[token]/page.tsx +0 -357
- package/.next/standalone/src/app/signin/page.tsx +0 -16
- package/.next/standalone/src/app/signin/signin-form.tsx +0 -238
- package/.next/standalone/src/components/ai-panel/activity-row.tsx +0 -284
- package/.next/standalone/src/components/ai-panel/ai-panel.tsx +0 -311
- package/.next/standalone/src/components/ai-panel/token-section.tsx +0 -313
- package/.next/standalone/src/components/auth-settings-sheet.tsx +0 -474
- package/.next/standalone/src/components/confirm-dialog.tsx +0 -56
- package/.next/standalone/src/components/dir-picker.tsx +0 -606
- package/.next/standalone/src/components/editor/backlinks-panel.tsx +0 -139
- package/.next/standalone/src/components/editor/bubble-menu.tsx +0 -346
- package/.next/standalone/src/components/editor/callout-extension.ts +0 -36
- package/.next/standalone/src/components/editor/comment-pip.tsx +0 -56
- package/.next/standalone/src/components/editor/comment-thread.tsx +0 -254
- package/.next/standalone/src/components/editor/csv-viewer.tsx +0 -379
- package/.next/standalone/src/components/editor/document-outline.tsx +0 -230
- package/.next/standalone/src/components/editor/editor-toolbar.tsx +0 -564
- package/.next/standalone/src/components/editor/editor.tsx +0 -1327
- package/.next/standalone/src/components/editor/extensions/drag-handle.ts +0 -325
- package/.next/standalone/src/components/editor/extensions/heading-anchors.ts +0 -55
- package/.next/standalone/src/components/editor/extensions/proof-span.ts +0 -60
- package/.next/standalone/src/components/editor/extensions/resizable-image.tsx +0 -159
- package/.next/standalone/src/components/editor/extensions.ts +0 -150
- package/.next/standalone/src/components/editor/file-fallback-viewer.tsx +0 -75
- package/.next/standalone/src/components/editor/folder-index.tsx +0 -236
- package/.next/standalone/src/components/editor/google-doc-viewer.tsx +0 -73
- package/.next/standalone/src/components/editor/image-viewer.tsx +0 -57
- package/.next/standalone/src/components/editor/large-file-gate.tsx +0 -108
- package/.next/standalone/src/components/editor/link-popover.tsx +0 -111
- package/.next/standalone/src/components/editor/media-popover.tsx +0 -212
- package/.next/standalone/src/components/editor/media-viewer.tsx +0 -70
- package/.next/standalone/src/components/editor/mermaid-viewer.tsx +0 -286
- package/.next/standalone/src/components/editor/node-app-viewer.tsx +0 -328
- package/.next/standalone/src/components/editor/notebook-viewer.tsx +0 -371
- package/.next/standalone/src/components/editor/office/docx-viewer.tsx +0 -93
- package/.next/standalone/src/components/editor/office/office-chrome.tsx +0 -83
- package/.next/standalone/src/components/editor/office/pptx-viewer.tsx +0 -104
- package/.next/standalone/src/components/editor/office/xlsx-viewer.tsx +0 -192
- package/.next/standalone/src/components/editor/pdf-viewer.tsx +0 -32
- package/.next/standalone/src/components/editor/proof-span-popover.tsx +0 -162
- package/.next/standalone/src/components/editor/slash-commands.tsx +0 -629
- package/.next/standalone/src/components/editor/source-viewer.tsx +0 -289
- package/.next/standalone/src/components/editor/suggest-edit-popover.tsx +0 -229
- package/.next/standalone/src/components/editor/suggestion-card.tsx +0 -206
- package/.next/standalone/src/components/editor/table-menu.tsx +0 -232
- package/.next/standalone/src/components/editor/view-mode-comment-button.tsx +0 -77
- package/.next/standalone/src/components/editor/website-viewer.tsx +0 -75
- package/.next/standalone/src/components/editor/wiki-link-create-dialog.tsx +0 -189
- package/.next/standalone/src/components/editor/wiki-link-decorator.ts +0 -79
- package/.next/standalone/src/components/editor/wiki-link-extension.ts +0 -120
- package/.next/standalone/src/components/editor/wiki-link-picker.tsx +0 -250
- package/.next/standalone/src/components/layout/viewer-toolbar.tsx +0 -54
- package/.next/standalone/src/components/search/search-command-dialog.tsx +0 -246
- package/.next/standalone/src/components/search/sidebar-search-box.tsx +0 -121
- package/.next/standalone/src/components/search/snippet-text.tsx +0 -48
- package/.next/standalone/src/components/share-dialog.tsx +0 -307
- package/.next/standalone/src/components/theme-provider.tsx +0 -16
- package/.next/standalone/src/components/theme-toggle.tsx +0 -24
- package/.next/standalone/src/components/ui/badge.tsx +0 -35
- package/.next/standalone/src/components/ui/button.tsx +0 -56
- package/.next/standalone/src/components/ui/card.tsx +0 -63
- package/.next/standalone/src/components/ui/collapsible.tsx +0 -9
- package/.next/standalone/src/components/ui/command.tsx +0 -151
- package/.next/standalone/src/components/ui/context-menu.tsx +0 -191
- package/.next/standalone/src/components/ui/dialog.tsx +0 -119
- package/.next/standalone/src/components/ui/dropdown-menu.tsx +0 -194
- package/.next/standalone/src/components/ui/input.tsx +0 -22
- package/.next/standalone/src/components/ui/label.tsx +0 -25
- package/.next/standalone/src/components/ui/popover.tsx +0 -33
- package/.next/standalone/src/components/ui/scroll-area.tsx +0 -47
- package/.next/standalone/src/components/ui/select.tsx +0 -156
- package/.next/standalone/src/components/ui/separator.tsx +0 -30
- package/.next/standalone/src/components/ui/sheet.tsx +0 -140
- package/.next/standalone/src/components/ui/skeleton.tsx +0 -15
- package/.next/standalone/src/components/ui/switch.tsx +0 -28
- package/.next/standalone/src/components/ui/tabs.tsx +0 -54
- package/.next/standalone/src/components/ui/textarea.tsx +0 -21
- package/.next/standalone/src/components/ui/tip.tsx +0 -23
- package/.next/standalone/src/components/ui/toggle-group.tsx +0 -41
- package/.next/standalone/src/components/ui/tooltip.tsx +0 -27
- package/.next/standalone/src/components/view-width-toggle.tsx +0 -70
- package/.next/standalone/src/components/wiki/frontmatter-header.tsx +0 -164
- package/.next/standalone/src/hooks/use-is-mobile.ts +0 -14
- package/.next/standalone/src/lib/app-runner.ts +0 -272
- package/.next/standalone/src/lib/auth/admin.ts +0 -120
- package/.next/standalone/src/lib/auth/allowlist.ts +0 -50
- package/.next/standalone/src/lib/auth/client.ts +0 -4
- package/.next/standalone/src/lib/auth/csrf.ts +0 -68
- package/.next/standalone/src/lib/auth/server.ts +0 -215
- package/.next/standalone/src/lib/cabinets/tree.ts +0 -75
- package/.next/standalone/src/lib/config.ts +0 -114
- package/.next/standalone/src/lib/embeds/detect.ts +0 -142
- package/.next/standalone/src/lib/git-secrets.ts +0 -65
- package/.next/standalone/src/lib/git.ts +0 -503
- package/.next/standalone/src/lib/google/detect.ts +0 -107
- package/.next/standalone/src/lib/markdown/parse-frontmatter.ts +0 -104
- package/.next/standalone/src/lib/markdown/remark-wikilinks.ts +0 -100
- package/.next/standalone/src/lib/markdown/sanitize-schema.ts +0 -106
- package/.next/standalone/src/lib/markdown/to-html.ts +0 -282
- package/.next/standalone/src/lib/markdown/to-markdown.ts +0 -252
- package/.next/standalone/src/lib/proof/activity-shared.ts +0 -31
- package/.next/standalone/src/lib/proof/activity.ts +0 -74
- package/.next/standalone/src/lib/proof/audit.ts +0 -88
- package/.next/standalone/src/lib/proof/auth.ts +0 -143
- package/.next/standalone/src/lib/proof/block-refs.ts +0 -133
- package/.next/standalone/src/lib/proof/blocks.ts +0 -73
- package/.next/standalone/src/lib/proof/client-auth.ts +0 -23
- package/.next/standalone/src/lib/proof/collab-state.ts +0 -81
- package/.next/standalone/src/lib/proof/event-bus.ts +0 -47
- package/.next/standalone/src/lib/proof/file-lock.ts +0 -43
- package/.next/standalone/src/lib/proof/glob.ts +0 -51
- package/.next/standalone/src/lib/proof/idempotency.ts +0 -37
- package/.next/standalone/src/lib/proof/lease.ts +0 -117
- package/.next/standalone/src/lib/proof/mutex.ts +0 -36
- package/.next/standalone/src/lib/proof/ops-applier.ts +0 -727
- package/.next/standalone/src/lib/proof/owner-auth.ts +0 -2
- package/.next/standalone/src/lib/proof/pending.ts +0 -97
- package/.next/standalone/src/lib/proof/proof-span.ts +0 -141
- package/.next/standalone/src/lib/proof/rate-limit.ts +0 -53
- package/.next/standalone/src/lib/proof/raw-fs.ts +0 -197
- package/.next/standalone/src/lib/proof/register-rate-limit.ts +0 -53
- package/.next/standalone/src/lib/proof/registry.ts +0 -195
- package/.next/standalone/src/lib/proof/sidecar.ts +0 -103
- package/.next/standalone/src/lib/proof/suggest-capture.ts +0 -69
- package/.next/standalone/src/lib/proof/types.ts +0 -182
- package/.next/standalone/src/lib/proof-config.ts +0 -14
- package/.next/standalone/src/lib/root-dir.ts +0 -35
- package/.next/standalone/src/lib/search/indexable-exts.ts +0 -25
- package/.next/standalone/src/lib/search/indexer.ts +0 -634
- package/.next/standalone/src/lib/search/sanitize.ts +0 -35
- package/.next/standalone/src/lib/search/search-db.ts +0 -57
- package/.next/standalone/src/lib/search/watcher-pool.ts +0 -79
- package/.next/standalone/src/lib/shared-docs/db.ts +0 -177
- package/.next/standalone/src/lib/toast.ts +0 -9
- package/.next/standalone/src/lib/utils.ts +0 -6
- package/.next/standalone/src/lib/wiki-helpers.ts +0 -36
- package/.next/standalone/src/lib/workspace-client.ts +0 -82
- package/.next/standalone/src/lib/workspace-context.ts +0 -173
- package/.next/standalone/src/lib/workspaces.ts +0 -400
- package/.next/standalone/src/middleware.ts +0 -44
- package/.next/standalone/src/stores/ai-panel-store.ts +0 -79
- package/.next/standalone/src/stores/editor-store.ts +0 -439
- package/.next/standalone/src/stores/pin-store.ts +0 -56
- package/.next/standalone/src/stores/proof-store.ts +0 -124
- package/.next/standalone/src/stores/recent-store.ts +0 -51
- package/.next/standalone/src/stores/search-store.ts +0 -104
- package/.next/standalone/src/stores/show-hidden-store.ts +0 -26
- package/.next/standalone/src/stores/sidebar-width-store.ts +0 -39
- package/.next/standalone/src/stores/skin-store.ts +0 -42
- package/.next/standalone/src/stores/tree-store.ts +0 -52
- package/.next/standalone/src/stores/view-width-store.ts +0 -101
- package/.next/standalone/src/stores/wiki-slugs-store.ts +0 -75
- package/.next/standalone/src/tests/proof/activity-aggregator.test.ts +0 -146
- package/.next/standalone/src/tests/proof/admin-create-user.test.ts +0 -43
- package/.next/standalone/src/tests/proof/agent-fs.test.ts +0 -973
- package/.next/standalone/src/tests/proof/agent-ownership.test.ts +0 -140
- package/.next/standalone/src/tests/proof/agent-workspace-scope.test.ts +0 -134
- package/.next/standalone/src/tests/proof/agents-install.test.ts +0 -110
- package/.next/standalone/src/tests/proof/bench-write.ts +0 -179
- package/.next/standalone/src/tests/proof/better-auth.test.ts +0 -68
- package/.next/standalone/src/tests/proof/block-refs.test.ts +0 -108
- package/.next/standalone/src/tests/proof/blocks.test.ts +0 -92
- package/.next/standalone/src/tests/proof/collab-state.test.ts +0 -501
- package/.next/standalone/src/tests/proof/comments-ops.test.ts +0 -177
- package/.next/standalone/src/tests/proof/editor-roundtrip.test.ts +0 -85
- package/.next/standalone/src/tests/proof/external-edit.test.ts +0 -58
- package/.next/standalone/src/tests/proof/file-lock.test.ts +0 -80
- package/.next/standalone/src/tests/proof/git-branches-checkout.test.ts +0 -136
- package/.next/standalone/src/tests/proof/git-read-features.test.ts +0 -473
- package/.next/standalone/src/tests/proof/git-secrets.test.ts +0 -138
- package/.next/standalone/src/tests/proof/git-sparse.test.ts +0 -257
- package/.next/standalone/src/tests/proof/git-workspace.test.ts +0 -135
- package/.next/standalone/src/tests/proof/git.test.ts +0 -243
- package/.next/standalone/src/tests/proof/glob.test.ts +0 -82
- package/.next/standalone/src/tests/proof/helpers/auth-session.ts +0 -27
- package/.next/standalone/src/tests/proof/markdown-to-html.test.ts +0 -46
- package/.next/standalone/src/tests/proof/ops-applier.test.ts +0 -368
- package/.next/standalone/src/tests/proof/owner-init.test.ts +0 -2
- package/.next/standalone/src/tests/proof/parse-frontmatter.test.ts +0 -60
- package/.next/standalone/src/tests/proof/preload.ts +0 -21
- package/.next/standalone/src/tests/proof/proof-span.test.ts +0 -98
- package/.next/standalone/src/tests/proof/rate-limit.test.ts +0 -54
- package/.next/standalone/src/tests/proof/readonly-enforcement.test.ts +0 -161
- package/.next/standalone/src/tests/proof/reconcile-sidecar.test.ts +0 -195
- package/.next/standalone/src/tests/proof/registration-flow.test.ts +0 -330
- package/.next/standalone/src/tests/proof/registry.test.ts +0 -169
- package/.next/standalone/src/tests/proof/routes.test.ts +0 -516
- package/.next/standalone/src/tests/proof/search-index.test.ts +0 -376
- package/.next/standalone/src/tests/proof/sidecar-lifecycle.test.ts +0 -177
- package/.next/standalone/src/tests/proof/sidecar-trim.test.ts +0 -58
- package/.next/standalone/src/tests/proof/suggestion-ops.test.ts +0 -280
- package/.next/standalone/src/tests/proof/wiki-content-put.test.ts +0 -140
- package/.next/standalone/src/tests/proof/wiki-routes-auth.test.ts +0 -208
- package/.next/standalone/src/tests/proof/workspace-isolation.test.ts +0 -87
- package/.next/standalone/src/tests/proof/workspaces-api.test.ts +0 -227
- package/.next/standalone/src/types/index.ts +0 -47
- package/.next/standalone/tailwind.config.ts +0 -191
- package/.next/standalone/tsconfig.json +0 -41
- package/.next/standalone/tsconfig.tsbuildinfo +0 -1
- /package/.next/standalone/.next/static/{WMOTgfV82apmdovxzgbMx → rRyvFORvJoc5OWbCR2A_P}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{WMOTgfV82apmdovxzgbMx → rRyvFORvJoc5OWbCR2A_P}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{WMOTgfV82apmdovxzgbMx → rRyvFORvJoc5OWbCR2A_P}/_ssgManifest.js +0 -0
|
@@ -1,771 +0,0 @@
|
|
|
1
|
-
# Agent Collaboration Protocol — v2 Implementation Plan
|
|
2
|
-
|
|
3
|
-
**Status:** Draft for review. Do not implement until user signs off.
|
|
4
|
-
**Target:** Pivot wiki-viewer from single-user local-first to multi-user remote-server with realtime CRDT collab.
|
|
5
|
-
**Audience:** Implementing agent, with no prior context, who has read `docs/agent-collab-plan.md` (v1) for reference.
|
|
6
|
-
**Relationship to v1:** v1 stays as historical reference. v2 supersedes §0 prohibition on Yjs and §0 single-user constraint. The HTTP agent surface, op vocabulary, sidecar shape, and provenance marks survive in modified form. The file-on-disk-is-truth rule is loosened (see §1.3).
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## 0. What changed since v1
|
|
11
|
-
|
|
12
|
-
| v1 axiom | v2 status |
|
|
13
|
-
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
14
|
-
| Single-user, local-first | **REVERSED** — multi-user, remote-server first |
|
|
15
|
-
| File on disk is source of truth, always | **REFINED** — Y.Doc is source of truth during live session; markdown is durable export + cold-start seed |
|
|
16
|
-
| No Yjs, no WebSockets, no realtime CRDT | **REVERSED** — Yjs + Hocuspocus realtime, y-prosemirror binding |
|
|
17
|
-
| Bearer token + owner cookie | **REPLACED** — OAuth (GitHub primary, Google secondary), session in DB, per-user identity |
|
|
18
|
-
| Single ROOT_DIR | **REPLACED** — workspace entity with member ACL |
|
|
19
|
-
| Agent registry in `~/.wiki-viewer/agents.json` | **REPLACED** — per-workspace agent rows in DB |
|
|
20
|
-
| In-process mutex serializes mutations | **KEPT for cold path** — Y.Doc handles concurrent edits, server applies agent ops as Y transactions; mutex still guards persistence-flush and external-edit reconciliation |
|
|
21
|
-
| `PUT /api/wiki/content` bypasses ops-applier | **FIXED** — editor never writes raw markdown; saves go through Y.Doc |
|
|
22
|
-
| Pending registrations in-memory | **REPLACED** — DB-backed |
|
|
23
|
-
| No HTTPS / no CSRF | **FIXED** — enforce HTTPS in prod, CSRF on owner-cookie POSTs (now session-cookie POSTs), Origin check on WS upgrade |
|
|
24
|
-
| No presence, no live cursors | **ADDED** — Yjs awareness |
|
|
25
|
-
|
|
26
|
-
What stays:
|
|
27
|
-
|
|
28
|
-
- TipTap editor (no Milkdown swap)
|
|
29
|
-
- proof-span mark (now lives inside Y.XmlFragment)
|
|
30
|
-
- Block-ref + idempotency semantics on the HTTP surface (agents see no API break for their core ops, only auth/scope)
|
|
31
|
-
- Sidecar concept (comments, suggestions, events, blockProvenance) — migrates from JSON files to DB rows
|
|
32
|
-
- 160-test suite via `node --test` / `tsx --test` — augmented, not replaced
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## 1. Architecture sketch
|
|
37
|
-
|
|
38
|
-
### 1.1 Block diagram (post-pivot)
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
┌────────────────────────────────────────────────────────────────────────────┐
|
|
42
|
-
│ Browser (User) │
|
|
43
|
-
│ │
|
|
44
|
-
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
|
45
|
-
│ │ Next.js app │ │
|
|
46
|
-
│ │ ┌──────────────────────┐ ┌──────────────────────────────────┐ │ │
|
|
47
|
-
│ │ │ TipTap editor │◄──►│ y-prosemirror binding │ │ │
|
|
48
|
-
│ │ │ + ProofSpan mark │ │ + @tiptap/extension-collab │ │ │
|
|
49
|
-
│ │ │ + suggestion cards │ │ + Y.Doc (per file, in-memory) │ │ │
|
|
50
|
-
│ │ │ + comment pips │ └────────────────┬─────────────────┘ │ │
|
|
51
|
-
│ │ │ + presence cursors │ │ │ │
|
|
52
|
-
│ │ └──────────────────────┘ │ │ │
|
|
53
|
-
│ │ ┌──────────────────────┐ │ │ │
|
|
54
|
-
│ │ │ Sidecar REST client │◄────────────────────┼───── y-protocol │ │
|
|
55
|
-
│ │ │ (comments, sugg., │ │ messages │ │
|
|
56
|
-
│ │ │ events polling) │ │ │ │
|
|
57
|
-
│ │ └──────────────────────┘ │ │ │
|
|
58
|
-
│ └────────────────────────────────────────────────┼───────────────────┘ │
|
|
59
|
-
│ HTTPS │ session cookie WSS │ session cookie │
|
|
60
|
-
└──────────────────────────┼───────────────────────────┼───────────────────┘
|
|
61
|
-
│ │
|
|
62
|
-
┌──────────────────────────▼───────────────────────────▼───────────────────┐
|
|
63
|
-
│ Node process (custom server.ts) │
|
|
64
|
-
│ │
|
|
65
|
-
│ ┌─────────────────────────────────┐ ┌───────────────────────────────┐ │
|
|
66
|
-
│ │ Next.js HTTP handler │ │ Hocuspocus (mounted on /yjs) │ │
|
|
67
|
-
│ │ - /api/auth/* (OAuth) │ │ onAuthenticate → session │ │
|
|
68
|
-
│ │ - /api/workspaces/* │ │ onLoadDocument → seed Y.Doc │ │
|
|
69
|
-
│ │ - /api/agent/* (HTTP bridge) │ │ from markdown OR DB blob │ │
|
|
70
|
-
│ │ - /api/wiki/* (read-only file │ │ onChange → debounced flush │ │
|
|
71
|
-
│ │ tree, content GET) │ │ → markdown + commit to │ │
|
|
72
|
-
│ │ - /api/sidecar/* │ │ workspace store + DB blob │ │
|
|
73
|
-
│ └────────────────┬────────────────┘ │ onDisconnect → presence rm │ │
|
|
74
|
-
│ │ └────────────┬──────────────────┘ │
|
|
75
|
-
│ │ │ │
|
|
76
|
-
│ ┌────────────────▼─────────────────────────────────▼──────────────────┐ │
|
|
77
|
-
│ │ Server-side proof core (refactored) │ │
|
|
78
|
-
│ │ - ops-applier-yjs.ts (translates Op → Y.Doc transaction) │ │
|
|
79
|
-
│ │ - block-refs.ts (now reads block list from Y.Doc snapshot) │ │
|
|
80
|
-
│ │ - sidecar-store.ts (DB-backed; legacy JSON read for migration) │ │
|
|
81
|
-
│ │ - mutex.ts (per-doc serial for flush + agent ops batch)│ │
|
|
82
|
-
│ └─────────────┬──────────────────────────────┬────────────────────────┘ │
|
|
83
|
-
│ │ │ │
|
|
84
|
-
│ ┌─────────────▼──────────┐ ┌──────────────▼──────────────────────┐ │
|
|
85
|
-
│ │ Postgres (or SQLite) │ │ File-system store (per workspace) │ │
|
|
86
|
-
│ │ users │ │ <workspace_root>/<path>.md │ │
|
|
87
|
-
│ │ sessions │ │ (debounced flush from Y.Doc, │ │
|
|
88
|
-
│ │ workspaces │ │ chokidar watches for external) │ │
|
|
89
|
-
│ │ workspace_members │ └──────────────────────────────────────┘ │
|
|
90
|
-
│ │ agent_registrations │ │
|
|
91
|
-
│ │ agent_records │ │
|
|
92
|
-
│ │ comments │ │
|
|
93
|
-
│ │ suggestions │ │
|
|
94
|
-
│ │ events │ │
|
|
95
|
-
│ │ ydoc_snapshots │ ← optional: persisted Yjs state vectors │
|
|
96
|
-
│ └────────────────────────┘ │
|
|
97
|
-
└──────────────────────────────────────────────────────────────────────────┘
|
|
98
|
-
▲
|
|
99
|
-
│ HTTPS (Authorization: Bearer <agent-token>)
|
|
100
|
-
│
|
|
101
|
-
┌──────────┴──────────┐
|
|
102
|
-
│ Remote AI agent │ (Claude, Cursor, ChatGPT desktop, etc.)
|
|
103
|
-
│ uses /api/agent/* │ Sees blocks + revision, NOT raw Yjs protocol.
|
|
104
|
-
└─────────────────────┘
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### 1.2 Yjs provider choice
|
|
108
|
-
|
|
109
|
-
Pick: **Hocuspocus**.
|
|
110
|
-
|
|
111
|
-
| Option | Why not / why yes |
|
|
112
|
-
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
113
|
-
| Raw `y-protocols` + `ws` | Too low-level. Need to reimplement onAuthenticate, onLoadDocument, onStore, debounced persistence, awareness routing, hooks. Reinventing Hocuspocus. |
|
|
114
|
-
| `y-websocket` server | Good enough for happy path, but auth hook is `gc`-only, no built-in onLoadDocument/onStore hooks tuned for our flush model, no extension API. Adopted then abandoned by many teams. |
|
|
115
|
-
| **Hocuspocus** | Built for this. First-class hooks: `onAuthenticate`, `onConnect`, `onLoadDocument`, `onChange`, `onStoreDocument`, `onDisconnect`. Cleanly mountable inside a custom Next.js server. MIT, actively maintained. Extension ecosystem (`@hocuspocus/extension-database`, `@hocuspocus/extension-throttle`, `@hocuspocus/extension-logger`) overlaps with our needs. Cost: ships a chunky server bundle and ties our persistence to its hook lifecycle, but the lifecycle matches our model. |
|
|
116
|
-
|
|
117
|
-
Trade-off accepted: Hocuspocus binds us to a specific Yjs server vendor. Escape hatch: ops-applier-yjs and persistence sink live in our code; Hocuspocus is a thin transport layer. We can swap to a custom y-protocols server later without rewriting business logic.
|
|
118
|
-
|
|
119
|
-
### 1.3 Where does the markdown file fit?
|
|
120
|
-
|
|
121
|
-
**Rule (explicit):**
|
|
122
|
-
|
|
123
|
-
1. **Y.Doc is the live truth** during any active editing session for a given file. All edits (human, agent) land in the Y.Doc first.
|
|
124
|
-
2. **Markdown on disk is durable export + cold seed.** When a Y.Doc has no awareness connections for `IDLE_FLUSH_MS` (default 30s), or on graceful shutdown, server serializes Y.Doc to markdown and writes the file. File commit is also debounced during the live session (`LIVE_FLUSH_MS`, default 5s) so external tools see fresh content.
|
|
125
|
-
3. **DB stores the canonical Yjs state vector** + a debounced markdown snapshot blob in `ydoc_snapshots`. The file system markdown is regenerated from DB on every flush. The on-disk file is for: git, chokidar consumers, command-line tools, downloads.
|
|
126
|
-
4. **On document open with no live session:** if `ydoc_snapshots` has rows, load latest snapshot's Y.Doc state and continue. If not, parse markdown from disk → seed Y.Doc → write snapshot. (This is the migration path from v1 sidecars.)
|
|
127
|
-
5. **External edit (vim) while doc is live:** chokidar fires. Server's `onExternalFileChange` handler computes diff between current Y.Doc serialization and new file content. If different, **the external edit wins** by being applied as a Yjs transaction (using `applyMarkdownAsYjsTransaction`, which diffs at block granularity to minimize churn). This emits a `file.externallyEdited` event and shows a toast in the editor. Rationale: vim user explicitly typed those bytes; refusing them would be confusing.
|
|
128
|
-
6. **External edit while doc is NOT live:** trivial. Next open seeds from disk, which is now newer than the last `ydoc_snapshots` row.
|
|
129
|
-
|
|
130
|
-
This is the **single hardest design decision** in v2. Document it in `docs/file-vs-yjs-authority.md` with examples before phase F.
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## 2. Persistence model
|
|
135
|
-
|
|
136
|
-
### 2.1 Y.Doc lifecycle per file
|
|
137
|
-
|
|
138
|
-
| Phase | Action |
|
|
139
|
-
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
140
|
-
| First open ever | `parseBlocks(md)` → for each block, create a Y.XmlElement with attributes `data-ref=<ref>` and content. Wrap into the doc's primary Y.XmlFragment named `default`. Write initial snapshot to `ydoc_snapshots`. |
|
|
141
|
-
| Reopen with snapshot | `Y.applyUpdate(doc, snapshot.update)`. Skip markdown parse. |
|
|
142
|
-
| Live edits | Editor's y-prosemirror binding applies Y.Doc updates. Awareness updates broadcast cursors / users. |
|
|
143
|
-
| Debounced live flush | Every `LIVE_FLUSH_MS` or on quiet window of `IDLE_FLUSH_MS`: serialize Y.Doc → markdown via `yDocToMarkdown()`, write `<workspace_root>/<path>.md`, upsert `ydoc_snapshots` row with `Y.encodeStateAsUpdate(doc)`. |
|
|
144
|
-
| External edit detected | Reconcile (§1.3.5). |
|
|
145
|
-
| All clients disconnect | Schedule full flush + idle eviction after `IDLE_EVICT_MS` (default 5 min). After eviction, Y.Doc unloaded from memory; next open reseeds. |
|
|
146
|
-
|
|
147
|
-
### 2.2 Sidecar migration
|
|
148
|
-
|
|
149
|
-
v1 stored comments / suggestions / events / refMap / blockProvenance in `<root>/.proof/<path>.json`. v2:
|
|
150
|
-
|
|
151
|
-
- **Comments, suggestions, events, blockProvenance** → DB rows keyed by `(workspace_id, path)`. See §5.2 schema.
|
|
152
|
-
- **refMap, refAliases** → no longer stored. Refs are derived from current Y.Doc state: each block-level Y.XmlElement carries `data-ref` attribute, stable across edits to its content (the ref is set at insertion time and survives content edits because we attach it to the element node, not its text).
|
|
153
|
-
- **lastAck** → DB column on `agent_records` keyed by `(agent_id, workspace_id, path)`.
|
|
154
|
-
|
|
155
|
-
Recommendation: **drop sidecar JSON entirely.** Migrate existing files at first read: if `.proof/<path>.json` exists, import into DB, then leave the file in place (read-only, ignored) for safety. After 2 weeks of green prod, delete migration code.
|
|
156
|
-
|
|
157
|
-
### 2.3 Agent block-ref + revision model under Yjs
|
|
158
|
-
|
|
159
|
-
This is the second hardest design decision. Two truths to reconcile:
|
|
160
|
-
|
|
161
|
-
- Agents send `baseRevision: N` and expect 409 STALE_REVISION if the doc moved.
|
|
162
|
-
- Yjs has no monotonic per-doc revision; it has a state vector (a `Map<clientId, clock>`) representing the latest seen update per peer.
|
|
163
|
-
|
|
164
|
-
**Approach (Proof-SDK-inspired): mutationBaseToken**
|
|
165
|
-
|
|
166
|
-
When an agent calls `GET /api/agent/files/<workspace>/<path>`, the response includes:
|
|
167
|
-
|
|
168
|
-
```json
|
|
169
|
-
{
|
|
170
|
-
"revision": 47,
|
|
171
|
-
"mutationBaseToken": "<base64-state-vector>",
|
|
172
|
-
"blocks": [ { "ref": "b7f2c1", ... }, ... ]
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
- `revision` is still a monotonic integer maintained per (workspace, path) in DB. Bumped only when ops-applier-yjs commits an agent ops batch. Human edits do NOT bump this counter (they're CRDT; no agent should care).
|
|
177
|
-
- `mutationBaseToken` is `Y.encodeStateVector(doc)` base64-encoded. Carries far more information than `revision`; tells us exactly what the agent saw.
|
|
178
|
-
|
|
179
|
-
When agent POSTs ops with `baseRevision: 47` and (optionally) `mutationBaseToken`:
|
|
180
|
-
|
|
181
|
-
1. Check `revision === 47` — if not, return STALE_REVISION with fresh snapshot.
|
|
182
|
-
2. Optionally validate `mutationBaseToken` matches current state vector. If matches: pure forward agent op, no human edits since. If doesn't match: humans typed since the snapshot. Apply the agent ops anyway — Yjs CRDT will merge — but include a warning header `X-Concurrent-Human-Edits: true` so agent SDKs can log it.
|
|
183
|
-
3. Each block ref in the ops is resolved against current Y.Doc state (the `data-ref` attribute on element nodes). If missing, return BLOCK_NOT_FOUND with fresh snapshot.
|
|
184
|
-
|
|
185
|
-
**Implication for agents:** v1's block-ref-not-found error becomes much rarer because content edits don't invalidate refs. Only deletions do.
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
## 3. Multi-user identity
|
|
190
|
-
|
|
191
|
-
### 3.1 Auth library choice
|
|
192
|
-
|
|
193
|
-
Pick: **NextAuth.js v5 (Auth.js)**.
|
|
194
|
-
|
|
195
|
-
| Option | Verdict |
|
|
196
|
-
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
197
|
-
| **NextAuth v5** | Mature, OAuth providers are config-only, supports DB sessions with Drizzle adapter, integrates with App Router middleware cleanly, has been battle-tested on Next 14/15/16. Cost: opinionated session shape, bundle weight. |
|
|
198
|
-
| lucia-auth | Smaller, more flexible, but author paused active development and recommended migrating away. Don't bet on it. |
|
|
199
|
-
| Hand-rolled OAuth | We'd reimplement: provider URL builders, state cookie, PKCE, JWT signing, refresh rotation. Each is a security landmine. Reject. |
|
|
200
|
-
|
|
201
|
-
**Session storage:** **DB sessions, not JWT.** Reasons:
|
|
202
|
-
|
|
203
|
-
- Logout-anywhere is a real requirement
|
|
204
|
-
- Session revocation on permission change
|
|
205
|
-
- Token refresh rotation easier with DB
|
|
206
|
-
- JWT cookies become large; DB sessions remain a short opaque ID
|
|
207
|
-
|
|
208
|
-
**Cookie:** `__Host-session` in prod, `next-auth.session-token` semantics. HttpOnly, SameSite=Lax, Secure in prod.
|
|
209
|
-
|
|
210
|
-
### 3.2 User record schema (Drizzle pseudo)
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
users
|
|
214
|
-
id uuid primary key
|
|
215
|
-
email text not null unique
|
|
216
|
-
display_name text not null
|
|
217
|
-
avatar_url text
|
|
218
|
-
created_at timestamptz default now()
|
|
219
|
-
|
|
220
|
-
oauth_accounts
|
|
221
|
-
id uuid primary key
|
|
222
|
-
user_id uuid references users on delete cascade
|
|
223
|
-
provider text not null -- "github" | "google"
|
|
224
|
-
provider_sub text not null -- subject id from provider
|
|
225
|
-
access_token text -- encrypted at rest (later phase)
|
|
226
|
-
refresh_token text -- encrypted at rest
|
|
227
|
-
expires_at timestamptz
|
|
228
|
-
unique (provider, provider_sub)
|
|
229
|
-
|
|
230
|
-
sessions
|
|
231
|
-
id text primary key -- random 32 bytes b64u
|
|
232
|
-
user_id uuid references users on delete cascade
|
|
233
|
-
expires_at timestamptz not null
|
|
234
|
-
created_at timestamptz default now()
|
|
235
|
-
last_seen timestamptz default now()
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### 3.3 Identity threading
|
|
239
|
-
|
|
240
|
-
| Surface | How identity flows |
|
|
241
|
-
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
242
|
-
| HTTP request | Session cookie → middleware → `req.user` (uid, email, displayName) |
|
|
243
|
-
| Yjs WebSocket | `onAuthenticate({ token })` token is the session cookie value; server validates same way → resolves user; injected into `context.user`. Awareness sets `{ user: { id, name, color } }`. |
|
|
244
|
-
| proof-span `by` attribute | Now `"user:<uuid>"` for humans (was `"human"`), `"ai:<agent-id>"` for agents. v1 `"human"` value preserved during read for back-compat in old files (mapped to `"user:legacy"`). |
|
|
245
|
-
| Agent HTTP `by` field | Must equal authenticated agent's id (already enforced by `verifyBy`). Browser-driven mutations (suggestion accept, span accept) use `by: "user:<uuid>"`. |
|
|
246
|
-
| Agent registration approval | Whoever owns the workspace approves. Workspace admins can approve. Pending registrations carry the requested workspace_id. |
|
|
247
|
-
| Agent registry ownership | Each agent record is `(workspace_id, agent_id)` keyed. Tokens are workspace-scoped. |
|
|
248
|
-
|
|
249
|
-
---
|
|
250
|
-
|
|
251
|
-
## 4. Workspace model
|
|
252
|
-
|
|
253
|
-
### 4.1 Workspaces as first-class entity
|
|
254
|
-
|
|
255
|
-
```
|
|
256
|
-
workspaces
|
|
257
|
-
id uuid primary key
|
|
258
|
-
name text not null
|
|
259
|
-
root_path text not null -- absolute on server
|
|
260
|
-
created_by uuid references users
|
|
261
|
-
created_at timestamptz default now()
|
|
262
|
-
visibility enum('private', 'invite_only') default 'invite_only'
|
|
263
|
-
|
|
264
|
-
workspace_members
|
|
265
|
-
workspace_id uuid references workspaces on delete cascade
|
|
266
|
-
user_id uuid references users on delete cascade
|
|
267
|
-
role enum('admin', 'editor', 'viewer') not null
|
|
268
|
-
added_at timestamptz default now()
|
|
269
|
-
primary key (workspace_id, user_id)
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
- All files live under `<workspaces.root_path>/`. Server enforces traversal protection (current `safeRootPath` generalizes to `safeWorkspacePath(workspace_id, rel)`).
|
|
273
|
-
- A user with no membership row gets 404 on the workspace (do not leak existence via 403).
|
|
274
|
-
- `admin` can: invite/remove members, register agents, change visibility, delete workspace.
|
|
275
|
-
- `editor` can: read + write files, manage comments/suggestions, mention agents.
|
|
276
|
-
- `viewer` can: read only. Read-only Y.Doc connection (Hocuspocus supports `readOnly: true` in onAuthenticate).
|
|
277
|
-
|
|
278
|
-
### 4.2 Path namespacing
|
|
279
|
-
|
|
280
|
-
URLs become `/w/<workspace_slug>/<file-path>`. API:
|
|
281
|
-
|
|
282
|
-
- `/api/workspaces/<workspace_id>/files/<path>` (catch-all)
|
|
283
|
-
- `/api/workspaces/<workspace_id>/agent/files/<path>` (agent HTTP surface, see §7)
|
|
284
|
-
- WS: `wss://<host>/yjs/<workspace_id>/<path>`
|
|
285
|
-
|
|
286
|
-
Slug or UUID in URL? Use **slug** for human URLs (`/w/team-notes/plan.md`), **UUID** under the hood (resolved server-side in middleware). Keeps URLs nice without leaking enumerable IDs.
|
|
287
|
-
|
|
288
|
-
### 4.3 Agent registry under workspace
|
|
289
|
-
|
|
290
|
-
```
|
|
291
|
-
agent_records
|
|
292
|
-
id text primary key -- agent slug, e.g. "claude-1"
|
|
293
|
-
workspace_id uuid references workspaces on delete cascade
|
|
294
|
-
display_name text not null
|
|
295
|
-
token_hash text not null -- sha256 of plaintext
|
|
296
|
-
scope_paths jsonb not null -- string[] glob
|
|
297
|
-
scope_ops jsonb not null -- ('read'|'mutate')[]
|
|
298
|
-
created_by uuid references users
|
|
299
|
-
created_at timestamptz default now()
|
|
300
|
-
last_seen timestamptz
|
|
301
|
-
unique (workspace_id, id)
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
Token check becomes: lookup by hash → find row → check workspace + match `X-Agent-Id` to row id.
|
|
305
|
-
|
|
306
|
-
`agent_registrations` table mirrors `pending.ts` but DB-backed (rows pruned after TTL).
|
|
307
|
-
|
|
308
|
-
---
|
|
309
|
-
|
|
310
|
-
## 5. Database
|
|
311
|
-
|
|
312
|
-
### 5.1 Engine choice
|
|
313
|
-
|
|
314
|
-
Pick: **Postgres** (managed; e.g., Neon, Supabase, or Railway).
|
|
315
|
-
|
|
316
|
-
Rationale:
|
|
317
|
-
|
|
318
|
-
- User says "remote server, multi-user". SQLite single-writer model becomes a bottleneck under realtime workloads where Hocuspocus may write `ydoc_snapshots` every 5s per active file.
|
|
319
|
-
- WS server may run as multiple replicas; SQLite + multiple writers = pain. Postgres handles concurrent writes natively.
|
|
320
|
-
- LISTEN/NOTIFY useful for fan-out of events to non-WS subscribers (later).
|
|
321
|
-
- Drizzle ORM has identical TS API for both, so swap cost is low if user prefers SQLite later.
|
|
322
|
-
|
|
323
|
-
**Fallback for small deploys:** keep SQLite as an option via drizzle's dialect switch. Migrations written to both. Defaulted off; documented.
|
|
324
|
-
|
|
325
|
-
### 5.2 Full schema (concise)
|
|
326
|
-
|
|
327
|
-
```
|
|
328
|
-
users (§3.2)
|
|
329
|
-
oauth_accounts (§3.2)
|
|
330
|
-
sessions (§3.2)
|
|
331
|
-
workspaces (§4.1)
|
|
332
|
-
workspace_members (§4.1)
|
|
333
|
-
agent_records (§4.3)
|
|
334
|
-
|
|
335
|
-
agent_registrations
|
|
336
|
-
id text primary key -- registration code
|
|
337
|
-
workspace_id uuid references workspaces on delete cascade
|
|
338
|
-
payload jsonb -- display_name, requested scope
|
|
339
|
-
status enum('pending','approved','rejected','expired') default 'pending'
|
|
340
|
-
approved_by uuid references users
|
|
341
|
-
approved_at timestamptz
|
|
342
|
-
expires_at timestamptz
|
|
343
|
-
created_at timestamptz default now()
|
|
344
|
-
|
|
345
|
-
comments
|
|
346
|
-
id text primary key -- "c" + hex
|
|
347
|
-
workspace_id uuid references workspaces on delete cascade
|
|
348
|
-
path text not null
|
|
349
|
-
block_ref text not null
|
|
350
|
-
resolved boolean default false
|
|
351
|
-
created_at timestamptz default now()
|
|
352
|
-
unique (workspace_id, path, id)
|
|
353
|
-
|
|
354
|
-
comment_turns
|
|
355
|
-
id bigserial primary key
|
|
356
|
-
comment_id text references comments on delete cascade
|
|
357
|
-
by text not null
|
|
358
|
-
text text not null
|
|
359
|
-
at timestamptz default now()
|
|
360
|
-
|
|
361
|
-
suggestions
|
|
362
|
-
id text primary key
|
|
363
|
-
workspace_id uuid references workspaces on delete cascade
|
|
364
|
-
path text not null
|
|
365
|
-
block_ref text not null
|
|
366
|
-
kind text not null
|
|
367
|
-
status text not null
|
|
368
|
-
by text not null
|
|
369
|
-
markdown text
|
|
370
|
-
basis text
|
|
371
|
-
basis_detail text
|
|
372
|
-
created_at timestamptz default now()
|
|
373
|
-
resolved_at timestamptz
|
|
374
|
-
resolved_by text
|
|
375
|
-
unique (workspace_id, path, id)
|
|
376
|
-
|
|
377
|
-
events
|
|
378
|
-
id bigserial primary key
|
|
379
|
-
workspace_id uuid references workspaces on delete cascade
|
|
380
|
-
path text not null
|
|
381
|
-
seq bigint not null -- monotonic per (workspace, path)
|
|
382
|
-
type text not null
|
|
383
|
-
by text not null
|
|
384
|
-
at timestamptz default now()
|
|
385
|
-
payload jsonb
|
|
386
|
-
unique (workspace_id, path, seq)
|
|
387
|
-
|
|
388
|
-
ack_cursors
|
|
389
|
-
workspace_id uuid
|
|
390
|
-
path text
|
|
391
|
-
by text -- agent id
|
|
392
|
-
up_to bigint -- event seq
|
|
393
|
-
primary key (workspace_id, path, by)
|
|
394
|
-
|
|
395
|
-
ydoc_snapshots
|
|
396
|
-
workspace_id uuid
|
|
397
|
-
path text
|
|
398
|
-
taken_at timestamptz default now()
|
|
399
|
-
state_update bytea -- Y.encodeStateAsUpdate
|
|
400
|
-
primary key (workspace_id, path)
|
|
401
|
-
|
|
402
|
-
block_provenance
|
|
403
|
-
workspace_id uuid
|
|
404
|
-
path text
|
|
405
|
-
block_ref text
|
|
406
|
-
attrs jsonb -- SpanAttrs for non-wrappable blocks
|
|
407
|
-
primary key (workspace_id, path, block_ref)
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
Files (the markdown bytes) stay on disk. No `files` table needed; the file tree under each workspace root is the source of fileness.
|
|
411
|
-
|
|
412
|
-
### 5.3 Migrations: Drizzle
|
|
413
|
-
|
|
414
|
-
Pick: **Drizzle ORM + drizzle-kit**.
|
|
415
|
-
|
|
416
|
-
| Option | Verdict |
|
|
417
|
-
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
418
|
-
| **Drizzle** | TS-first schemas, lightweight, no runtime overhead, generates SQL migrations as files, works with both Postgres and SQLite. NextAuth has a Drizzle adapter. Recommended for greenfield TS projects. |
|
|
419
|
-
| Prisma | Heavier, slower start, opinionated client generation, less control. Reject. |
|
|
420
|
-
| Kysely | Beautiful query builder, no migration story (drizzle's `drizzle-kit` is the closest equivalent and tighter). |
|
|
421
|
-
| Raw SQL + a runner | Tempting but reintroduces bugs we already get for free. Reject. |
|
|
422
|
-
|
|
423
|
-
Migration command in `package.json`: `db:generate`, `db:push`, `db:migrate`. Add to `scripts`.
|
|
424
|
-
|
|
425
|
-
---
|
|
426
|
-
|
|
427
|
-
## 6. Realtime transport
|
|
428
|
-
|
|
429
|
-
### 6.1 Co-location vs separate process
|
|
430
|
-
|
|
431
|
-
**Pick co-location** via custom `server.ts`:
|
|
432
|
-
|
|
433
|
-
```ts
|
|
434
|
-
// server.ts (sketch)
|
|
435
|
-
const httpServer = http.createServer(nextHandler);
|
|
436
|
-
const hocuspocus = Hocuspocus.configure({
|
|
437
|
-
extensions: [database, throttle, logger],
|
|
438
|
-
onAuthenticate,
|
|
439
|
-
onLoadDocument,
|
|
440
|
-
onChange,
|
|
441
|
-
onStoreDocument,
|
|
442
|
-
onDisconnect,
|
|
443
|
-
});
|
|
444
|
-
hocuspocus.attachWebSocketServer(httpServer, { path: "/yjs" });
|
|
445
|
-
httpServer.listen(PORT);
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
Reasons for co-location:
|
|
449
|
-
|
|
450
|
-
- One process = one session model = one deploy unit
|
|
451
|
-
- WS upgrade and HTTP go through the same TLS cert / domain (no CORS, no cross-origin cookies)
|
|
452
|
-
- Hocuspocus's API is designed for this mounting style
|
|
453
|
-
|
|
454
|
-
Cost: lose Next.js' built-in `next start` ergonomics. We replace with `node server.ts`. Document this.
|
|
455
|
-
|
|
456
|
-
Alternative if WS scaling becomes a problem: factor Hocuspocus into a separate process behind the same reverse proxy, use Postgres or Redis as the cross-replica sync layer (`@hocuspocus/extension-database` supports this). Not in v2 scope.
|
|
457
|
-
|
|
458
|
-
### 6.2 Auth gate on WS upgrade
|
|
459
|
-
|
|
460
|
-
```ts
|
|
461
|
-
async onAuthenticate({ token, documentName, request }) {
|
|
462
|
-
const session = await verifySessionCookie(token);
|
|
463
|
-
if (!session) throw new Error('UNAUTHORIZED');
|
|
464
|
-
|
|
465
|
-
const [workspaceId, ...pathParts] = documentName.split('/');
|
|
466
|
-
const role = await getMemberRole(workspaceId, session.user.id);
|
|
467
|
-
if (!role) throw new Error('FORBIDDEN');
|
|
468
|
-
|
|
469
|
-
const origin = request.headers.origin;
|
|
470
|
-
if (!isAllowedOrigin(origin)) throw new Error('FORBIDDEN_ORIGIN');
|
|
471
|
-
|
|
472
|
-
return {
|
|
473
|
-
user: { id: session.user.id, name: session.user.displayName, color: hashColor(session.user.id) },
|
|
474
|
-
workspaceId,
|
|
475
|
-
path: pathParts.join('/'),
|
|
476
|
-
readOnly: role === 'viewer'
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
Browser side: y-hocuspocus provider's `token` param holds session cookie. Or, simpler — since same-origin WS auto-sends cookies, parse the cookie server-side in `onAuthenticate({ request })`. Pick whichever Hocuspocus's API exposes cleanly; document the choice in `server.ts`.
|
|
482
|
-
|
|
483
|
-
### 6.3 Awareness contract
|
|
484
|
-
|
|
485
|
-
Each connection's awareness state:
|
|
486
|
-
|
|
487
|
-
```ts
|
|
488
|
-
{
|
|
489
|
-
user: { id: string, name: string, color: string },
|
|
490
|
-
cursor?: { anchor: number, head: number },
|
|
491
|
-
typing?: boolean
|
|
492
|
-
}
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
- Color: deterministic from user id (`hashColor`), 12-color palette tuned for the editor's dark + light themes. Same user same color across reconnects.
|
|
496
|
-
- Display name: from session.
|
|
497
|
-
- Cursors render via `y-prosemirror`'s built-in `yCursorPlugin`. Configure to render small caret bar + tag with name on hover.
|
|
498
|
-
- Typing indicator (optional polish): set true on input, debounce-clear after 1s. Not strictly necessary v2.
|
|
499
|
-
|
|
500
|
-
---
|
|
501
|
-
|
|
502
|
-
## 7. Agent bridge changes
|
|
503
|
-
|
|
504
|
-
### 7.1 Two architectural options
|
|
505
|
-
|
|
506
|
-
**Option A: HTTP-only agent surface, server compiles ops to Y.Doc transactions.** (chosen)
|
|
507
|
-
|
|
508
|
-
```
|
|
509
|
-
Agent ─HTTP POST /api/agent/files─► route
|
|
510
|
-
│
|
|
511
|
-
├─ verify auth + scope + by
|
|
512
|
-
├─ open Y.Doc (load from in-memory pool or rehydrate from snapshot)
|
|
513
|
-
├─ check baseRevision
|
|
514
|
-
├─ ops-applier-yjs.applyOps(ops, ydoc, { actor })
|
|
515
|
-
│ - for each block.* op: mutate Y.XmlFragment within doc.transact()
|
|
516
|
-
│ - for each comment.* / suggestion.* op: DB row update
|
|
517
|
-
│ - emit events
|
|
518
|
-
│ - wrap proof-spans inline as Y nodes
|
|
519
|
-
├─ bump revision counter (DB)
|
|
520
|
-
├─ schedule flush (markdown serialize + ydoc_snapshot upsert)
|
|
521
|
-
└─ return new Snapshot
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
The transact's awareness identity is set to a synthetic clientId reserved for the agent (e.g. negative integers per agent id, deterministic). Browser sees the edit live via Hocuspocus broadcast; cursors show "claude is editing" via awareness if the agent also pushes presence.
|
|
525
|
-
|
|
526
|
-
**Option B: Agent has its own Yjs client connection.** (rejected)
|
|
527
|
-
|
|
528
|
-
| Pro | Con |
|
|
529
|
-
| ------------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
|
530
|
-
| Agent gets realtime echo | Agent SDK becomes 10x more complex (must speak y-protocols) |
|
|
531
|
-
| | Agent can no longer be "just curl" |
|
|
532
|
-
| | Breaks v1 skill / bootstrap-prompt entirely |
|
|
533
|
-
| | No clear win — agents don't need to see human keystrokes in real time; polling events catches comment additions etc. |
|
|
534
|
-
|
|
535
|
-
**Decision: Option A.** Existing skill/bootstrap-prompt survives unchanged in shape; only the workspace prefix on URLs is new and the bearer token replaces env-token semantics.
|
|
536
|
-
|
|
537
|
-
### 7.2 baseRevision becomes mutationBaseToken (or stays revision)
|
|
538
|
-
|
|
539
|
-
Both are returned. Agents that send only `baseRevision` still work. Newer agents may send `mutationBaseToken` for finer-grained concurrency awareness (§2.3).
|
|
540
|
-
|
|
541
|
-
### 7.3 proof-span behavior
|
|
542
|
-
|
|
543
|
-
Unchanged in shape. Wrapping logic moves: ops-applier-yjs constructs the proof-span as a Y.XmlElement (or as inline HTML inside a paragraph's Y.XmlText, depending on how y-prosemirror serializes ProofSpan marks).
|
|
544
|
-
|
|
545
|
-
**Important integration test (Phase 6):** insert a paragraph via agent → verify y-prosemirror renders the proof-span mark on the right text → verify markdown serialization emits the right HTML.
|
|
546
|
-
|
|
547
|
-
### 7.4 New agent endpoints
|
|
548
|
-
|
|
549
|
-
- `POST /api/workspaces/<wid>/agent/files/<path>` — same op vocab
|
|
550
|
-
- `GET /api/workspaces/<wid>/agent/files/<path>` — snapshot
|
|
551
|
-
- `GET /api/workspaces/<wid>/agent/events/<path>` — events
|
|
552
|
-
- `POST /api/workspaces/<wid>/agent/events/<path>` — ack
|
|
553
|
-
- `GET /api/workspaces/<wid>/agent/sidecar/<path>` — comments + suggestions JSON (now built from DB)
|
|
554
|
-
|
|
555
|
-
Backward compat shim: `GET /api/agent/files/<path>` redirects to `/api/workspaces/<default>/agent/files/<path>` for single-workspace deployments. Document deprecation.
|
|
556
|
-
|
|
557
|
-
---
|
|
558
|
-
|
|
559
|
-
## 8. Editor changes
|
|
560
|
-
|
|
561
|
-
### 8.1 Remove the htmlToMarkdown save loop
|
|
562
|
-
|
|
563
|
-
Current path: editor change → debounce → `htmlToMarkdown` → `PUT /api/wiki/content`. This bypasses the ops-applier and the revision counter — v1 known bug.
|
|
564
|
-
|
|
565
|
-
v2:
|
|
566
|
-
|
|
567
|
-
- Editor binds to Y.Doc via `@tiptap/extension-collaboration` + `y-prosemirror`.
|
|
568
|
-
- All edits land in Y.Doc → Hocuspocus broadcasts.
|
|
569
|
-
- Server's `onChange` debounces flush (§2.1).
|
|
570
|
-
- `PUT /api/wiki/content` becomes **read-only-or-removed**. Optionally keep for non-markdown files (json, ts, etc. that the editor still saves directly). For markdown files: return 410 Gone with message "use Yjs editor".
|
|
571
|
-
|
|
572
|
-
Editor's `editor-store.ts` simplifies: no debounced save side, just open/close Y.Doc connections.
|
|
573
|
-
|
|
574
|
-
### 8.2 ProofSpan mark stays
|
|
575
|
-
|
|
576
|
-
Definition unchanged. Y-prosemirror serializes marks fine. Verify with a Y.Doc round-trip test (§9 Phase 4).
|
|
577
|
-
|
|
578
|
-
### 8.3 Suggestion cards + comment pips read from DB
|
|
579
|
-
|
|
580
|
-
Currently they read from `useProofStore` populated by `GET /api/agent/sidecar/<path>`. v2: same store, same component code; underlying API now reads from DB rows. Almost zero React component change.
|
|
581
|
-
|
|
582
|
-
When a comment is added (by anyone), server emits an event AND pushes to a per-workspace SSE channel that the editor subscribes to → store updates → pip appears. (Could route via Hocuspocus message channel too, but SSE keeps comment state out of the Y.Doc, which is intentional.)
|
|
583
|
-
|
|
584
|
-
### 8.4 Markdown export
|
|
585
|
-
|
|
586
|
-
Server-side function `yDocToMarkdown(doc, { workspaceId, path })`:
|
|
587
|
-
|
|
588
|
-
1. Walk Y.XmlFragment → convert each block element to mdast node (reverse of v1's parse, with provenance attrs preserved on text marks).
|
|
589
|
-
2. Stringify via remark-stringify (same options as v1's `blocks.ts`).
|
|
590
|
-
3. Augment with block_provenance rows for non-wrappable blocks.
|
|
591
|
-
|
|
592
|
-
Used by:
|
|
593
|
-
|
|
594
|
-
- Debounced disk flush (§2.1)
|
|
595
|
-
- `GET /api/workspaces/<wid>/files/<path>?format=markdown` (read-only API)
|
|
596
|
-
- Git friendly view, downloads, mobile read-only mode if it ever ships
|
|
597
|
-
|
|
598
|
-
---
|
|
599
|
-
|
|
600
|
-
## 9. Concrete phase plan
|
|
601
|
-
|
|
602
|
-
Each phase ends with a green-test gate.
|
|
603
|
-
|
|
604
|
-
### Phase 1 — Auth: OAuth + sessions
|
|
605
|
-
|
|
606
|
-
- Add Drizzle + Postgres. Schema for users, oauth_accounts, sessions.
|
|
607
|
-
- Add NextAuth v5 with GitHub + Google providers. Drizzle adapter.
|
|
608
|
-
- Middleware that populates `req.user` from session cookie.
|
|
609
|
-
- Replace `wv_owner` cookie reads in routes with session reads.
|
|
610
|
-
- `/signin` page.
|
|
611
|
-
- **Tests:** session round-trip, OAuth mock callback creates user.
|
|
612
|
-
- **LoC ~700. Time ~1-2d.**
|
|
613
|
-
- **Gate:** sign in via GitHub, see name in header, sign out.
|
|
614
|
-
|
|
615
|
-
### Phase 2 — Workspaces + DB migration of registry
|
|
616
|
-
|
|
617
|
-
- Schema: workspaces, workspace_members, agent_records, agent_registrations.
|
|
618
|
-
- Migration script: read `~/.wiki-viewer/agents.json` → seed default workspace + agents.
|
|
619
|
-
- `/api/workspaces` CRUD.
|
|
620
|
-
- `safeRootPath` → `safeWorkspacePath(workspaceId, rel)`. Update consumers.
|
|
621
|
-
- DB-backed registry.
|
|
622
|
-
- **Tests:** workspaces.test, registry-db.test (ports of v1 registry tests).
|
|
623
|
-
- **LoC ~900. Time ~2d.**
|
|
624
|
-
- **Gate:** existing 160 + new tests green.
|
|
625
|
-
|
|
626
|
-
### Phase 3 — DB-backed sidecar
|
|
627
|
-
|
|
628
|
-
- Schema: comments, comment_turns, suggestions, events, ack_cursors, block_provenance.
|
|
629
|
-
- `lib/proof/sidecar-store.ts` DB-backed; same API as v1.
|
|
630
|
-
- ops-applier writes DB rows.
|
|
631
|
-
- Migration: import `.proof/*.json` into DB on first read.
|
|
632
|
-
- **Tests:** all v1 ops-applier/comments/suggestion/trim tests via in-mem DB.
|
|
633
|
-
- **LoC ~700. Time ~2d.**
|
|
634
|
-
- **Gate:** 160+ tests green.
|
|
635
|
-
|
|
636
|
-
### Phase 4 — Yjs server + flush sink (no editor binding yet)
|
|
637
|
-
|
|
638
|
-
- Add yjs, @hocuspocus/server, y-prosemirror, @tiptap/extension-collaboration.
|
|
639
|
-
- `server.ts` mounts Hocuspocus on `/yjs`.
|
|
640
|
-
- `lib/proof/yjs-store.ts`: loadDoc / flushDoc / closeDoc.
|
|
641
|
-
- `lib/proof/yjs-markdown.ts`: markdownToYjs, yDocToMarkdown.
|
|
642
|
-
- Chokidar reconciler (§1.3.5).
|
|
643
|
-
- WS auth (§6.2).
|
|
644
|
-
- **Tests:** yjs-roundtrip, yjs-external-edit, yjs-flush.
|
|
645
|
-
- **LoC ~1300. Time ~3d.**
|
|
646
|
-
- **Gate:** Y.Doc lifecycle tests green.
|
|
647
|
-
|
|
648
|
-
### Phase 5 — Editor binding + presence
|
|
649
|
-
|
|
650
|
-
- Replace save loop with y-prosemirror collab binding.
|
|
651
|
-
- Hocuspocus provider client.
|
|
652
|
-
- Cursor caret + name tag (yCursorPlugin).
|
|
653
|
-
- Presence avatar bar.
|
|
654
|
-
- Remove `PUT /api/wiki/content` for `.md`.
|
|
655
|
-
- **Tests:** editor-roundtrip via Y.Doc; manual two-tab test.
|
|
656
|
-
- **LoC ~600. Time ~2d.**
|
|
657
|
-
- **Gate:** two-tab live edit works.
|
|
658
|
-
|
|
659
|
-
### Phase 6 — Agent bridge port to Yjs
|
|
660
|
-
|
|
661
|
-
- `ops-applier.ts` → `ops-applier-yjs.ts`. `doc.transact()` for block ops.
|
|
662
|
-
- proof-span wrap via Y nodes.
|
|
663
|
-
- mutationBaseToken in snapshot.
|
|
664
|
-
- Routes under `/api/workspaces/<wid>/agent/*`; back-compat redirect.
|
|
665
|
-
- Update skill bootstrap-prompt for workspace prefix.
|
|
666
|
-
- **Tests:** rewrite ops-applier assertions to Y.Doc state; new concurrent-edits.test.
|
|
667
|
-
- **LoC ~1100. Time ~3d.**
|
|
668
|
-
- **Gate:** all proof tests green + concurrency test.
|
|
669
|
-
|
|
670
|
-
### Phase 7 — Realtime UI polish
|
|
671
|
-
|
|
672
|
-
- Per-block selection indicators.
|
|
673
|
-
- Comment SSE push for instant pip appearance.
|
|
674
|
-
- Suggestion live push.
|
|
675
|
-
- HTTPS enforcement, CSRF token, Origin check.
|
|
676
|
-
- **LoC ~500. Time ~2d.**
|
|
677
|
-
|
|
678
|
-
### Phase 8 — Hardening
|
|
679
|
-
|
|
680
|
-
- Rate limit WS connections per user.
|
|
681
|
-
- WS message size cap.
|
|
682
|
-
- Idle eviction tests.
|
|
683
|
-
- Snapshot pruning.
|
|
684
|
-
- Docs: README "running multi-user", `docs/file-vs-yjs-authority.md`.
|
|
685
|
-
- **LoC ~400. Time ~1-2d.**
|
|
686
|
-
|
|
687
|
-
**Total: ~6200 LoC, 16-18 working days.**
|
|
688
|
-
|
|
689
|
-
---
|
|
690
|
-
|
|
691
|
-
## 10. Risks + open questions
|
|
692
|
-
|
|
693
|
-
### 10.1 File vs Y.Doc authority
|
|
694
|
-
|
|
695
|
-
External edit during live session: external wins, applied as Yjs transaction. Risk: silent loss of unflushed Y.Doc edits. Mitigation: aggressive flush during active session.
|
|
696
|
-
|
|
697
|
-
### 10.2 Git commits during live editing
|
|
698
|
-
|
|
699
|
-
Commit captures whatever was flushed. Sample pre-commit hook calls `/api/flush`. Document, don't auto-install.
|
|
700
|
-
|
|
701
|
-
### 10.3 Bandwidth
|
|
702
|
-
|
|
703
|
-
Idle: few KB/s keepalive. Active edit: ~1-10 KB/s. 100 users ≈ 1 MB/s peak. Acceptable.
|
|
704
|
-
|
|
705
|
-
### 10.4 Suggestion lifecycle in CRDT
|
|
706
|
-
|
|
707
|
-
Accept applies Y.Doc transaction. If target block was deleted by human edit: BLOCK_NOT_FOUND, suggestion auto-archived "block_deleted".
|
|
708
|
-
|
|
709
|
-
### 10.5 Undo for AI-marked content
|
|
710
|
-
|
|
711
|
-
proof-span removal via Y.Doc transaction. Edge case: undo after human typed in span may restore mark over human content. Document and accept.
|
|
712
|
-
|
|
713
|
-
### 10.6 Open questions for user (before Phase 1)
|
|
714
|
-
|
|
715
|
-
1. Postgres OK, or insist on SQLite for solo deploys? (Recommend Postgres + SQLite-as-option.)
|
|
716
|
-
2. Managed Postgres (Neon/Supabase) or self-host?
|
|
717
|
-
3. GitHub + Google confirmed. Microsoft later? (No for v2.)
|
|
718
|
-
4. Workspace creation: anyone or admin-only? (Recommend: anyone creates their own; admins invite to existing.)
|
|
719
|
-
5. Editor for non-markdown files: keep `PUT /api/wiki/content`? (Yes.)
|
|
720
|
-
6. Browser localStorage Y.Doc persistence for offline read? (Not v2.)
|
|
721
|
-
|
|
722
|
-
---
|
|
723
|
-
|
|
724
|
-
## 11. Out of scope
|
|
725
|
-
|
|
726
|
-
- Mobile native app
|
|
727
|
-
- Offline editing without sync
|
|
728
|
-
- E2E encryption
|
|
729
|
-
- Plugin marketplace
|
|
730
|
-
- Search (full-text), tags
|
|
731
|
-
- Real-time voice/video
|
|
732
|
-
- File create/rename/delete via agent
|
|
733
|
-
- Multi-file agent ops
|
|
734
|
-
- Cross-workspace links
|
|
735
|
-
- Snapshot history browsing
|
|
736
|
-
- Audit log UI beyond event stream
|
|
737
|
-
|
|
738
|
-
---
|
|
739
|
-
|
|
740
|
-
## 12. Constraint compliance
|
|
741
|
-
|
|
742
|
-
| Constraint | v2 |
|
|
743
|
-
| --------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
744
|
-
| Preserve agent HTTP contract | Op vocab unchanged. URL gains `/workspaces/<wid>`. Skill ships with one-line path update. |
|
|
745
|
-
| Tests keep running | `node --test` / `tsx --test`. New tests follow pattern. |
|
|
746
|
-
| 160 tests mostly survive | Phases 1-3 port them. Phase 6 rewrites assertions, preserves cases. ≥150 survive. |
|
|
747
|
-
| No big-bang | 8 phases each shippable. User can pause after Phase 3. |
|
|
748
|
-
| Tabs, TS strict, no `any`, no em-dashes | Continued. |
|
|
749
|
-
|
|
750
|
-
---
|
|
751
|
-
|
|
752
|
-
## 13. Definition of done
|
|
753
|
-
|
|
754
|
-
- Phases 1-8 merged
|
|
755
|
-
- All tests green incl. Phase 6 rewrites
|
|
756
|
-
- Two-tab live edit smoke passes
|
|
757
|
-
- External edit reconciliation smoke passes
|
|
758
|
-
- Agent skill updated + re-tested end-to-end
|
|
759
|
-
- `docs/file-vs-yjs-authority.md` reviewed
|
|
760
|
-
- README "running multi-user" section
|
|
761
|
-
- v1 plan (`docs/agent-collab-plan.md`) untouched
|
|
762
|
-
|
|
763
|
-
---
|
|
764
|
-
|
|
765
|
-
## 14. Critical files for implementation
|
|
766
|
-
|
|
767
|
-
- `src/lib/proof/ops-applier.ts` → becomes `ops-applier-yjs.ts`; biggest refactor
|
|
768
|
-
- `src/components/editor/editor.tsx` → replace save loop with y-prosemirror collab binding
|
|
769
|
-
- `src/lib/proof/registry.ts` → DB-backed per-workspace agent records
|
|
770
|
-
- `src/lib/proof/auth.ts` → session+OAuth gate, plus WS onAuthenticate
|
|
771
|
-
- `src/app/api/wiki/content/route.ts` → remove markdown PUT path that bypasses pipeline
|