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,229 +0,0 @@
|
|
|
1
|
-
# Mobile-readiness spec
|
|
2
|
-
|
|
3
|
-
Implementation-ready spec for making wiki-viewer usable on phones/tablets. Desktop layout and behavior stay visually unchanged. Mobile is additive, gated behind a breakpoint.
|
|
4
|
-
|
|
5
|
-
Stack: Next.js 16, React 19, Tailwind 3, Radix/shadcn, TipTap 3, Zustand.
|
|
6
|
-
|
|
7
|
-
## Design principle
|
|
8
|
-
|
|
9
|
-
The app already has the two primitives we need:
|
|
10
|
-
|
|
11
|
-
1. Sidebar already has a **collapse/expand** state (`sidebarCollapsed`, local `useState` in `src/app/page.tsx:405`). When collapsed, the sidebar `Card` is unmounted and a reopen button appears top-left of the editor area (`page.tsx:~2057`).
|
|
12
|
-
2. AI panel is already a **fixed overlay + backdrop** driven by zustand (`useAIPanelStore.isOpen`), not part of the flex row (`src/components/ai-panel/ai-panel.tsx:152`).
|
|
13
|
-
|
|
14
|
-
So mobile work is mostly: (a) make the sidebar **overlay instead of push** on small screens, (b) auto-collapse it on mobile, (c) add a hamburger affordance, (d) cap fixed panel widths, (e) make touch-only affordances reachable, (f) fix the viewport meta. We do NOT introduce a new layout engine.
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## 1. Breakpoint strategy
|
|
19
|
-
|
|
20
|
-
Single breakpoint. Mobile = viewport `< 768px` (Tailwind `md`). At/above `md` = desktop, unchanged.
|
|
21
|
-
|
|
22
|
-
- Tailwind prefix: `md:` is the desktop gate. Mobile styles are the unprefixed base; `md:` restores desktop.
|
|
23
|
-
- Rationale: one breakpoint keeps the matrix small and matches the real failure mode (3 panels do not fit on a phone; they fit fine on a ≥768px tablet in landscape and on iPad portrait). Tablets ≥768px keep the desktop 3-panel layout, which is acceptable.
|
|
24
|
-
- No second tier. Phone-vs-tablet nuance is not worth the complexity here.
|
|
25
|
-
|
|
26
|
-
A JS boolean is needed in a few places (auto-collapse on mount, "opening sidebar closes AI panel" logic, ignoring the resize handle). Add a `useIsMobile()` hook (section 7) backed by `matchMedia("(max-width: 767px)")`. Everything purely visual stays in Tailwind `md:` classes; only logic that branches on the boolean uses the hook.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## 2. Layout transformation
|
|
31
|
-
|
|
32
|
-
Desktop (`md` and up): unchanged. `flex h-screen overflow-hidden` row of [sidebar (resizable, pushes content)] + [editor flex-1] + [AI panel overlay].
|
|
33
|
-
|
|
34
|
-
Mobile (`< md`):
|
|
35
|
-
|
|
36
|
-
### Sidebar → overlay drawer (left)
|
|
37
|
-
|
|
38
|
-
- Sidebar must **overlay** the editor, not occupy a flex column. On mobile the sidebar `Card` becomes `fixed inset-y-0 left-0 z-50` with its own backdrop (`fixed inset-0 z-40`, tap-to-close). On desktop it stays in-flow (`md:static md:z-auto`, no backdrop).
|
|
39
|
-
- Width on mobile: `w-[85vw] max-w-[20rem]` (≈ phone-width minus a peek of the editor). Ignore the `sidebarWidth` store on mobile (section 9). On desktop keep `style={{ width: sidebarWidth }}` and `md:w-[var(...)]` is not needed because the inline style wins; instead gate the inline width so it only applies at `md+` (section 7 / component table).
|
|
40
|
-
- Default state on mobile: **collapsed** (closed). On first mount at mobile width, force `sidebarCollapsed = true`.
|
|
41
|
-
- Open trigger: hamburger button in the mobile top bar (section 3).
|
|
42
|
-
- Close: tap backdrop, tap a file (navigating opens the file then closes the drawer on mobile), or the existing collapse button in the sidebar header.
|
|
43
|
-
- Backdrop: only rendered on mobile (`md:hidden`) and only when sidebar open.
|
|
44
|
-
- z-index: backdrop `z-40`, sidebar `z-50`. AI panel uses backdrop `z-40` / panel `z-50` too; they are mutually exclusive on mobile (opening one closes the other, section 8) so no stacking conflict.
|
|
45
|
-
|
|
46
|
-
### AI panel → full-ish width sheet (right)
|
|
47
|
-
|
|
48
|
-
- Already an overlay. Only change: width. `w-80` becomes `w-[90vw] max-w-sm md:w-80` so it never exceeds viewport on small screens. Everything else (backdrop, Esc, focus trap) already works.
|
|
49
|
-
|
|
50
|
-
### Editor → full width
|
|
51
|
-
|
|
52
|
-
- Editor area is already `flex-1 min-w-0`. With the sidebar overlaying instead of pushing, it naturally spans full width on mobile. No change beyond the top-bar offset (section 3).
|
|
53
|
-
|
|
54
|
-
### Body scroll
|
|
55
|
-
|
|
56
|
-
- Keep `h-screen overflow-hidden` on the shell on all breakpoints. The editor body already scrolls internally (`overflow-y-auto`). Do not switch to body scroll. Reason: the editor toolbar and doc header are sticky chrome; body scroll would detach them and complicate the on-screen-keyboard case. The editor's own scroll container handles long docs.
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## 3. Mobile top bar / chrome
|
|
61
|
-
|
|
62
|
-
Desktop has no global header; do not add one there. The editor's doc-header row already exists per-view (`page.tsx:2086`, `:2198`) and hosts the collapsed-sidebar reopen button via `pl-11` padding.
|
|
63
|
-
|
|
64
|
-
Mobile approach: **reuse the existing reopen-button slot, add an AI-panel toggle next to it.** No new full-width header bar.
|
|
65
|
-
|
|
66
|
-
- The existing `sidebarCollapsed && <PanelLeftOpen>` button (top-left, `absolute left-2 top-2`) already serves as the hamburger. On mobile the sidebar is always collapsed-by-default, so this button is the persistent menu affordance. Change: render it whenever `isMobile || sidebarCollapsed` (so it shows on mobile even though state is "collapsed"), icon stays `PanelLeftOpen` (menu-like). Keep `pl-11` header offset logic but key it on the same condition.
|
|
67
|
-
- Add an **AI panel toggle** button: top-right of the editor area, `absolute right-2 top-2 z-10`, `md:hidden` (desktop opens AI panel via existing mechanism/keyboard; do not change desktop). Icon: `Bot` (already imported in ai-panel; import in page). `onClick={() => useAIPanelStore.getState().open()}`. 44px tap target on mobile (section 5).
|
|
68
|
-
- These two floating buttons sit above the editor doc-header. They must not overlap the doc title; the existing `pl-11` already reserves left space, add matching `pr-11` on mobile when the AI toggle shows.
|
|
69
|
-
|
|
70
|
-
Rationale for no dedicated header: the per-view doc headers already carry title + actions. A second global bar would eat vertical space (precious with the on-screen keyboard up) and duplicate chrome. Floating buttons over existing chrome is the minimal, non-regressing change.
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## 4. Touch interaction replacements
|
|
75
|
-
|
|
76
|
-
Decisive choice per item:
|
|
77
|
-
|
|
78
|
-
### Hover-gated sidebar row actions (`page.tsx:1326`, `:1861`)
|
|
79
|
-
|
|
80
|
-
- Make them **always visible on coarse pointers**. Use `@media (hover: none)` to neutralize the `opacity-0 ... group-hover:opacity-100` reveal. Add a global CSS rule in `globals.css`:
|
|
81
|
-
```css
|
|
82
|
-
@media (hover: none) {
|
|
83
|
-
.hover-reveal {
|
|
84
|
-
opacity: 1 !important;
|
|
85
|
-
max-width: none !important;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
Add class `hover-reveal` to the two reveal wrappers. Keeps desktop hover behavior intact; on touch the actions are simply always shown. Chosen over a per-row "..." menu because the actions already collapse cleanly and always-visible is zero new UI.
|
|
90
|
-
|
|
91
|
-
### Image resize handles (`resizable-image.tsx:100-107`)
|
|
92
|
-
|
|
93
|
-
- Same `@media (hover: none)` always-visible treatment via the `hover-reveal` class. Handles already use `onPointerDown` (touch-capable) so once visible they work. Bump handle hit area on coarse pointers to ≥24px (visually can stay small via a transparent padded hit zone) so they are tappable.
|
|
94
|
-
|
|
95
|
-
### Tree drag-reorder (`page.tsx:1202`, HTML5 `draggable`)
|
|
96
|
-
|
|
97
|
-
- **Disable on touch.** HTML5 DnD is not touch-capable and a touch DnD lib is out of scope. Set `draggable={!isMobile}`. Reordering on mobile is done via the existing context menu (long-press opens Radix `ContextMenu`, already wired at `page.tsx:1189`) which exposes move/rename actions. No new UI.
|
|
98
|
-
|
|
99
|
-
### Sidebar resize handle (`page.tsx:2009`, `onMouseDown`)
|
|
100
|
-
|
|
101
|
-
- **Hide on mobile.** The mobile sidebar is a fixed-width drawer; resizing is meaningless. Add `md:block hidden` (or render-gate on `!isMobile`). No touch handler added.
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## 5. Tap targets
|
|
106
|
-
|
|
107
|
-
Target: 44×44 CSS px on touch (iOS HIG; covers Android 48 in practice with padding). Apply **only on coarse pointers** so desktop density is unchanged.
|
|
108
|
-
|
|
109
|
-
- Current `h-7 w-7` (28px) and `h-8 w-8` (32px) icon buttons are below target. Do not resize globally.
|
|
110
|
-
- Add a global rule:
|
|
111
|
-
```css
|
|
112
|
-
@media (hover: none) and (pointer: coarse) {
|
|
113
|
-
button,
|
|
114
|
-
[role="button"],
|
|
115
|
-
a.btn {
|
|
116
|
-
min-height: 44px;
|
|
117
|
-
min-width: 44px;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
Scope carefully: this can distort dense editor toolbar. Prefer a utility class `touch-target` applied to the specific mobile-critical controls (hamburger, AI toggle, sidebar row primary tap, file-tree rows) rather than all buttons, if the blanket rule visually breaks the toolbar. Implementer picks blanket vs scoped after a quick visual check; default to **scoped** to avoid toolbar regressions.
|
|
122
|
-
- File-tree rows: ensure row height ≥44px on coarse pointers (they are currently compact). Add `touch-target`-style min-height to the row container on mobile.
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## 6. Viewport meta
|
|
127
|
-
|
|
128
|
-
`src/app/layout.tsx:43`, replace:
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
export const viewport: Viewport = {
|
|
132
|
-
themeColor: "#0c0a09",
|
|
133
|
-
width: "device-width",
|
|
134
|
-
initialScale: 1,
|
|
135
|
-
viewportFit: "cover",
|
|
136
|
-
};
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
- `width: device-width`, `initialScale: 1`: standard responsive baseline.
|
|
140
|
-
- `viewportFit: "cover"`: needed for notch / safe-area on installed iOS PWA (status bar is `black-translucent`). Pairs with safe-area insets (section 9).
|
|
141
|
-
- **Do NOT set `maximumScale` or `userScalable: false`.** This is a text editor; pinch-zoom is an accessibility requirement. Allow zoom.
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
## 7. Component-by-component change list
|
|
146
|
-
|
|
147
|
-
New shared hook: `src/hooks/use-is-mobile.ts` (create `src/hooks/`).
|
|
148
|
-
|
|
149
|
-
```ts
|
|
150
|
-
"use client";
|
|
151
|
-
import { useEffect, useState } from "react";
|
|
152
|
-
export function useIsMobile(query = "(max-width: 767px)") {
|
|
153
|
-
const [isMobile, setIsMobile] = useState(false); // SSR-safe default
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
const mql = window.matchMedia(query);
|
|
156
|
-
const on = () => setIsMobile(mql.matches);
|
|
157
|
-
on();
|
|
158
|
-
mql.addEventListener("change", on);
|
|
159
|
-
return () => mql.removeEventListener("change", on);
|
|
160
|
-
}, [query]);
|
|
161
|
-
return isMobile;
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
SSR returns `false` (desktop) to avoid hydration mismatch; corrects on mount. Acceptable: first paint is desktop layout, snaps to mobile in one frame.
|
|
166
|
-
|
|
167
|
-
| File | Change | Verdict gate |
|
|
168
|
-
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
|
|
169
|
-
| `src/app/layout.tsx` | Expand `viewport` export (section 6). Add safe-area CSS hook if needed. | always |
|
|
170
|
-
| `src/hooks/use-is-mobile.ts` | New hook. | new |
|
|
171
|
-
| `src/app/page.tsx` | Consume `useIsMobile()`. Force `sidebarCollapsed=true` on mobile mount. Sidebar `Card`: add `fixed inset-y-0 left-0 z-50 w-[85vw] max-w-[20rem] md:static md:z-auto md:w-auto` and gate inline `style={{width}}` to desktop only (apply `width` only when `!isMobile`). Render a `md:hidden` backdrop when sidebar open on mobile. Hamburger button shows when `isMobile | | sidebarCollapsed`. Add `md:hidden`AI-panel toggle button (top-right). Resize handle`hidden md:block`. Tree items `draggable={!isMobile}`. On mobile, selecting a file closes the sidebar drawer. `pr-11` doc-header offset when AI toggle visible. | `md:` + hook |
|
|
172
|
-
| `src/components/ai-panel/ai-panel.tsx` | Panel width `w-80` → `w-[90vw] max-w-sm md:w-80`. No other change. | `md:` only |
|
|
173
|
-
| `src/components/auth-settings-sheet.tsx` | Width `w-80` → `w-[90vw] max-w-sm md:w-80` (line ~158). | `md:` only |
|
|
174
|
-
| `src/components/editor/extensions/resizable-image.tsx` | Add `hover-reveal` class to the resize-handle wrapper(s) (lines ~100-107). Enlarge touch hit area on coarse pointers. | CSS `@media (hover:none)` |
|
|
175
|
-
| `src/components/editor/editor-toolbar.tsx` | Verify `overflow-x-scroll` still works with larger touch targets; if blanket 44px rule breaks it, keep toolbar buttons out of the touch-target scope. Likely no code change. | verify only |
|
|
176
|
-
| `src/app/globals.css` | Add `@media (hover: none)` always-visible rule for `.hover-reveal`; add scoped `touch-target` min-size rule; add safe-area padding utilities for notch (section 9). | CSS |
|
|
177
|
-
|
|
178
|
-
Sidebar row reveal wrappers (`page.tsx:1326`, `:1861`): add `hover-reveal` class.
|
|
179
|
-
|
|
180
|
-
---
|
|
181
|
-
|
|
182
|
-
## 8. State management
|
|
183
|
-
|
|
184
|
-
- **Mobile sidebar drawer** reuses the existing `sidebarCollapsed` local state. No new store. `collapsed === closed` on mobile; `collapsed === pushed-off` on desktop. Same boolean, different presentation (overlay vs in-flow), gated by `md:`/`useIsMobile`.
|
|
185
|
-
- **AI panel** keeps its zustand `isOpen`.
|
|
186
|
-
- **Mutual exclusion on mobile only:** opening the sidebar calls `useAIPanelStore.getState().close()`; opening the AI panel sets `sidebarCollapsed=true`. Only do this when `isMobile` (desktop can show both). Wire in the respective open handlers in `page.tsx`.
|
|
187
|
-
- Persist nothing new. `sidebarWidth` store stays for desktop.
|
|
188
|
-
|
|
189
|
-
---
|
|
190
|
-
|
|
191
|
-
## 9. Edge cases
|
|
192
|
-
|
|
193
|
-
- **Orientation change:** `matchMedia` change listener re-evaluates `isMobile`; rotating a phone to landscape ≥768px wide will switch to desktop layout. Acceptable and correct. Ensure the sidebar `style` width gate re-reads `isMobile` on render (it will, via hook).
|
|
194
|
-
- **On-screen keyboard:** editor focus raises the keyboard, shrinking visual viewport. Because the shell is `h-screen overflow-hidden` and the editor scrolls internally, the focused caret stays in view via native scroll-into-view. Do not add JS viewport-resize handling. Verify the sticky toolbar does not cover the caret; if it does, that is a follow-up, not a blocker.
|
|
195
|
-
- **Safe-area insets (notch)** given `viewportFit: cover`: add safe-area padding so chrome is not under the notch/home indicator. In `globals.css`:
|
|
196
|
-
```css
|
|
197
|
-
@supports (padding: max(0px)) {
|
|
198
|
-
.safe-top {
|
|
199
|
-
padding-top: max(0.5rem, env(safe-area-inset-top));
|
|
200
|
-
}
|
|
201
|
-
.safe-bottom {
|
|
202
|
-
padding-bottom: env(safe-area-inset-bottom);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
Apply `safe-top` to the mobile floating buttons / sidebar header, `safe-bottom` to the AI panel and any bottom-anchored sheet. Desktop unaffected (`env()` is 0).
|
|
207
|
-
- **`sidebarWidth` store on mobile:** ignored. The drawer uses `w-[85vw] max-w-[20rem]`. Do not write to the store from mobile; do not clamp. On return to desktop the stored width applies again unchanged.
|
|
208
|
-
|
|
209
|
-
---
|
|
210
|
-
|
|
211
|
-
## Implementation order
|
|
212
|
-
|
|
213
|
-
1. `useIsMobile` hook + viewport meta (`layout.tsx`). Foundational, zero risk.
|
|
214
|
-
2. Width caps: `ai-panel.tsx`, `auth-settings-sheet.tsx` (`w-[90vw] max-w-sm md:w-80`). Independent, low risk, immediate win.
|
|
215
|
-
3. globals.css: `.hover-reveal` always-visible rule, `touch-target` scoped rule, safe-area utilities.
|
|
216
|
-
4. Sidebar overlay + auto-collapse + backdrop + hamburger in `page.tsx`. Core layout change.
|
|
217
|
-
5. AI-panel mobile toggle + mutual exclusion in `page.tsx`.
|
|
218
|
-
6. Touch fixes: `draggable={!isMobile}`, resize handle `hidden md:block`, `hover-reveal` on reveal wrappers + resizable-image, tree row min-height.
|
|
219
|
-
7. Verify editor toolbar + tap-target scoping doesn't regress desktop.
|
|
220
|
-
|
|
221
|
-
## Out of scope
|
|
222
|
-
|
|
223
|
-
- Touch drag-and-drop reordering of the file tree (use context menu instead).
|
|
224
|
-
- Touch-resizable sidebar (fixed-width drawer on mobile).
|
|
225
|
-
- Bottom-sheet redesign of the AI panel (side sheet with width cap is enough).
|
|
226
|
-
- Any change to desktop layout, spacing, or behavior.
|
|
227
|
-
- Editor on-screen-keyboard caret-tracking JS.
|
|
228
|
-
- New global mobile header bar.
|
|
229
|
-
- PWA/manifest changes (already solid).
|
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
# Workspacing plan
|
|
2
|
-
|
|
3
|
-
> **Status: IMPLEMENTED.** Phases A–E complete. 294 tests pass, tsc clean.
|
|
4
|
-
> Key files: `src/lib/workspaces.ts`, `src/lib/workspace-context.ts`,
|
|
5
|
-
> `src/lib/auth/admin.ts`, `src/lib/workspace-client.ts`, workspace + admin
|
|
6
|
-
> APIs under `src/app/api/system/`, agent scope via `scope.workspaceId`.
|
|
7
|
-
> The legacy `src/lib/root-dir.ts` global is retained as a fallback source
|
|
8
|
-
> (synthetic default workspace) for `ROOT_DIR`/CLI/tests — not yet deleted.
|
|
9
|
-
|
|
10
|
-
## Goal
|
|
11
|
-
|
|
12
|
-
Let one running wiki-viewer serve multiple root directories ("workspaces")
|
|
13
|
-
concurrently, with zero state bleed between them. A user can have two tabs open
|
|
14
|
-
on two different workspaces at once. AI agents target a specific workspace.
|
|
15
|
-
|
|
16
|
-
Decisions (locked):
|
|
17
|
-
|
|
18
|
-
- **Selection**: URL/query carries `ws=<wsId>` for browser routes; agents send
|
|
19
|
-
an `X-Workspace: <wsId>` header. No process-global "current root".
|
|
20
|
-
- **Agents are workspace-scoped**: every agent request resolves a workspace and
|
|
21
|
-
is denied if it has no grant for it.
|
|
22
|
-
- **Isolation type**: organizational, not OS-level ACL. We add an explicit
|
|
23
|
-
per-workspace access list (which users / which agents may use a workspace).
|
|
24
|
-
We do NOT add per-user filesystem path restrictions on the directory browser.
|
|
25
|
-
|
|
26
|
-
## Why the current code bleeds
|
|
27
|
-
|
|
28
|
-
`src/lib/root-dir.ts` keeps a single `globalThis.__wikiRootDir`. Every route
|
|
29
|
-
calls `getRootDir()` / `safeRootPath()` against that one value, so:
|
|
30
|
-
|
|
31
|
-
- "Change" (`/api/system/clear-root`) wipes the root for every user/tab.
|
|
32
|
-
- Only one root can be active per process. No concurrency at all.
|
|
33
|
-
|
|
34
|
-
In-memory stores keyed by **relPath only** (collide across roots once more than
|
|
35
|
-
one root is live):
|
|
36
|
-
|
|
37
|
-
- `src/lib/proof/lease.ts` — `leases` + `generations` maps keyed by relPath.
|
|
38
|
-
- `src/lib/proof/idempotency.ts` — single LRU keyed by idempotency key.
|
|
39
|
-
- `src/lib/proof/mutex.ts` — in-process lock map keyed by relPath / mdPath.
|
|
40
|
-
- `src/lib/proof/file-lock.ts` — cross-process sentinel hashed from lockKey
|
|
41
|
-
(relPath). Two workspaces with `notes.md` share one lock.
|
|
42
|
-
|
|
43
|
-
Config is single-root shaped:
|
|
44
|
-
|
|
45
|
-
- `src/lib/config.ts` — one `lastOpenedPath`, one flat `pinnedPaths`.
|
|
46
|
-
|
|
47
|
-
Already correctly scoped (these take `rootDir` as a param, keep as-is):
|
|
48
|
-
|
|
49
|
-
- `src/lib/proof/sidecar.ts`, `activity.ts` — read/write under
|
|
50
|
-
`<rootDir>/.proof/`. Naturally isolated once `rootDir` is per-workspace.
|
|
51
|
-
|
|
52
|
-
Global, needs a `workspaceId` column / field:
|
|
53
|
-
|
|
54
|
-
- `src/lib/proof/audit.ts` — `agent_fs_audit` table mixes paths from all roots.
|
|
55
|
-
- `src/lib/proof/registry.ts` — `agents.json`; agent scope is path globs with
|
|
56
|
-
no workspace dimension.
|
|
57
|
-
|
|
58
|
-
## Target architecture
|
|
59
|
-
|
|
60
|
-
### 0. Admin role (new prerequisite)
|
|
61
|
-
|
|
62
|
-
No admin concept exists today: every signed-in user is equal, and the settings
|
|
63
|
-
routes just `requireUser`. Workspace access management is admin-gated, so we
|
|
64
|
-
introduce a minimal global admin role.
|
|
65
|
-
|
|
66
|
-
New `src/lib/auth/admin.ts`:
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
isAdmin(userId, email): Promise<boolean>
|
|
70
|
-
listAdmins(): Promise<string[]> // user ids
|
|
71
|
-
addAdmin(userId): Promise<void>
|
|
72
|
-
removeAdmin(userId): Promise<void> // refuse to remove the last admin
|
|
73
|
-
requireAdmin(req): Promise<{ ok: true; user } | { ok: false; status; code }>
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Resolution:
|
|
77
|
-
|
|
78
|
-
- `adminUserIds: string[]` stored in `config.json`.
|
|
79
|
-
- `WIKI_ADMIN_EMAILS` (csv) env: any matching signed-in user is treated as
|
|
80
|
-
admin regardless of config (seed/override, headless-friendly).
|
|
81
|
-
- **Bootstrap**: if `adminUserIds` is empty AND no `WIKI_ADMIN_EMAILS`, the
|
|
82
|
-
first user to sign up is written into `adminUserIds`. Hook the better-auth
|
|
83
|
-
signup path, or do it lazily on the first authenticated request when the
|
|
84
|
-
admin set is empty (behind the config write mutex). Log once.
|
|
85
|
-
- `requireAdmin` returns 403 `ADMIN_REQUIRED` for non-admins.
|
|
86
|
-
|
|
87
|
-
Admin management API: `GET/POST/DELETE /api/system/admins` (admin-gated; list
|
|
88
|
-
users, promote, demote). `DELETE` refuses to remove the last admin (lockout
|
|
89
|
-
guard). A small Admins section in the settings sheet lists users (from the
|
|
90
|
-
better-auth `user` table) with promote/demote toggles.
|
|
91
|
-
|
|
92
|
-
Non-admin users keep full file access to workspaces they are granted; admin is
|
|
93
|
-
only about creating workspaces and editing access lists (and promoting admins).
|
|
94
|
-
|
|
95
|
-
### 1. Workspace registry (replaces single rootDir)
|
|
96
|
-
|
|
97
|
-
New `src/lib/workspaces.ts`. Persisted in `~/.wiki-viewer/config.json` under a
|
|
98
|
-
new `workspaces` array. Each entry:
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
interface Workspace {
|
|
102
|
-
id: string; // "ws_" + short random; stable, URL-safe
|
|
103
|
-
name: string; // display label, defaults to basename(rootDir)
|
|
104
|
-
rootDir: string; // absolute, resolved
|
|
105
|
-
createdAt: string;
|
|
106
|
-
lastOpenedAt?: string;
|
|
107
|
-
pinnedPaths?: string[]; // moved here from the flat top-level list
|
|
108
|
-
createdBy?: string; // user id of the admin who created it
|
|
109
|
-
// access management (organizational, admin-edited only):
|
|
110
|
-
allowedUserIds?: string[]; // empty/undefined = any signed-in user
|
|
111
|
-
// agent grants live in the agent registry, keyed by workspaceId
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
API surface (pure functions, no globalThis root):
|
|
116
|
-
|
|
117
|
-
```ts
|
|
118
|
-
listWorkspaces(): Promise<Workspace[]>
|
|
119
|
-
getWorkspace(id): Promise<Workspace | null>
|
|
120
|
-
createWorkspace({ rootDir, name? }): Promise<Workspace>
|
|
121
|
-
renameWorkspace(id, name): Promise<void>
|
|
122
|
-
removeWorkspace(id): Promise<void> // does NOT touch the directory
|
|
123
|
-
setWorkspaceAccess(id, userIds): Promise<void> // admin-only, edits allowedUserIds
|
|
124
|
-
userCanAccess(ws, userId): boolean // admins always pass; else allowedUserIds check
|
|
125
|
-
resolveWorkspaceRoot(id): Promise<string|null>
|
|
126
|
-
safeWorkspacePath(rootDir, rel): string|null // the old safeRootPath logic
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
`safeRootPath` logic moves here as `safeWorkspacePath(rootDir, rel)` (takes an
|
|
130
|
-
explicit root instead of reading the global). Keep `root-dir.ts` as a thin
|
|
131
|
-
deprecated shim during migration if helpful, then delete.
|
|
132
|
-
|
|
133
|
-
### 2. Request-scoped workspace resolution
|
|
134
|
-
|
|
135
|
-
New `src/lib/workspace-context.ts`:
|
|
136
|
-
|
|
137
|
-
```ts
|
|
138
|
-
// Browser/session routes
|
|
139
|
-
async function resolveWorkspaceForUser(
|
|
140
|
-
req,
|
|
141
|
-
): Promise<
|
|
142
|
-
| { ok: true; ws: Workspace; rootDir: string }
|
|
143
|
-
| { ok: false; status: number; code: string }
|
|
144
|
-
>;
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Resolution order for browser routes:
|
|
148
|
-
|
|
149
|
-
1. `ws` query param (preferred) or `x-workspace` header.
|
|
150
|
-
2. If absent, fall back to the user's `lastOpenedAt` workspace (back-compat,
|
|
151
|
-
single-workspace users never notice).
|
|
152
|
-
3. 400 `WORKSPACE_REQUIRED` if none and more than one workspace exists.
|
|
153
|
-
|
|
154
|
-
Then enforce `userCanAccess(ws, user.id)` → 403 `WORKSPACE_FORBIDDEN`.
|
|
155
|
-
|
|
156
|
-
For agent routes, `checkAuth` (in `src/lib/proof/auth.ts`) additionally reads
|
|
157
|
-
`X-Workspace`, looks up the workspace, and checks the agent's grant for it
|
|
158
|
-
(see §5). Returns `{ ok, agent, workspace, rootDir }`.
|
|
159
|
-
|
|
160
|
-
### 3. Namespacing the in-memory stores
|
|
161
|
-
|
|
162
|
-
Every store keyed by relPath becomes keyed by `wsId + "\0" + relPath` (the
|
|
163
|
-
workspace id is a stable prefix, NUL separator avoids collisions).
|
|
164
|
-
|
|
165
|
-
- `lease.ts`: `setLease`, `hasActiveLease`, `clearLease`, `leaseGeneration`
|
|
166
|
-
gain a `wsId` first arg → internal key `${wsId}\0${relPath}`.
|
|
167
|
-
`computeCollabState(rootDir, relPath)` already has rootDir; add `wsId` (or
|
|
168
|
-
derive a key from rootDir — but explicit wsId is cleaner). Update
|
|
169
|
-
`collab-state.ts` and `presence/route.ts` callers.
|
|
170
|
-
- `mutex.ts` / `file-lock.ts`: callers pass a key already; change every call
|
|
171
|
-
site to prefix with `wsId` (e.g. `withFileMutex(`${wsId}\0${relPath}`, ...)`).
|
|
172
|
-
Cross-process sentinel hash then differs per workspace automatically.
|
|
173
|
-
- `idempotency.ts`: prefix the key with `wsId` at the call site
|
|
174
|
-
(`events/[...path]/route.ts`, `files/[...path]/route.ts`).
|
|
175
|
-
- `registry.ts` `lastSeenWriteAt` throttle map is keyed by agentId — fine, no
|
|
176
|
-
change.
|
|
177
|
-
|
|
178
|
-
### 4. Threading rootDir through routes (29 files)
|
|
179
|
-
|
|
180
|
-
Pattern, every session route:
|
|
181
|
-
|
|
182
|
-
```ts
|
|
183
|
-
const ctx = await resolveWorkspaceForUser(request);
|
|
184
|
-
if (!ctx.ok)
|
|
185
|
-
return NextResponse.json({ error: ctx.code }, { status: ctx.status });
|
|
186
|
-
const { ws, rootDir } = ctx;
|
|
187
|
-
const filePath = safeWorkspacePath(rootDir, rel);
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
Replace all `getRootDir()` → `ctx.rootDir`, `safeRootPath(rel)` →
|
|
191
|
-
`safeWorkspacePath(rootDir, rel)`, `isRootDirSet()` → existence of ctx.
|
|
192
|
-
|
|
193
|
-
Route inventory (all under `src/app/api`):
|
|
194
|
-
|
|
195
|
-
- `wiki/route.ts`, `wiki/content/route.ts`, `wiki/page/route.ts`,
|
|
196
|
-
`wiki/slugs/route.ts`, `wiki/file/route.ts`, `wiki/new-file/route.ts`,
|
|
197
|
-
`wiki/folder/route.ts`, `wiki/move/route.ts`, `wiki/upload/route.ts`,
|
|
198
|
-
`wiki/download/route.ts`, `wiki/app/route.ts`, `wiki/watch/route.ts`,
|
|
199
|
-
`wiki/presence/route.ts`
|
|
200
|
-
- `assets/[...path]/route.ts`, `upload/[...path]/route.ts`
|
|
201
|
-
- `system/reveal/route.ts`
|
|
202
|
-
- agent: `fs/file`, `fs/ls`, `fs/move`, `fs/search`, `files`, `events`,
|
|
203
|
-
`sidecar`, `activity`, `settings`, `internal/span`
|
|
204
|
-
|
|
205
|
-
`watch/route.ts` (SSE / chokidar): watcher must watch `ws.rootDir`. Each EventSource
|
|
206
|
-
connection is per-workspace; the client opens `/api/wiki/watch?ws=<id>`.
|
|
207
|
-
|
|
208
|
-
### 5. Workspace-scoped agents
|
|
209
|
-
|
|
210
|
-
Agent grants gain a workspace dimension. In `registry.ts`:
|
|
211
|
-
|
|
212
|
-
```ts
|
|
213
|
-
interface AgentScope {
|
|
214
|
-
workspaceId: string; // NEW — the workspace this grant applies to
|
|
215
|
-
paths: string[];
|
|
216
|
-
ops: Array<"read" | "mutate" | "delete">;
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
(An agent id may appear once per workspace, or scope becomes an array of
|
|
221
|
-
per-workspace grants. Simpler: one registry row per (agentId, workspaceId).
|
|
222
|
-
Recommend: keep `Agent.id` unique, add `workspaceId` to the record; an agent
|
|
223
|
-
that needs two workspaces registers twice.)
|
|
224
|
-
|
|
225
|
-
- `register` route: request body includes target `workspaceId`. Approval UI in
|
|
226
|
-
the AI panel shows which workspace the request is for.
|
|
227
|
-
- `checkAuth`: resolve `X-Workspace`, then `enforceScope` also checks
|
|
228
|
-
`agent.scope.workspaceId === ws.id`. Mismatch → 403.
|
|
229
|
-
- Browser session users (`user:<id>`) are synthesized in `checkAuth` with full
|
|
230
|
-
scope; gate them with `userCanAccess(ws, userId)` instead.
|
|
231
|
-
- `audit.ts`: add `workspace_id` column to `agent_fs_audit`
|
|
232
|
-
(`ALTER TABLE ... ADD COLUMN`; tolerate existing DBs). Write it on every row.
|
|
233
|
-
|
|
234
|
-
### 6. Config migration
|
|
235
|
-
|
|
236
|
-
On first read of `config.json` with the new code:
|
|
237
|
-
|
|
238
|
-
- If `workspaces` is absent but `lastOpenedPath` exists, synthesize one
|
|
239
|
-
workspace `{ id: ws_*, rootDir: lastOpenedPath, name: basename, pinnedPaths:
|
|
240
|
-
config.pinnedPaths }` and mark it lastOpened. Keep old fields for one release
|
|
241
|
-
for rollback safety; new writes use `workspaces`.
|
|
242
|
-
- `agents.json`: existing agents have no `workspaceId`. On load, backfill them
|
|
243
|
-
to the migrated default workspace id (and log once, mirroring the existing
|
|
244
|
-
legacy `ownerUserId` handling in the README checklist).
|
|
245
|
-
|
|
246
|
-
### 7. API: workspace management routes
|
|
247
|
-
|
|
248
|
-
New under `src/app/api/system/workspaces`:
|
|
249
|
-
|
|
250
|
-
- `GET /api/system/workspaces` — list workspaces visible to the user
|
|
251
|
-
(filtered by `userCanAccess`).
|
|
252
|
-
- `POST /api/system/workspaces` — create `{ rootDir, name? }` (validates dir
|
|
253
|
-
exists, isDirectory). Replaces `set-root` semantics.
|
|
254
|
-
- `PATCH /api/system/workspaces/<id>` — rename / pin-unpin allowed for any
|
|
255
|
-
user with access; **editing `allowedUserIds` is admin-only** (enforced field
|
|
256
|
-
by field in the handler).
|
|
257
|
-
- `DELETE /api/system/workspaces/<id>` — **admin-only**. Unregister (directory
|
|
258
|
-
untouched).
|
|
259
|
-
|
|
260
|
-
Workspace create (`POST`) is **admin-only** and sets `createdBy`. `GET` is open
|
|
261
|
-
to any signed-in user but filtered: admins see all, others see only workspaces
|
|
262
|
-
they can access. Admins management: `GET/POST/DELETE /api/system/admins`.
|
|
263
|
-
|
|
264
|
-
Deprecate / re-point: `set-root`, `clear-root`, `root-status`, `pins`. Keep
|
|
265
|
-
thin back-compat shims that operate on "the user's current/last workspace" so
|
|
266
|
-
nothing 404s mid-migration, then remove in a follow-up.
|
|
267
|
-
|
|
268
|
-
### 8. Client (browser)
|
|
269
|
-
|
|
270
|
-
- `src/app/page.tsx`: hold `activeWorkspaceId` in state. Read from URL `?ws=`
|
|
271
|
-
first, else last opened. Put `ws` into every fetch (`/api/wiki?ws=...`,
|
|
272
|
-
watch URL, presence body). Cleanest: a small `wsFetch(path, init)` helper /
|
|
273
|
-
thin client store (`workspace-store.ts`) that injects `ws` automatically so
|
|
274
|
-
we are not editing dozens of `fetch` calls by hand.
|
|
275
|
-
- Sidebar footer "Change" → opens a workspace switcher (list of workspaces the
|
|
276
|
-
user may access). "Add workspace" (reuses `DirPicker`) is shown **only to
|
|
277
|
-
admins**. Switching navigates to `?ws=<id>` (and `path` resets). Two tabs
|
|
278
|
-
with different `?ws=` are fully independent.
|
|
279
|
-
- `DirPicker` (`src/components/dir-picker.tsx`): admin-only path. "Select" now
|
|
280
|
-
POSTs `/api/system/workspaces` (create) instead of `set-root`, then routes
|
|
281
|
-
to `?ws=<newId>`. Pins move under the chosen workspace.
|
|
282
|
-
- Settings sheet, admins only: an **Admins** section (promote/demote users)
|
|
283
|
-
and a per-workspace **Access** editor (`allowedUserIds`). The client reads an
|
|
284
|
-
`isAdmin` flag (from `/api/system/admins` or root-status) to gate this UI.
|
|
285
|
-
- `wiki-slugs-store`, `editor-store`, `tree-store`: any cached state that is
|
|
286
|
-
keyed by path must also be reset on workspace switch (cheap: remount the
|
|
287
|
-
page subtree on `ws` change via a React `key={activeWorkspaceId}`).
|
|
288
|
-
|
|
289
|
-
### 9. CLI / startup (`bin/wiki-viewer.js`, `ROOT_DIR`)
|
|
290
|
-
|
|
291
|
-
- `ROOT_DIR` (and `wiki-viewer ~/dir`) still works: on boot, if no workspaces
|
|
292
|
-
exist, auto-create one from `ROOT_DIR` and mark it default/lastOpened. So the
|
|
293
|
-
zero-config single-dir path is unchanged for existing users.
|
|
294
|
-
- README "switch the served directory" copy updates to mention multiple
|
|
295
|
-
concurrent workspaces.
|
|
296
|
-
|
|
297
|
-
## Isolation guarantees (the "no bleed" contract)
|
|
298
|
-
|
|
299
|
-
| Surface | Before | After |
|
|
300
|
-
| -------------------- | ----------------- | ------------------------------- |
|
|
301
|
-
| Root dir | one global | per request, from `ws` |
|
|
302
|
-
| Sidecars / activity | `<root>/.proof/` | unchanged (already per-root) |
|
|
303
|
-
| Edit leases | key=relPath | key=`wsId\0relPath` |
|
|
304
|
-
| In-proc + file locks | key=relPath | key=`wsId\0relPath` |
|
|
305
|
-
| Idempotency LRU | key=idemKey | key=`wsId\0idemKey` |
|
|
306
|
-
| Audit DB | path only | `+ workspace_id` column |
|
|
307
|
-
| Agents | global path globs | `+ workspaceId` grant, enforced |
|
|
308
|
-
| Access control | any signed-in | admin-set `allowedUserIds`/ws |
|
|
309
|
-
|
|
310
|
-
Two workspaces sharing the same relative path (`notes.md`) can be edited
|
|
311
|
-
simultaneously without lock contention, lease confusion, or idempotency
|
|
312
|
-
cross-talk.
|
|
313
|
-
|
|
314
|
-
## Testing
|
|
315
|
-
|
|
316
|
-
Extend `src/tests/proof/*`:
|
|
317
|
-
|
|
318
|
-
- Two-workspace lease test: lease on `(wsA, x.md)` does not make
|
|
319
|
-
`(wsB, x.md)` active.
|
|
320
|
-
- Mutex/lock test: concurrent writes to same relPath in two workspaces do not
|
|
321
|
-
serialize against each other (or at least do not corrupt).
|
|
322
|
-
- Idempotency: same key in two workspaces are independent.
|
|
323
|
-
- Scope test: agent granted `wsA` is 403 on `X-Workspace: wsB`.
|
|
324
|
-
- Access test: user not in `allowedUserIds` is 403; admin always passes.
|
|
325
|
-
- Admin test: non-admin POST /workspaces is 403; last-admin DELETE refused;
|
|
326
|
-
first-signup bootstrap promotes one admin.
|
|
327
|
-
- Migration test: old config.json (lastOpenedPath + pinnedPaths) yields one
|
|
328
|
-
workspace; old agents.json backfills workspaceId.
|
|
329
|
-
- Update existing tests: `setRootDir(tmpRoot)` calls in test setup become
|
|
330
|
-
"create a workspace + resolve it" (helper).
|
|
331
|
-
|
|
332
|
-
## Rollout / sequencing
|
|
333
|
-
|
|
334
|
-
0. Admin role: `auth/admin.ts`, `/api/system/admins`, bootstrap, settings UI
|
|
335
|
-
section. Independent, shippable alone.
|
|
336
|
-
1. `workspaces.ts` + `workspace-context.ts` + config migration (no route
|
|
337
|
-
changes yet; default workspace mirrors old behavior).
|
|
338
|
-
2. Namespacing stores (lease, mutex, file-lock, idempotency) with `wsId` arg,
|
|
339
|
-
defaulting to the single default workspace so nothing breaks.
|
|
340
|
-
3. Thread `ctx.rootDir` through all 29 routes; delete `getRootDir` global.
|
|
341
|
-
4. Agent workspace scoping + audit column.
|
|
342
|
-
5. Workspace management API + client switcher + DirPicker repoint.
|
|
343
|
-
6. Tests, README, CLI copy.
|
|
344
|
-
|
|
345
|
-
Steps 1-2 are non-breaking and shippable alone. Step 3 is the big mechanical
|
|
346
|
-
diff (good candidate for a `worker` pass with explicit outcomes per route).
|
|
347
|
-
|
|
348
|
-
## Open questions / risks
|
|
349
|
-
|
|
350
|
-
- **`pnpm-workspace.yaml` naming clash**: this repo is itself a pnpm workspace.
|
|
351
|
-
Use "workspace" in UI/API, but consider an internal code name like `space` or
|
|
352
|
-
keep `workspace` and just be careful in docs. Low risk, naming only.
|
|
353
|
-
- **SSE watcher fan-out**: one chokidar watcher per (connection, workspace).
|
|
354
|
-
Many open tabs on the same workspace = many watchers. Acceptable now;
|
|
355
|
-
optimize later with a shared per-workspace watcher + ref count if needed.
|
|
356
|
-
- **Admin bootstrap timing**: writing the first user into `adminUserIds` must
|
|
357
|
-
happen exactly once. Better-auth has no built-in "first user" hook, so
|
|
358
|
-
either tap the signup callback or do it lazily on the first authenticated
|
|
359
|
-
request when the admin set is empty (race-safe behind the config write
|
|
360
|
-
mutex). Decide during step 0.
|
|
361
|
-
- **`allowedUserIds` default**: empty = any signed-in user (matches today's
|
|
362
|
-
behavior). Admins tighten per workspace via the Access editor.
|
|
363
|
-
- Removing a workspace leaves `.proof/` and `.locks/` sentinels on disk; fine
|
|
364
|
-
(directory untouched by design), but document it.
|