mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
166 lines
5.2 KiB
TypeScript
166 lines
5.2 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
|
|
|
import { relayClient } from "@/shared/api/relayClient";
|
|
import {
|
|
applyWorkspace,
|
|
getDefaultRelayUrl,
|
|
getIdentity,
|
|
} from "@/shared/api/tauri";
|
|
import { resetMediaCaches } from "@/shared/lib/mediaUrl";
|
|
import { clearSearchHitEventCache } from "@/app/navigation/searchHitEventCache";
|
|
import { clearAllDrafts } from "@/features/messages/lib/useDrafts";
|
|
import { resetAgentObserverStore } from "@/features/agents/observerRelayStore";
|
|
import { resetSidebarRelayConnectionCardState } from "@/features/sidebar/ui/useSidebarRelayConnectionCard";
|
|
import { resetVideoPlayerState } from "@/shared/ui/videoPlayerState";
|
|
|
|
import { initFirstWorkspace } from "./workspaceStorage";
|
|
import type { Workspace } from "./types";
|
|
|
|
/**
|
|
* Tear down all workspace-scoped module singletons so the new
|
|
* workspace starts with a clean slate. Hook-managed singletons
|
|
* (e.g. ChannelMuteSyncManager, ChannelSectionSyncManager) are
|
|
* destroyed via effect cleanup and do not need entries here.
|
|
* See AGENTS.md "Workspace Switching" for the full contract.
|
|
*/
|
|
function resetWorkspaceState(): void {
|
|
relayClient.disconnect();
|
|
resetAgentObserverStore();
|
|
resetSidebarRelayConnectionCardState();
|
|
resetMediaCaches();
|
|
resetVideoPlayerState();
|
|
clearSearchHitEventCache();
|
|
clearAllDrafts();
|
|
}
|
|
|
|
type WorkspaceInitResult =
|
|
| { isReady: true; needsSetup: false; appliedKey: string }
|
|
| {
|
|
isReady: false;
|
|
needsSetup: true;
|
|
defaultRelayUrl: string;
|
|
}
|
|
| { isReady: false; needsSetup: false; appliedKey: string | null };
|
|
|
|
/**
|
|
* Applies the active workspace config to the Tauri backend and resets
|
|
* all workspace-scoped module singletons when the workspace changes.
|
|
*
|
|
* Returns a discriminated union — only render the app after the
|
|
* workspace is applied. When `needsSetup` is true, the caller
|
|
* should show a first-run welcome screen.
|
|
*/
|
|
export function useWorkspaceInit(
|
|
activeWorkspace: Workspace | null,
|
|
workspaceKey: string,
|
|
isSharedIdentity: boolean,
|
|
): WorkspaceInitResult {
|
|
const [result, setResult] = useState<WorkspaceInitResult>({
|
|
isReady: false,
|
|
needsSetup: false,
|
|
appliedKey: null,
|
|
});
|
|
|
|
// Track whether this is the initial mount or a workspace switch.
|
|
// On the initial mount we skip resetting singletons (they're fresh).
|
|
const hasInitializedRef = useRef(false);
|
|
|
|
// biome-ignore lint/correctness/useExhaustiveDependencies: we intentionally depend on specific properties (id/relayUrl/token) — depending on the whole object would trigger resets on name-only changes
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
async function init() {
|
|
if (!activeWorkspace) {
|
|
try {
|
|
const defaultRelayUrl = await getDefaultRelayUrl();
|
|
|
|
if (isSharedIdentity) {
|
|
const identity = await getIdentity();
|
|
if (cancelled) return;
|
|
initFirstWorkspace(defaultRelayUrl, identity.pubkey);
|
|
if (!cancelled) {
|
|
window.location.reload();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!cancelled) {
|
|
setResult({
|
|
isReady: false,
|
|
needsSetup: true,
|
|
defaultRelayUrl,
|
|
});
|
|
}
|
|
} catch {
|
|
if (!cancelled) {
|
|
setResult({
|
|
isReady: false,
|
|
needsSetup: true,
|
|
defaultRelayUrl: "ws://localhost:3000",
|
|
});
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Mark this workspace config as pending while it is applied to the
|
|
// backend. App.tsx also checks appliedKey against the active workspaceKey,
|
|
// which prevents rendering workspace-scoped UI for a new workspace until
|
|
// that exact config has finished applying.
|
|
setResult({
|
|
isReady: false,
|
|
needsSetup: false,
|
|
appliedKey: workspaceKey,
|
|
});
|
|
|
|
// On workspace switch (not initial mount), reset module singletons
|
|
// so the new tree starts with a clean slate.
|
|
if (hasInitializedRef.current) {
|
|
resetWorkspaceState();
|
|
}
|
|
hasInitializedRef.current = true;
|
|
|
|
// Apply workspace config to the Tauri backend.
|
|
//
|
|
// Note: we deliberately do NOT pass an nsec here. The persisted
|
|
// `identity.key` file (resolved at startup by `resolve_persisted_identity`,
|
|
// and updated atomically by `import_identity`) is the single source of
|
|
// truth for the active key. Older builds stored the nsec in localStorage
|
|
// and re-applied it on every reload, which silently overwrote any
|
|
// imported key. `loadWorkspaces()` strips lingering `nsec` fields from
|
|
// legacy entries; this site refuses to apply one even if present.
|
|
try {
|
|
await applyWorkspace(
|
|
activeWorkspace.relayUrl,
|
|
undefined,
|
|
activeWorkspace.token,
|
|
);
|
|
} catch (error) {
|
|
console.error("Failed to apply workspace to backend:", error);
|
|
}
|
|
|
|
if (!cancelled) {
|
|
setResult({
|
|
isReady: true,
|
|
needsSetup: false,
|
|
appliedKey: workspaceKey,
|
|
});
|
|
}
|
|
}
|
|
|
|
void init();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [
|
|
activeWorkspace?.id,
|
|
activeWorkspace?.relayUrl,
|
|
activeWorkspace?.token,
|
|
isSharedIdentity,
|
|
workspaceKey,
|
|
]);
|
|
|
|
return result;
|
|
}
|