import * as React from "react"; import { useAtomValue } from "@effect/atom-react"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import type { ScopeId } from "@executor-js/sdk"; import { scopeAtom } from "./atoms"; export interface ScopeStackEntry { readonly id: ScopeId; readonly name: string; readonly dir: string; } export interface ScopeInfo { readonly id: ScopeId; readonly name: string; readonly dir: string; readonly stack: readonly ScopeStackEntry[]; } const ScopeContext = React.createContext(null); /** * Provides the server scope to all children. * Renders the optional `fallback` until the scope is fetched. */ export function ScopeProvider(props: React.PropsWithChildren<{ fallback?: React.ReactNode }>) { const result = useAtomValue(scopeAtom); if (AsyncResult.isSuccess(result)) { return {props.children}; } return <>{props.fallback ?? null}; } /** * Returns the current scope ID. * Must be used inside a ScopeProvider (which gates rendering until scope is loaded). */ export function useScope(): ScopeId { const scope = React.useContext(ScopeContext); if (scope === null) { throw new Error("useScope must be used inside a ScopeProvider"); } return scope.id; } /** * Returns the full scope info (id + display name). * Must be used inside a ScopeProvider. */ export function useScopeInfo(): ScopeInfo { const scope = React.useContext(ScopeContext); if (scope === null) { throw new Error("useScopeInfo must be used inside a ScopeProvider"); } return scope; } export function useScopeStack(): readonly ScopeStackEntry[] { return useScopeInfo().stack; } export function useUserScope(): ScopeId { const stack = useScopeStack(); const innermost = stack[0]; if (!innermost) { throw new Error("useUserScope requires a non-empty scope stack"); } return innermost.id; }