···6767 setStatus("sharing");
68686969 // Core handles: fetch document → unwrap key → wrap to recipient → create grant
7070- await getActiveFileManager().share(
7171- documentUri,
7272- recipient.did,
7373- recipient.publicKey,
7474- "read",
7575- null,
7676- );
7070+ await getActiveFileManager().share(documentUri, recipient.did, recipient.publicKey, "read");
7771 } catch (resolveError) {
7872 if (resolveError instanceof RecipientNotReadyError) {
7973 // REMOVE: pending share needs core domain method (Opake::create_pending_share)
···8781 throw resolveError;
8882 }
89839090- // Optimistically mark the item as shared in the store
9191- const { items } = useDocumentsStore.getState();
9292- const item = items[documentUri];
9393- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard: Record lookup
9494- if (item) {
9595- useDocumentsStore.setState((state) => ({
9696- items: { ...state.items, [documentUri]: { ...item, status: "shared" as const } },
9797- }));
9898- }
8484+ // Optimistically mark the item as shared in the store. `items` is
8585+ // an array of FileItems for the current directory — find by URI,
8686+ // replace in place. SSE / directory reload will reconcile later.
8787+ useDocumentsStore.setState((state) => ({
8888+ items: state.items.map((item) =>
8989+ item.uri === documentUri ? { ...item, status: "shared" as const } : item,
9090+ ),
9191+ }));
999210093 setStatus("done");
10194 toastSuccess(`Shared "${documentName}" with ${handle}`);
+40-3
apps/web/src/lib/sharing.ts
···11-// Stubbed — sharing not yet implemented
11+// Recipient resolution for the share flow.
22+//
33+// Wraps `opake.resolveIdentity` with a distinct error type for the
44+// "not yet on Opake" case — the UI wants to handle those gracefully
55+// via a pending-share queue instead of blocking with a raw error.
66+77+import { getOpake } from "@/stores/auth";
88+import type { ResolvedIdentity } from "@opake/sdk";
291010+/**
1111+ * Thrown when a recipient has a valid handle/DID but hasn't published
1212+ * an X25519 public key yet (no identity record on their PDS). The
1313+ * caller should enqueue a pending share in this case rather than
1414+ * failing the whole flow.
1515+ */
316export class RecipientNotReadyError extends Error {
417 constructor(message: string) {
518 super(message);
···720 }
821}
9221010-export async function resolveRecipient(_handle: string) {
1111- throw new Error("Sharing not implemented");
2323+/**
2424+ * Resolve a recipient handle or DID to their identity (DID + public key).
2525+ *
2626+ * @throws RecipientNotReadyError if the recipient has no published identity.
2727+ * @throws Error (generic) on network or resolution failure.
2828+ */
2929+export async function resolveRecipient(handle: string): Promise<ResolvedIdentity> {
3030+ const identity = await getOpake()
3131+ .resolveIdentity(handle)
3232+ .catch((err: unknown) => {
3333+ const message = err instanceof Error ? err.message : String(err);
3434+ // The SDK throws a generic error when the identity record is absent —
3535+ // sniff the message to decide whether to raise the "not ready" error.
3636+ if (/publicKey|public key|not found|no identity/i.test(message)) {
3737+ throw new RecipientNotReadyError(message);
3838+ }
3939+ throw err;
4040+ });
4141+4242+ if (identity.publicKey.length === 0) {
4343+ throw new RecipientNotReadyError(
4444+ `${handle} has an account but hasn't published a public key yet.`,
4545+ );
4646+ }
4747+4848+ return identity;
1249}