this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Resolve workspace document URIs from the tree, not the caller's DID

The workspace editor route synthesized documentUri(currentUserDid, docRkey)
to figure out what to edit. That's fine for the cabinet — cabinet docs are
always authored by the current user — but wrong for workspaces, where each
member uploads to their own repo. Opening a document a peer uploaded
produced an at:// pointing at the wrong authority, then failed to decrypt
(or worse, matched nothing and silently 404'd).

Resolve the full URI from the tree snapshot instead. findDocumentUriByRkey
scans every directory for a document entry whose rkey matches the URL
parameter. Unique match → use it. Zero matches or multiple matches (the
latter is vanishingly unlikely with TID-based rkeys but theoretically
possible across repos) → show "Document not found in this workspace"
rather than guess.

The cabinet route is unchanged — single author, no ambiguity.

+56 -6
+30
apps/web/src/lib/directoryTree.ts
··· 4 4 import { rkeyFromUri } from "@/lib/atUri"; 5 5 6 6 /** 7 + * Locate a document URI in a snapshot by its rkey. 8 + * 9 + * Cabinet documents are always owned by the current user, so 10 + * `documentUri(did, rkey)` is sufficient in those routes. Workspace 11 + * documents are authored by whichever member uploaded them — their 12 + * URIs are anchored at the uploader's DID, not the workspace owner's 13 + * or the current viewer's. Callers that only know the rkey must scan 14 + * the tree for the full URI. 15 + * 16 + * Returns null if no matching document is found, or if multiple 17 + * documents in the workspace share the rkey (which shouldn't happen 18 + * in practice since rkeys are locally generated TIDs, but we refuse 19 + * to guess rather than pick one silently). 20 + */ 21 + export function findDocumentUriByRkey( 22 + snapshot: DirectoryTreeSnapshot, 23 + rkey: string, 24 + ): string | null { 25 + const matches = new Set<string>(); 26 + for (const info of Object.values(snapshot.directories)) { 27 + for (const entry of info.entries) { 28 + if (entry.type === "document" && rkeyFromUri(entry.uri) === rkey) { 29 + matches.add(entry.uri); 30 + } 31 + } 32 + } 33 + return matches.size === 1 ? matches.values().next().value ?? null : null; 34 + } 35 + 36 + /** 7 37 * Find the parent directory of an entry (document or directory) in the tree. 8 38 * Returns null if the entry is in the root or not found. 9 39 */
+26 -6
apps/web/src/routes/cabinet/workspace-editor/$rkey/$docRkey.lazy.tsx
··· 2 2 import { useDirectory, useWorkspaces } from "@opake/react"; 3 3 import { EditorView } from "@/components/cabinet/EditorView"; 4 4 import { useAuthStore } from "@/stores/auth"; 5 - import { documentUri, rkeyFromUri } from "@/lib/atUri"; 6 - import { directoryPathSuffix, findParentUri } from "@/lib/directoryTree"; 5 + import { rkeyFromUri } from "@/lib/atUri"; 6 + import { 7 + directoryPathSuffix, 8 + findDocumentUriByRkey, 9 + findParentUri, 10 + } from "@/lib/directoryTree"; 7 11 8 12 function WorkspaceEditor() { 9 13 const { rkey, docRkey } = Route.useParams(); ··· 11 15 const { data: workspaces } = useWorkspaces(); 12 16 const workspace = workspaces.find((w) => rkeyFromUri(w.uri) === rkey); 13 17 14 - // Compute URI unconditionally so the hook below runs with stable deps 15 - // even when the workspace isn't resolved yet. Guard rendering below. 16 - const uri = did ? documentUri(did, docRkey) : null; 18 + // Workspace documents aren't owned by the current viewer — they live at 19 + // the uploader's DID, so we can't synthesize the full URI from `did + 20 + // docRkey` the way the cabinet editor does. Resolve the full URI from 21 + // the tree snapshot instead. Null until the snapshot arrives, which 22 + // triggers the loading path below. 17 23 const { snapshot } = useDirectory(workspace?.uri ?? null, null); 24 + const uri = snapshot ? findDocumentUriByRkey(snapshot, docRkey) : null; 18 25 const parentUri = uri && snapshot ? findParentUri(snapshot, uri) : null; 19 26 const pathSuffix = parentUri && snapshot ? directoryPathSuffix(snapshot, parentUri) : null; 20 27 const returnPath = pathSuffix 21 28 ? `/cabinet/workspace/${rkey}/${pathSuffix}` 22 29 : `/cabinet/workspace/${rkey}`; 23 30 24 - if (!did || !workspace || !uri) { 31 + if (!did || !workspace) { 25 32 return ( 26 33 <div className="flex flex-1 items-center justify-center"> 27 34 <p className="text-base-content/40 text-sm"> 28 35 {!did ? "Not signed in" : "Workspace not found"} 29 36 </p> 37 + </div> 38 + ); 39 + } 40 + 41 + if (!snapshot) { 42 + // Still loading the tree — no UI yet. 43 + return null; 44 + } 45 + 46 + if (!uri) { 47 + return ( 48 + <div className="flex flex-1 items-center justify-center"> 49 + <p className="text-base-content/40 text-sm">Document not found in this workspace</p> 30 50 </div> 31 51 ); 32 52 }