// §22.9 (M3) — runtime deployment config, replacing the build-time // VITE_APP_NAME. Fetches GET /api/deployment once on boot and provides it to // the whole tree: // // { name, tagline, defaultProjectId, defaultProjectReadable, projects[], // viewer, loading, refresh } // // `projects` is the per-caller-visible set (§22.5: public + member-gated; // unlisted omitted). `defaultProjectId` is the corpus-served project the // M3-frontend guard keys on. `defaultProjectReadable` (§22 S5) is whether that // redirect target is reachable by this viewer — the deployment landing only // bounces into it when true, else it renders the role-aware directory empty // state. `viewer` carries the §22 S5 capability flags (`can_create_project`). // `refresh` re-fetches the payload (e.g. after creating a project). During the // pre-fetch paint, consumers fall back to the neutral brandTitle() default // ('RFC') — never a hardcoded deployment name. import { createContext, useContext, useCallback, useEffect, useState } from 'react' import { getDeployment } from '../api' const DeploymentContext = createContext({ name: '', tagline: '', defaultProjectId: null, defaultProjectReadable: false, projects: [], viewer: null, loading: true, refresh: () => {}, }) export function useDeployment() { return useContext(DeploymentContext) } export function DeploymentProvider({ children }) { const [state, setState] = useState({ name: '', tagline: '', defaultProjectId: null, defaultProjectReadable: false, projects: [], viewer: null, loading: true, }) const load = useCallback((cancelledRef) => { return getDeployment() .then(d => { if (cancelledRef?.cancelled) return setState({ name: d.name || '', tagline: d.tagline || '', defaultProjectId: d.default_project_id || null, defaultProjectReadable: !!d.default_project_readable, projects: Array.isArray(d.projects) ? d.projects : [], viewer: d.viewer || null, loading: false, }) }) .catch(() => { // A failed deployment fetch must not wedge the app: fall back to the // neutral brand and an empty directory so the shell still paints. if (!cancelledRef?.cancelled) setState(s => ({ ...s, loading: false })) }) }, []) useEffect(() => { const ref = { cancelled: false } load(ref) return () => { ref.cancelled = true } }, [load]) const refresh = useCallback(() => load(), [load]) return ( {children} ) }