wiki-viewer 1.7.2 → 1.8.0
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/app-path-routes-manifest.json +6 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/routes-manifest.json +40 -0
- 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/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- package/.next/standalone/.next/server/app/api/agent/activity/route.js +6 -6
- package/.next/standalone/.next/server/app/api/agent/activity/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/admin/agents/[agentId]/revoke/route.js +6 -6
- package/.next/standalone/.next/server/app/api/agent/admin/agents/[agentId]/revoke/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/admin/agents/route.js +6 -6
- package/.next/standalone/.next/server/app/api/agent/admin/agents/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/approve/route.js +7 -7
- package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/approve/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/deny/route.js +5 -5
- package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/deny/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/admin/registrations/route.js +5 -5
- package/.next/standalone/.next/server/app/api/agent/admin/registrations/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js +8 -7
- 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 +9 -8
- 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 +8 -7
- 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 +7 -7
- 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 +6 -5
- 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 +7 -6
- 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 +6 -5
- 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 +7 -7
- 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 +8 -7
- package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js +2 -2
- package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/assets/[...path]/route.js +8 -2
- package/.next/standalone/.next/server/app/api/assets/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/[...all]/route.js +5 -5
- package/.next/standalone/.next/server/app/api/auth/[...all]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/admins/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/system/admins/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/system/admins/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/system/admins/route.js +14 -0
- package/.next/standalone/.next/server/app/api/system/admins/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/system/admins/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/system/admins/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/system/auth-config/route.js +6 -6
- package/.next/standalone/.next/server/app/api/system/auth-config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/auth-settings/route.js +4 -4
- package/.next/standalone/.next/server/app/api/system/auth-settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/browse/route.js +4 -4
- package/.next/standalone/.next/server/app/api/system/browse/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/clear-root/route.js +5 -5
- package/.next/standalone/.next/server/app/api/system/clear-root/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/config/route.js +6 -6
- package/.next/standalone/.next/server/app/api/system/config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/pins/route.js +4 -4
- package/.next/standalone/.next/server/app/api/system/pins/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/reveal/route.js +6 -6
- package/.next/standalone/.next/server/app/api/system/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/root-status/route.js +5 -5
- package/.next/standalone/.next/server/app/api/system/root-status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/set-root/route.js +6 -6
- package/.next/standalone/.next/server/app/api/system/set-root/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/users/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/system/users/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/system/users/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/system/users/route.js +14 -0
- package/.next/standalone/.next/server/app/api/system/users/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/system/users/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/system/users/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js +14 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js +14 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/route.js +14 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/system/workspaces/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/upload/[...path]/route.js +8 -2
- 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 +5 -5
- package/.next/standalone/.next/server/app/api/wiki/app/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/content/route.js +6 -6
- 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 +7 -7
- 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 +8 -2
- 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 +6 -6
- package/.next/standalone/.next/server/app/api/wiki/folder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/move/route.js +6 -6
- 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 +7 -7
- package/.next/standalone/.next/server/app/api/wiki/new-file/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/page/route.js +7 -7
- 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 +7 -7
- 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 +6 -6
- package/.next/standalone/.next/server/app/api/wiki/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/search/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/wiki/search/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/wiki/search/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/wiki/search/route.js +15 -0
- package/.next/standalone/.next/server/app/api/wiki/search/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/wiki/search/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/wiki/search/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/wiki/slugs/route.js +6 -6
- 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 +7 -7
- 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 +7 -9
- 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 +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/signin/page.js +1 -1
- package/.next/standalone/.next/server/app/signin/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/signin/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +6 -0
- package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0d2jbo5.js +1 -1
- package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0ytral2.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-244bv._.js +16 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0xz-_0v._.js → [root-of-the-server]__0-inabx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-ao6a_._.js → [root-of-the-server]__0.-9jt0._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0~-r-8f._.js → [root-of-the-server]__00c1-dr._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00lep-u._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00mtevo._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00qu86p._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__012753j._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01hp8ax._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0jhr.ex._.js → [root-of-the-server]__02n6z97._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__036mhjp._.js +5 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__03p0ob1._.js +16 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05t~v6l._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__068ni_k._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__082nhm6._.js → [root-of-the-server]__07vphbd._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0pg6dn-._.js → [root-of-the-server]__08lkuwa._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__09cqzem._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0p_ox-k._.js → [root-of-the-server]__09k4q9e._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0_601._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ae3dk6._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0s28.4-._.js → [root-of-the-server]__0af-x45._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cqu-wm._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d75a5q._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d7x3e1._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d7zxm_._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e7vs98._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fxzg4v._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0gda_iu._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0hu~q20._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jk1uf4._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l-8clq._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0lhka0x._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0kdn1le._.js → [root-of-the-server]__0na11h0._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0nd.b_c._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0otb8ra._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0p0-p__._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0piofm3._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rrf-jr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s6_lzw._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sh13v5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ta~flu._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0v4im4p._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0qn8e~s._.js → [root-of-the-server]__0v_kyp-._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vatqe-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vdb2nr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wdh63w._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z3wg9x._.js +12 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0mzo03s._.js → [root-of-the-server]__0~.-3yk._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__123._fe._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__12800b1._.js → [root-of-the-server]__12ee_s6._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__12onytt._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__04udrya._.js → [root-of-the-server]__12q8ch8._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1377.79._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1378-ox._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13aid91._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13hp4tw._.js +12 -0
- package/.next/standalone/.next/server/chunks/_079xuln._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0dr1r1g._.js +452 -0
- package/.next/standalone/.next/server/chunks/_101exke._.js +452 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_admins_route_actions_0dsm7cx.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_users_route_actions_0whj5cy.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_workspaces_[id]_open_route_actions_1087xu7.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_workspaces_[id]_route_actions_0~h7-8s.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_workspaces_route_actions_0mkalc3.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_wiki_search_route_actions_0inxu93.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules__pnpm_0aj5g-w._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_lib_search_0d-_j3u._.js +46 -0
- package/.next/standalone/.next/server/chunks/src_lib_search_0ymmp0c._.js +46 -0
- package/.next/standalone/.next/server/chunks/ssr/{0~w0_katex_dist_katex_mjs_0b8ra51._.js → 0~w0_katex_dist_katex_mjs_04mhawr._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_erDiagram-TEJ5UH35_mjs_0v6rnqy._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_mermaid_core_mjs_0hj_n-~._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0btcd4o._.js → [root-of-the-server]__02twzph._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ytvlzs._.js → [root-of-the-server]__09~x2v~._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0pqaawz._.js +18 -18
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0c71i8a._.js → node_modules__pnpm_0.96tmo._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0lm8e2c._.js → node_modules__pnpm_011vy_q._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0ezm2us._.js → node_modules__pnpm_01o_5qt._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0j3mvgg._.js → node_modules__pnpm_04n0h6.._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0~ln0by._.js → node_modules__pnpm_093dp2q._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_11e~j49._.js → node_modules__pnpm_09ej62_._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0de8xt-._.js → node_modules__pnpm_09ljbdz._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0-x6zex._.js → node_modules__pnpm_09~_ob2._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0yq5z.h._.js → node_modules__pnpm_0a01ng7._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0kwc-vs._.js → node_modules__pnpm_0b8qx0g._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0hrbz84._.js → node_modules__pnpm_0bbc---._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0hobcbs._.js → node_modules__pnpm_0c.309.._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0rw91-f._.js → node_modules__pnpm_0c8aunj._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0o.c1z8._.js → node_modules__pnpm_0hk6xys._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0dboa_e._.js → node_modules__pnpm_0hlzy8f._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0j53hti._.js → node_modules__pnpm_0nw5ou2._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0xrmzh1._.js → node_modules__pnpm_0o.dn8o._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0cxwhfd._.js → node_modules__pnpm_0s0ur~d._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_07rxv2x._.js → node_modules__pnpm_0shlang._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0i.pw_m._.js → node_modules__pnpm_0sq3c2r._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0ajzar9._.js → node_modules__pnpm_0t9jb7d._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_012nt.s._.js → node_modules__pnpm_0wmx9kq._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0z~h2ow._.js → node_modules__pnpm_0xfx0sf._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0cbsnwf._.js → node_modules__pnpm_0yiz.-t._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0bum3-3._.js → node_modules__pnpm_11sut1u._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0mdo4i3._.js → node_modules__pnpm_124u4o2._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0il8j.j._.js → node_modules__pnpm_13tajgx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/src_app_signin_00yk0vm._.js +2 -2
- package/.next/standalone/.next/server/edge/chunks/{08yl_next_dist_esm_build_templates_edge-wrapper_0o9h3w_.js → 08yl_next_dist_esm_build_templates_edge-wrapper_0xbpts6.js} +2 -2
- package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__0.pmgt2._.js +3 -0
- package/.next/standalone/.next/server/functions-config-manifest.json +6 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +8 -8
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/0c_~zevf2n0r4.css +2 -0
- package/.next/standalone/.next/static/chunks/0l4842ez-0_66.js +129 -0
- package/.next/standalone/README.md +46 -2
- package/.next/standalone/docs/workspacing-plan.md +364 -0
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/src/app/api/agent/activity/route.ts +9 -8
- package/.next/standalone/src/app/api/agent/events/[...path]/route.ts +20 -8
- package/.next/standalone/src/app/api/agent/files/[...path]/route.ts +16 -9
- package/.next/standalone/src/app/api/agent/fs/file/[...path]/route.ts +28 -18
- package/.next/standalone/src/app/api/agent/fs/ls/[[...path]]/route.ts +12 -7
- package/.next/standalone/src/app/api/agent/fs/move/route.ts +16 -11
- package/.next/standalone/src/app/api/agent/fs/search/route.ts +49 -13
- package/.next/standalone/src/app/api/agent/internal/span/route.ts +9 -5
- package/.next/standalone/src/app/api/agent/register/route.ts +9 -1
- package/.next/standalone/src/app/api/agent/settings/route.ts +7 -3
- package/.next/standalone/src/app/api/agent/sidecar/[...path]/route.ts +8 -4
- package/.next/standalone/src/app/api/assets/[...path]/route.ts +9 -4
- package/.next/standalone/src/app/api/system/admins/route.ts +103 -0
- package/.next/standalone/src/app/api/system/reveal/route.ts +7 -6
- package/.next/standalone/src/app/api/system/set-root/route.ts +24 -1
- package/.next/standalone/src/app/api/system/users/route.ts +120 -0
- package/.next/standalone/src/app/api/system/workspaces/[id]/open/route.ts +34 -0
- package/.next/standalone/src/app/api/system/workspaces/[id]/route.ts +74 -0
- package/.next/standalone/src/app/api/system/workspaces/route.ts +67 -0
- package/.next/standalone/src/app/api/upload/[...path]/route.ts +10 -5
- package/.next/standalone/src/app/api/wiki/app/route.ts +10 -9
- package/.next/standalone/src/app/api/wiki/content/route.ts +14 -14
- package/.next/standalone/src/app/api/wiki/download/route.ts +7 -6
- package/.next/standalone/src/app/api/wiki/file/route.ts +7 -2
- package/.next/standalone/src/app/api/wiki/folder/route.ts +6 -5
- package/.next/standalone/src/app/api/wiki/move/route.ts +7 -7
- package/.next/standalone/src/app/api/wiki/new-file/route.ts +7 -6
- package/.next/standalone/src/app/api/wiki/page/route.ts +6 -5
- package/.next/standalone/src/app/api/wiki/presence/route.ts +8 -7
- package/.next/standalone/src/app/api/wiki/route.ts +13 -11
- package/.next/standalone/src/app/api/wiki/search/route.ts +44 -0
- package/.next/standalone/src/app/api/wiki/slugs/route.ts +8 -8
- package/.next/standalone/src/app/api/wiki/upload/route.ts +6 -5
- package/.next/standalone/src/app/api/wiki/watch/route.ts +16 -40
- package/.next/standalone/src/app/globals.css +484 -484
- package/.next/standalone/src/app/page.tsx +163 -49
- package/.next/standalone/src/components/auth-settings-sheet.tsx +171 -2
- package/.next/standalone/src/components/dir-picker.tsx +8 -6
- package/.next/standalone/src/components/editor/comment-thread.tsx +2 -1
- package/.next/standalone/src/components/editor/csv-viewer.tsx +3 -2
- package/.next/standalone/src/components/editor/editor-toolbar.tsx +2 -2
- package/.next/standalone/src/components/editor/editor.tsx +6 -5
- package/.next/standalone/src/components/editor/file-fallback-viewer.tsx +3 -2
- package/.next/standalone/src/components/editor/folder-index.tsx +2 -1
- package/.next/standalone/src/components/editor/image-viewer.tsx +2 -1
- package/.next/standalone/src/components/editor/media-popover.tsx +2 -1
- package/.next/standalone/src/components/editor/media-viewer.tsx +2 -1
- package/.next/standalone/src/components/editor/mermaid-viewer.tsx +3 -2
- package/.next/standalone/src/components/editor/node-app-viewer.tsx +4 -3
- package/.next/standalone/src/components/editor/notebook-viewer.tsx +3 -2
- package/.next/standalone/src/components/editor/office/docx-viewer.tsx +2 -1
- package/.next/standalone/src/components/editor/office/office-chrome.tsx +3 -2
- package/.next/standalone/src/components/editor/office/pptx-viewer.tsx +2 -1
- package/.next/standalone/src/components/editor/office/xlsx-viewer.tsx +2 -1
- package/.next/standalone/src/components/editor/pdf-viewer.tsx +2 -1
- package/.next/standalone/src/components/editor/proof-span-popover.tsx +2 -1
- package/.next/standalone/src/components/editor/source-viewer.tsx +3 -2
- package/.next/standalone/src/components/editor/suggest-edit-popover.tsx +2 -1
- package/.next/standalone/src/components/editor/suggestion-card.tsx +2 -1
- package/.next/standalone/src/components/editor/website-viewer.tsx +2 -1
- package/.next/standalone/src/components/editor/wiki-link-create-dialog.tsx +2 -1
- package/.next/standalone/src/components/search/search-command-dialog.tsx +109 -0
- package/.next/standalone/src/components/search/sidebar-search-box.tsx +121 -0
- package/.next/standalone/src/components/search/snippet-text.tsx +48 -0
- package/.next/standalone/src/components/ui/command.tsx +3 -2
- package/.next/standalone/src/lib/auth/admin.ts +112 -0
- package/.next/standalone/src/lib/auth/server.ts +13 -2
- package/.next/standalone/src/lib/config.ts +56 -4
- package/.next/standalone/src/lib/proof/audit.ts +19 -10
- package/.next/standalone/src/lib/proof/auth.ts +11 -0
- package/.next/standalone/src/lib/proof/collab-state.ts +4 -2
- package/.next/standalone/src/lib/proof/file-lock.ts +5 -0
- package/.next/standalone/src/lib/proof/idempotency.ts +5 -0
- package/.next/standalone/src/lib/proof/lease.ts +40 -27
- package/.next/standalone/src/lib/proof/mutex.ts +4 -0
- package/.next/standalone/src/lib/proof/raw-fs.ts +2 -3
- package/.next/standalone/src/lib/proof/registry.ts +5 -0
- package/.next/standalone/src/lib/search/indexable-exts.ts +25 -0
- package/.next/standalone/src/lib/search/indexer.ts +567 -0
- package/.next/standalone/src/lib/search/sanitize.ts +35 -0
- package/.next/standalone/src/lib/search/search-db.ts +57 -0
- package/.next/standalone/src/lib/search/watcher-pool.ts +79 -0
- package/.next/standalone/src/lib/workspace-client.ts +82 -0
- package/.next/standalone/src/lib/workspace-context.ts +142 -0
- package/.next/standalone/src/lib/workspaces.ts +196 -0
- package/.next/standalone/src/stores/ai-panel-store.ts +3 -2
- package/.next/standalone/src/stores/editor-store.ts +4 -3
- package/.next/standalone/src/stores/proof-store.ts +4 -3
- package/.next/standalone/src/stores/search-store.ts +86 -0
- package/.next/standalone/src/stores/wiki-slugs-store.ts +2 -1
- package/.next/standalone/src/tests/proof/admin-create-user.test.ts +43 -0
- package/.next/standalone/src/tests/proof/agent-workspace-scope.test.ts +134 -0
- package/.next/standalone/src/tests/proof/collab-state.test.ts +43 -41
- package/.next/standalone/src/tests/proof/preload.ts +21 -0
- package/.next/standalone/src/tests/proof/search-index.test.ts +339 -0
- package/.next/standalone/src/tests/proof/workspace-isolation.test.ts +87 -0
- package/.next/standalone/src/tests/proof/workspaces-api.test.ts +227 -0
- package/.next/standalone/tsconfig.tsbuildinfo +1 -1
- package/README.md +46 -2
- package/package.json +2 -2
- package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0o~h4zq.js +0 -5
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0.hb9q2._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0.uux34._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0092hkp._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05f6x0i._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05idwm9._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06-dtd0._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06sek6_._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07fpqks._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__094q5bj._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0awzyon._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d_da6q._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0hhhvm7._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kk26wc._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0km8suj._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kur4ro._.js +0 -15
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0mkwoul._.js +0 -12
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qc2~6l._.js +0 -15
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qyd-o8._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rkve~v._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rzo40z._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sjocwy._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ssj9~d._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s~i040._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t220d-._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t5mg.n._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0trsqf4._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xq124z._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__106uw_c._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10w2q~o._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13w-0iy._.js +0 -3
- package/.next/standalone/.next/server/chunks/src_lib_auth_server_ts_00f4e0h._.js +0 -452
- package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__0ug2rvl._.js +0 -3
- package/.next/standalone/.next/static/chunks/0vwkqw_rofm-c.js +0 -129
- package/.next/standalone/.next/static/chunks/0zay6349~v_wc.css +0 -2
- /package/.next/standalone/.next/static/{0XvNCOSEq7cbARTLtAolL → t7B6nXaoD1jo9f61t3aeK}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{0XvNCOSEq7cbARTLtAolL → t7B6nXaoD1jo9f61t3aeK}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{0XvNCOSEq7cbARTLtAolL → t7B6nXaoD1jo9f61t3aeK}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Persistent sidebar search box. Renders an inline results dropdown under the
|
|
5
|
+
* input. Shares state with the cmd+k palette via the search store.
|
|
6
|
+
* Supports keyboard navigation: ArrowUp / ArrowDown to move, Enter to open,
|
|
7
|
+
* Escape to clear.
|
|
8
|
+
*/
|
|
9
|
+
import { useEffect, useRef, useState } from "react";
|
|
10
|
+
import { Search } from "lucide-react";
|
|
11
|
+
import { useSearchStore } from "@/stores/search-store";
|
|
12
|
+
import { SnippetText } from "./snippet-text";
|
|
13
|
+
|
|
14
|
+
export function SidebarSearchBox({
|
|
15
|
+
onOpenFile,
|
|
16
|
+
}: {
|
|
17
|
+
onOpenFile: (relPath: string) => void;
|
|
18
|
+
}) {
|
|
19
|
+
const query = useSearchStore((s) => s.query);
|
|
20
|
+
const setQuery = useSearchStore((s) => s.setQuery);
|
|
21
|
+
const results = useSearchStore((s) => s.results);
|
|
22
|
+
const loading = useSearchStore((s) => s.loading);
|
|
23
|
+
const search = useSearchStore((s) => s.search);
|
|
24
|
+
const clear = useSearchStore((s) => s.clear);
|
|
25
|
+
|
|
26
|
+
const [active, setActive] = useState(0);
|
|
27
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
28
|
+
|
|
29
|
+
// Reset / clamp the highlighted row whenever the result set changes.
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
setActive(0);
|
|
32
|
+
}, [results]);
|
|
33
|
+
|
|
34
|
+
// Keep the active row scrolled into view.
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const el = listRef.current?.querySelector<HTMLElement>(
|
|
37
|
+
`[data-idx="${active}"]`,
|
|
38
|
+
);
|
|
39
|
+
el?.scrollIntoView({ block: "nearest" });
|
|
40
|
+
}, [active]);
|
|
41
|
+
|
|
42
|
+
function openAt(idx: number) {
|
|
43
|
+
const r = results[idx];
|
|
44
|
+
if (!r) return;
|
|
45
|
+
onOpenFile(r.path);
|
|
46
|
+
clear();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className="flex flex-col gap-1 px-2 py-1.5">
|
|
51
|
+
<div className="relative">
|
|
52
|
+
<Search className="pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 opacity-50" />
|
|
53
|
+
<input
|
|
54
|
+
type="text"
|
|
55
|
+
className="w-full rounded-sm border bg-transparent py-1 pl-7 pr-2 text-xs outline-none focus:ring-1 focus:ring-ring"
|
|
56
|
+
placeholder="Search… (⌘K)"
|
|
57
|
+
value={query}
|
|
58
|
+
onChange={(e) => {
|
|
59
|
+
setQuery(e.target.value);
|
|
60
|
+
void search(e.target.value);
|
|
61
|
+
}}
|
|
62
|
+
onKeyDown={(e) => {
|
|
63
|
+
if (e.key === "Escape") {
|
|
64
|
+
clear();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (results.length === 0) return;
|
|
68
|
+
if (e.key === "ArrowDown") {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
setActive((i) => (i + 1) % results.length);
|
|
71
|
+
} else if (e.key === "ArrowUp") {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
setActive((i) => (i - 1 + results.length) % results.length);
|
|
74
|
+
} else if (e.key === "Enter") {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
openAt(active);
|
|
77
|
+
}
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
{query.trim() !== "" && (
|
|
82
|
+
<div
|
|
83
|
+
ref={listRef}
|
|
84
|
+
className="max-h-64 overflow-y-auto rounded-sm border bg-popover"
|
|
85
|
+
>
|
|
86
|
+
{loading && (
|
|
87
|
+
<div className="px-2 py-1 text-xs text-muted-foreground">
|
|
88
|
+
Searching…
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
{!loading && results.length === 0 && (
|
|
92
|
+
<div className="px-2 py-1 text-xs text-muted-foreground">
|
|
93
|
+
No matches
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
{results.map((r, idx) => (
|
|
97
|
+
<button
|
|
98
|
+
key={r.path}
|
|
99
|
+
type="button"
|
|
100
|
+
data-idx={idx}
|
|
101
|
+
onClick={() => openAt(idx)}
|
|
102
|
+
onMouseEnter={() => setActive(idx)}
|
|
103
|
+
className={`block w-full px-2 py-1 text-left ${
|
|
104
|
+
idx === active ? "bg-accent" : "hover:bg-accent"
|
|
105
|
+
}`}
|
|
106
|
+
>
|
|
107
|
+
<div className="truncate font-mono text-xs">
|
|
108
|
+
{r.path}
|
|
109
|
+
</div>
|
|
110
|
+
{r.snippet && (
|
|
111
|
+
<div className="truncate text-[10px] text-muted-foreground">
|
|
112
|
+
<SnippetText html={r.snippet} />
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</button>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe renderer for FTS5 snippet() output.
|
|
3
|
+
* Parses <mark>...</mark> tags from the snippet string without using
|
|
4
|
+
* dangerouslySetInnerHTML. Any other HTML in source files is rendered as text.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
interface Part {
|
|
8
|
+
text: string;
|
|
9
|
+
mark: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function parseSnippet(html: string): Part[] {
|
|
13
|
+
const parts: Part[] = [];
|
|
14
|
+
const re = /<mark>([\s\S]*?)<\/mark>/g;
|
|
15
|
+
let last = 0;
|
|
16
|
+
let m: RegExpExecArray | null;
|
|
17
|
+
while ((m = re.exec(html)) !== null) {
|
|
18
|
+
if (m.index > last) {
|
|
19
|
+
parts.push({ text: html.slice(last, m.index), mark: false });
|
|
20
|
+
}
|
|
21
|
+
parts.push({ text: m[1] ?? "", mark: true });
|
|
22
|
+
last = m.index + m[0].length;
|
|
23
|
+
}
|
|
24
|
+
if (last < html.length) {
|
|
25
|
+
parts.push({ text: html.slice(last), mark: false });
|
|
26
|
+
}
|
|
27
|
+
return parts;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function SnippetText({ html }: { html: string }) {
|
|
31
|
+
const parts = parseSnippet(html);
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
{parts.map((p, i) =>
|
|
35
|
+
p.mark ? (
|
|
36
|
+
<mark
|
|
37
|
+
key={i}
|
|
38
|
+
className="bg-yellow-200 dark:bg-yellow-700 rounded-[2px] px-[1px]"
|
|
39
|
+
>
|
|
40
|
+
{p.text}
|
|
41
|
+
</mark>
|
|
42
|
+
) : (
|
|
43
|
+
<span key={i}>{p.text}</span>
|
|
44
|
+
),
|
|
45
|
+
)}
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -23,13 +23,14 @@ Command.displayName = CommandPrimitive.displayName;
|
|
|
23
23
|
|
|
24
24
|
function CommandDialog({
|
|
25
25
|
children,
|
|
26
|
+
shouldFilter,
|
|
26
27
|
...props
|
|
27
|
-
}: React.ComponentProps<typeof Dialog>) {
|
|
28
|
+
}: React.ComponentProps<typeof Dialog> & { shouldFilter?: boolean }) {
|
|
28
29
|
return (
|
|
29
30
|
<Dialog {...props}>
|
|
30
31
|
<DialogContent className="overflow-hidden p-0 shadow-golden max-w-lg">
|
|
31
32
|
<DialogTitle className="sr-only">Search</DialogTitle>
|
|
32
|
-
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-normal [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
|
33
|
+
<Command shouldFilter={shouldFilter} className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-normal [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
|
33
34
|
{children}
|
|
34
35
|
</Command>
|
|
35
36
|
</DialogContent>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global admin role.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order:
|
|
5
|
+
* 1. WIKI_ADMIN_EMAILS env (csv, case-insensitive) — seed/override, headless-friendly.
|
|
6
|
+
* 2. config.adminUserIds — persisted list managed via the admin API.
|
|
7
|
+
*
|
|
8
|
+
* Bootstrap: if adminUserIds is empty AND no WIKI_ADMIN_EMAILS is set,
|
|
9
|
+
* the first call to ensureBootstrapAdmin(userId) writes that user as admin.
|
|
10
|
+
* Called lazily on the first authenticated request (race-safe: re-read inside).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readConfig, updateConfig } from "@/lib/config";
|
|
14
|
+
import { requireUser } from "@/lib/auth/server";
|
|
15
|
+
|
|
16
|
+
// ── Internal helpers ───────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
function adminEmailsFromEnv(): string[] {
|
|
19
|
+
return (process.env.WIKI_ADMIN_EMAILS ?? "")
|
|
20
|
+
.split(",")
|
|
21
|
+
.map((e) => e.trim().toLowerCase())
|
|
22
|
+
.filter(Boolean);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── Public API ─────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
export async function isAdmin(userId: string, email?: string): Promise<boolean> {
|
|
28
|
+
// Env override: any matching email is admin
|
|
29
|
+
if (email) {
|
|
30
|
+
const envAdmins = adminEmailsFromEnv();
|
|
31
|
+
if (envAdmins.length > 0 && envAdmins.includes(email.toLowerCase())) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const cfg = await readConfig();
|
|
36
|
+
return (cfg.adminUserIds ?? []).includes(userId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function listAdmins(): Promise<string[]> {
|
|
40
|
+
const cfg = await readConfig();
|
|
41
|
+
return cfg.adminUserIds ?? [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function addAdmin(userId: string): Promise<void> {
|
|
45
|
+
await updateConfig((cfg) => {
|
|
46
|
+
const current = cfg.adminUserIds ?? [];
|
|
47
|
+
if (current.includes(userId)) return cfg;
|
|
48
|
+
return { ...cfg, adminUserIds: [...current, userId] };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function removeAdmin(userId: string): Promise<void> {
|
|
53
|
+
const hasEnvFallback = adminEmailsFromEnv().length > 0;
|
|
54
|
+
let rejected = false;
|
|
55
|
+
await updateConfig((cfg) => {
|
|
56
|
+
const current = cfg.adminUserIds ?? [];
|
|
57
|
+
const next = current.filter((id) => id !== userId);
|
|
58
|
+
// Refuse to remove last admin if no env fallback exists
|
|
59
|
+
if (next.length === 0 && !hasEnvFallback) {
|
|
60
|
+
rejected = true;
|
|
61
|
+
return cfg;
|
|
62
|
+
}
|
|
63
|
+
return { ...cfg, adminUserIds: next };
|
|
64
|
+
});
|
|
65
|
+
if (rejected) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
"LAST_ADMIN: cannot remove the last admin with no WIKI_ADMIN_EMAILS configured",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* If adminUserIds is empty AND no WIKI_ADMIN_EMAILS, write userId as the first
|
|
74
|
+
* admin. Safe to call concurrently: re-reads inside before writing.
|
|
75
|
+
*/
|
|
76
|
+
export async function ensureBootstrapAdmin(userId: string): Promise<void> {
|
|
77
|
+
if (adminEmailsFromEnv().length > 0) return; // env handles it
|
|
78
|
+
|
|
79
|
+
// Fast-path read to avoid the serialized write when an admin already exists.
|
|
80
|
+
const cfg = await readConfig();
|
|
81
|
+
if ((cfg.adminUserIds ?? []).length > 0) return;
|
|
82
|
+
|
|
83
|
+
let promoted = false;
|
|
84
|
+
// Atomic: re-check inside the lock so only one concurrent caller writes.
|
|
85
|
+
await updateConfig((fresh) => {
|
|
86
|
+
if ((fresh.adminUserIds ?? []).length > 0) return fresh;
|
|
87
|
+
promoted = true;
|
|
88
|
+
return { ...fresh, adminUserIds: [userId] };
|
|
89
|
+
});
|
|
90
|
+
if (promoted) {
|
|
91
|
+
console.log(`[wiki-viewer] Bootstrap: first user ${userId} promoted to admin`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Use in route handlers.
|
|
97
|
+
* Returns { ok: true, user } or { ok: false, status, code } ready for NextResponse.
|
|
98
|
+
*/
|
|
99
|
+
export async function requireAdmin(
|
|
100
|
+
req: Request,
|
|
101
|
+
): Promise<
|
|
102
|
+
| { ok: true; user: { id: string; email: string; name: string } }
|
|
103
|
+
| { ok: false; status: number; code: string }
|
|
104
|
+
> {
|
|
105
|
+
const auth = await requireUser(req);
|
|
106
|
+
if (!auth.ok) return { ok: false, status: 401, code: "UNAUTHORIZED" };
|
|
107
|
+
|
|
108
|
+
const admin = await isAdmin(auth.user.id, auth.user.email);
|
|
109
|
+
if (!admin) return { ok: false, status: 403, code: "ADMIN_REQUIRED" };
|
|
110
|
+
|
|
111
|
+
return { ok: true, user: auth.user };
|
|
112
|
+
}
|
|
@@ -10,8 +10,19 @@ import path from "node:path";
|
|
|
10
10
|
import os from "node:os";
|
|
11
11
|
import { mkdirSync, readFileSync, writeFileSync, existsSync, chmodSync } from "node:fs";
|
|
12
12
|
import { randomBytes } from "node:crypto";
|
|
13
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
14
|
import { isEmailAllowed } from "./allowlist";
|
|
14
15
|
|
|
16
|
+
// When an admin creates a user, the signup allowlist must not apply (the admin
|
|
17
|
+
// is the gate). We mark that context with an AsyncLocalStorage flag so the
|
|
18
|
+
// user-create hook can skip the allowlist check for that one call only.
|
|
19
|
+
const adminCreateContext = new AsyncLocalStorage<boolean>();
|
|
20
|
+
|
|
21
|
+
/** Run `fn` with the signup allowlist bypassed (admin-initiated user creation). */
|
|
22
|
+
export function withAdminUserCreate<T>(fn: () => Promise<T>): Promise<T> {
|
|
23
|
+
return adminCreateContext.run(true, fn);
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
// Guard runs at server startup, not during next build (NEXT_PHASE=phase-production-build).
|
|
16
27
|
// Set WIKI_ALLOW_INSECURE=1 to bypass the https requirement (development / CI / smoke tests).
|
|
17
28
|
if (
|
|
@@ -52,7 +63,7 @@ function resolveSecret(): string {
|
|
|
52
63
|
return fresh;
|
|
53
64
|
}
|
|
54
65
|
|
|
55
|
-
const db = new Database(DB_PATH);
|
|
66
|
+
export const db = new Database(DB_PATH);
|
|
56
67
|
db.pragma("journal_mode = WAL");
|
|
57
68
|
|
|
58
69
|
function getTrustedOrigins(): string[] {
|
|
@@ -142,7 +153,7 @@ export const auth = betterAuth({
|
|
|
142
153
|
user: {
|
|
143
154
|
create: {
|
|
144
155
|
before: async (user: { email: string }) => {
|
|
145
|
-
if (!(await isEmailAllowed(user.email))) {
|
|
156
|
+
if (!adminCreateContext.getStore() && !(await isEmailAllowed(user.email))) {
|
|
146
157
|
throw new Error("SIGNUP_NOT_ALLOWED");
|
|
147
158
|
}
|
|
148
159
|
return { data: user };
|
|
@@ -2,13 +2,30 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Workspace type is defined in workspaces.ts; we use a forward-compatible
|
|
6
|
+
// inline shape here to avoid a circular import between config ↔ workspaces.
|
|
7
|
+
export interface WorkspaceEntry {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
rootDir: string;
|
|
11
|
+
createdAt: string;
|
|
12
|
+
lastOpenedAt?: string;
|
|
13
|
+
pinnedPaths?: string[];
|
|
14
|
+
createdBy?: string;
|
|
15
|
+
allowedUserIds?: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface WikiViewerConfig {
|
|
6
19
|
pinnedPaths?: string[];
|
|
7
20
|
lastOpenedPath?: string;
|
|
8
21
|
/** Email allowlist for signup. Empty/undefined = no email restriction. */
|
|
9
22
|
allowedEmails?: string[];
|
|
10
23
|
/** Domain allowlist for signup. Empty/undefined = no domain restriction. */
|
|
11
24
|
allowedDomains?: string[];
|
|
25
|
+
/** Registered workspaces (replaces flat lastOpenedPath/pinnedPaths over time). */
|
|
26
|
+
workspaces?: WorkspaceEntry[];
|
|
27
|
+
/** User IDs with admin privileges. Empty = no admins yet (bootstrap on first request). */
|
|
28
|
+
adminUserIds?: string[];
|
|
12
29
|
}
|
|
13
30
|
|
|
14
31
|
function configPath() {
|
|
@@ -28,9 +45,44 @@ export async function readConfig(): Promise<WikiViewerConfig> {
|
|
|
28
45
|
}
|
|
29
46
|
}
|
|
30
47
|
|
|
31
|
-
|
|
48
|
+
// Serialize all writes so concurrent read-modify-write callers can't lose
|
|
49
|
+
// updates (workspace mutations, admin bootstrap all race on one file).
|
|
50
|
+
let _writeChain: Promise<unknown> = Promise.resolve();
|
|
51
|
+
|
|
52
|
+
function serialize<T>(fn: () => Promise<T>): Promise<T> {
|
|
53
|
+
const run = _writeChain.then(fn, fn);
|
|
54
|
+
// Keep the chain alive but swallow errors so one failure doesn't poison it.
|
|
55
|
+
_writeChain = run.then(
|
|
56
|
+
() => undefined,
|
|
57
|
+
() => undefined,
|
|
58
|
+
);
|
|
59
|
+
return run;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function writeConfigUnsafe(next: WikiViewerConfig): Promise<void> {
|
|
32
63
|
await ensureDir();
|
|
33
|
-
const existing = await readConfig();
|
|
34
|
-
const next = { ...existing, ...patch };
|
|
35
64
|
await writeFile(configPath(), JSON.stringify(next, null, 2), "utf8");
|
|
36
65
|
}
|
|
66
|
+
|
|
67
|
+
export async function writeConfig(patch: Partial<WikiViewerConfig>): Promise<void> {
|
|
68
|
+
return serialize(async () => {
|
|
69
|
+
const existing = await readConfig();
|
|
70
|
+
await writeConfigUnsafe({ ...existing, ...patch });
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Atomic read-modify-write. The mutator receives a fresh copy of the config
|
|
76
|
+
* (read inside the lock) and returns the next config. Serialized against all
|
|
77
|
+
* other writeConfig/updateConfig callers so updates are never lost.
|
|
78
|
+
*/
|
|
79
|
+
export async function updateConfig(
|
|
80
|
+
mutator: (cfg: WikiViewerConfig) => WikiViewerConfig,
|
|
81
|
+
): Promise<WikiViewerConfig> {
|
|
82
|
+
return serialize(async () => {
|
|
83
|
+
const existing = await readConfig();
|
|
84
|
+
const next = mutator({ ...existing });
|
|
85
|
+
await writeConfigUnsafe(next);
|
|
86
|
+
return next;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -27,16 +27,23 @@ function getDb(): InstanceType<typeof Database> {
|
|
|
27
27
|
_db.pragma("journal_mode = WAL");
|
|
28
28
|
_db.exec(`
|
|
29
29
|
CREATE TABLE IF NOT EXISTS agent_fs_audit (
|
|
30
|
-
id
|
|
31
|
-
agent_id
|
|
32
|
-
op
|
|
33
|
-
path
|
|
34
|
-
old_sha
|
|
35
|
-
new_sha
|
|
36
|
-
forced
|
|
37
|
-
at
|
|
30
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
31
|
+
agent_id TEXT NOT NULL,
|
|
32
|
+
op TEXT NOT NULL,
|
|
33
|
+
path TEXT NOT NULL,
|
|
34
|
+
old_sha TEXT,
|
|
35
|
+
new_sha TEXT,
|
|
36
|
+
forced INTEGER NOT NULL DEFAULT 0,
|
|
37
|
+
at TEXT NOT NULL,
|
|
38
|
+
workspace_id TEXT
|
|
38
39
|
)
|
|
39
40
|
`);
|
|
41
|
+
// Tolerant migration for existing DBs that lack the workspace_id column.
|
|
42
|
+
try {
|
|
43
|
+
_db.exec(`ALTER TABLE agent_fs_audit ADD COLUMN workspace_id TEXT`);
|
|
44
|
+
} catch {
|
|
45
|
+
// Column already exists — ignore.
|
|
46
|
+
}
|
|
40
47
|
return _db;
|
|
41
48
|
}
|
|
42
49
|
|
|
@@ -47,6 +54,7 @@ export interface AuditRow {
|
|
|
47
54
|
oldSha?: string;
|
|
48
55
|
newSha?: string;
|
|
49
56
|
forced?: boolean;
|
|
57
|
+
workspaceId?: string;
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
/**
|
|
@@ -57,8 +65,8 @@ export function writeAuditRow(row: AuditRow): void {
|
|
|
57
65
|
try {
|
|
58
66
|
const db = getDb();
|
|
59
67
|
db.prepare(
|
|
60
|
-
`INSERT INTO agent_fs_audit (agent_id, op, path, old_sha, new_sha, forced, at)
|
|
61
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
68
|
+
`INSERT INTO agent_fs_audit (agent_id, op, path, old_sha, new_sha, forced, at, workspace_id)
|
|
69
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
62
70
|
).run(
|
|
63
71
|
row.agentId,
|
|
64
72
|
row.op,
|
|
@@ -67,6 +75,7 @@ export function writeAuditRow(row: AuditRow): void {
|
|
|
67
75
|
row.newSha ?? null,
|
|
68
76
|
row.forced ? 1 : 0,
|
|
69
77
|
new Date().toISOString(),
|
|
78
|
+
row.workspaceId ?? null,
|
|
70
79
|
);
|
|
71
80
|
} catch (e) {
|
|
72
81
|
console.error("[agent-fs audit] write failed:", e);
|
|
@@ -72,6 +72,8 @@ export async function checkAuth(req: Request): Promise<CheckAuthResult> {
|
|
|
72
72
|
export interface ScopeParams {
|
|
73
73
|
filePath?: string;
|
|
74
74
|
op: "read" | "mutate" | "delete";
|
|
75
|
+
/** Id of the workspace this request resolved to. If the agent has a workspaceId grant and it differs, 403 is returned. */
|
|
76
|
+
workspaceId?: string;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
export function enforceScope(
|
|
@@ -80,6 +82,15 @@ export function enforceScope(
|
|
|
80
82
|
): { ok: true } | { ok: false; code: "FORBIDDEN"; message: string } {
|
|
81
83
|
const { scope } = agent;
|
|
82
84
|
|
|
85
|
+
// Workspace grant check: if agent is restricted to a workspace, enforce it.
|
|
86
|
+
if (scope.workspaceId !== undefined && params.workspaceId !== undefined && scope.workspaceId !== params.workspaceId) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
code: "FORBIDDEN",
|
|
90
|
+
message: `Agent scope does not cover workspace "${params.workspaceId}" (granted: "${scope.workspaceId}")`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
83
94
|
if (!scope.ops.includes(params.op)) {
|
|
84
95
|
return { ok: false, code: "FORBIDDEN", message: `Agent scope does not allow "${params.op}"` };
|
|
85
96
|
}
|
|
@@ -48,13 +48,15 @@ export async function computeCollabState(
|
|
|
48
48
|
const snapshotUrl = `/api/agent/files/${relPath}`;
|
|
49
49
|
|
|
50
50
|
// Compute revision components
|
|
51
|
+
// ns = rootDir: workspace-namespaces lease keys so two workspaces sharing
|
|
52
|
+
// the same relPath never collide in the lease store.
|
|
51
53
|
const sidecar = await readSidecar(rootDir, relPath);
|
|
52
|
-
const gen = leaseGeneration(relPath);
|
|
54
|
+
const gen = leaseGeneration(rootDir, relPath);
|
|
53
55
|
const sidecarRevision = sidecar?.revision ?? 0;
|
|
54
56
|
const revision = sidecarRevision + gen;
|
|
55
57
|
|
|
56
58
|
// Lease check — do after reading sidecar to keep gen stable within this call
|
|
57
|
-
const leaseActive = hasActiveLease(relPath);
|
|
59
|
+
const leaseActive = hasActiveLease(rootDir, relPath);
|
|
58
60
|
|
|
59
61
|
if (!sidecar && !leaseActive) {
|
|
60
62
|
return { state: "untracked", revision, snapshotUrl };
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// Workspacing note (Phase B): the lockKey must be workspace-unique. Callers
|
|
2
|
+
// should prefix lockKey with rootDir (or wsId): `${rootDir}\0${relPath}`.
|
|
3
|
+
// The cross-process sentinel hash then differs per workspace automatically.
|
|
4
|
+
// Phase B call sites will make this change when route threading is done.
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* Cross-process file locking via proper-lockfile.
|
|
3
8
|
*
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// Workspacing note (Phase B): the idempotency key must be prefixed with wsId
|
|
2
|
+
// (or rootDir) at the call site to prevent cross-workspace collisions:
|
|
3
|
+
// e.g. key = `${rootDir}\0${idempotencyHeader}`. Phase B call sites will
|
|
4
|
+
// make this change when route threading is done.
|
|
5
|
+
|
|
1
6
|
// Tiny in-memory LRU. Lives for process lifetime. Acceptable: idempotency
|
|
2
7
|
// guards retries within seconds, not days.
|
|
3
8
|
const MAX = 1000;
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Human edit-lease store (in-memory, globalThis for hot-reload survival).
|
|
3
3
|
*
|
|
4
|
+
* Namespace choice (workspacing): the first argument to every public function
|
|
5
|
+
* is `ns` — a namespace string that MUST be the workspace rootDir (or wsId in
|
|
6
|
+
* future phases). The internal map key is `${ns}\u0000${relPath}`. Two
|
|
7
|
+
* workspaces that share the same relative path (e.g. "notes.md") therefore
|
|
8
|
+
* never collide. Phase B routes pass rootDir from resolveWorkspaceForUser;
|
|
9
|
+
* Phase A callers pass getRootDir() or tmpRoot in tests.
|
|
10
|
+
*
|
|
4
11
|
* A lease is set when a human opens a .md doc in the editor and refreshed
|
|
5
12
|
* via heartbeat while the doc stays open. Its presence makes collab-state
|
|
6
|
-
* "active" even before the human has typed a single suggestion
|
|
7
|
-
* false-negative GPT-5.5 caught in the design review).
|
|
13
|
+
* "active" even before the human has typed a single suggestion.
|
|
8
14
|
*
|
|
9
15
|
* TTL: LEASE_TTL_MS (default 90 s). Editor should heartbeat every ~30 s.
|
|
10
16
|
*
|
|
11
17
|
* Collab-generation counter:
|
|
12
|
-
* - Starts at 0 per path.
|
|
18
|
+
* - Starts at 0 per (ns, path).
|
|
13
19
|
* - Increments on EVERY 0→1 transition (first setLease, or setLease after
|
|
14
20
|
* all prior leases expired) AND on every 1→0 transition (clearLease or
|
|
15
21
|
* expiry-on-read).
|
|
16
|
-
* - X-Collab-Revision = sidecar.revision + leaseGeneration(path).
|
|
17
|
-
* Any lease or sidecar state change therefore bumps the combined revision.
|
|
22
|
+
* - X-Collab-Revision = sidecar.revision + leaseGeneration(ns, path).
|
|
18
23
|
*/
|
|
19
24
|
|
|
20
25
|
export const LEASE_TTL_MS = 90_000;
|
|
@@ -38,62 +43,70 @@ function store(): LeaseStore {
|
|
|
38
43
|
return g.__wikiLeaseStore!;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
function storeKey(ns: string, relPath: string): string {
|
|
47
|
+
return `${ns}\u0000${relPath}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** True if (ns, relPath) has a non-expired lease RIGHT NOW (lazy-sweeps on check). */
|
|
51
|
+
function isLeaseActiveInternal(ns: string, relPath: string): boolean {
|
|
43
52
|
const s = store();
|
|
44
|
-
const
|
|
53
|
+
const key = storeKey(ns, relPath);
|
|
54
|
+
const entry = s.leases.get(key);
|
|
45
55
|
if (!entry) return false;
|
|
46
56
|
if (Date.now() >= entry.expiresAt) {
|
|
47
|
-
s.leases.delete(
|
|
57
|
+
s.leases.delete(key);
|
|
48
58
|
// 1→0 transition: bump generation
|
|
49
|
-
s.generations.set(
|
|
59
|
+
s.generations.set(key, (s.generations.get(key) ?? 0) + 1);
|
|
50
60
|
return false;
|
|
51
61
|
}
|
|
52
62
|
return true;
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
/**
|
|
56
|
-
* Set (or refresh) a lease for relPath by userId.
|
|
66
|
+
* Set (or refresh) a lease for (ns, relPath) by userId.
|
|
57
67
|
* Bumps the generation counter on a 0→1 transition.
|
|
68
|
+
* `ns` must be the workspace rootDir (or a stable workspace identifier).
|
|
58
69
|
*/
|
|
59
|
-
export function setLease(relPath: string, userId: string, ttlMs = LEASE_TTL_MS): void {
|
|
70
|
+
export function setLease(ns: string, relPath: string, userId: string, ttlMs = LEASE_TTL_MS): void {
|
|
60
71
|
const s = store();
|
|
61
|
-
const
|
|
62
|
-
|
|
72
|
+
const key = storeKey(ns, relPath);
|
|
73
|
+
const wasActive = isLeaseActiveInternal(ns, relPath);
|
|
74
|
+
s.leases.set(key, { userId, expiresAt: Date.now() + ttlMs });
|
|
63
75
|
if (!wasActive) {
|
|
64
76
|
// 0→1 transition: bump generation
|
|
65
|
-
s.generations.set(
|
|
77
|
+
s.generations.set(key, (s.generations.get(key) ?? 0) + 1);
|
|
66
78
|
}
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
/** True if relPath has a current non-expired lease. */
|
|
70
|
-
export function hasActiveLease(relPath: string): boolean {
|
|
71
|
-
return isLeaseActiveInternal(relPath);
|
|
81
|
+
/** True if (ns, relPath) has a current non-expired lease. */
|
|
82
|
+
export function hasActiveLease(ns: string, relPath: string): boolean {
|
|
83
|
+
return isLeaseActiveInternal(ns, relPath);
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
/**
|
|
75
|
-
* Clear the lease for relPath (on doc close).
|
|
87
|
+
* Clear the lease for (ns, relPath) (on doc close).
|
|
76
88
|
* Only clears if the existing lease belongs to userId.
|
|
77
89
|
* Bumps generation on removal.
|
|
78
90
|
*/
|
|
79
|
-
export function clearLease(relPath: string, userId: string): void {
|
|
91
|
+
export function clearLease(ns: string, relPath: string, userId: string): void {
|
|
80
92
|
const s = store();
|
|
81
|
-
const
|
|
93
|
+
const key = storeKey(ns, relPath);
|
|
94
|
+
const entry = s.leases.get(key);
|
|
82
95
|
if (!entry) return;
|
|
83
|
-
// Still clear expired leases owned by the same user
|
|
84
96
|
if (entry.userId === userId) {
|
|
85
|
-
s.leases.delete(
|
|
97
|
+
s.leases.delete(key);
|
|
86
98
|
// 1→0 transition: bump generation
|
|
87
|
-
s.generations.set(
|
|
99
|
+
s.generations.set(key, (s.generations.get(key) ?? 0) + 1);
|
|
88
100
|
}
|
|
89
101
|
}
|
|
90
102
|
|
|
91
103
|
/**
|
|
92
|
-
* Returns the monotonic-ish collab generation for relPath.
|
|
104
|
+
* Returns the monotonic-ish collab generation for (ns, relPath).
|
|
93
105
|
* Increments on every 0→1 and 1→0 lease transition.
|
|
94
106
|
*/
|
|
95
|
-
export function leaseGeneration(relPath: string): number {
|
|
96
|
-
|
|
107
|
+
export function leaseGeneration(ns: string, relPath: string): number {
|
|
108
|
+
const key = storeKey(ns, relPath);
|
|
109
|
+
return store().generations.get(key) ?? 0;
|
|
97
110
|
}
|
|
98
111
|
|
|
99
112
|
/** Reset store — for tests only. */
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// Workspacing note (Phase B): callers must prefix the lock key with rootDir
|
|
2
|
+
// (or wsId) to ensure two workspaces sharing the same relPath do not serialize
|
|
3
|
+
// against each other. e.g. key = `${rootDir}\0${relPath}`. Phase B call sites
|
|
4
|
+
// will make this change when route threading is done.
|
|
1
5
|
import { withFileLock } from "./file-lock";
|
|
2
6
|
|
|
3
7
|
const locks = new Map<string, Promise<void>>();
|