import { ReactNode } from 'react'; import { ActiveContextUrlParams, AuthStoreContextSnapshot } from './ActiveContextStorage'; /** * Context value interface for organization/workspace/project selection */ export interface ActiveContextValue { /** Currently selected organization ID */ organizationId: string | null; /** Currently selected workspace ID */ workspaceId: string | null; /** Currently selected project ID */ projectId: string | null; /** Currently selected tenant ID */ tenantId: string | null; /** Set the active organization */ setOrganization: (orgId: string | null) => void; /** Set the active workspace */ setWorkspace: (workspaceId: string | null) => void; /** Set the active project */ setProject: (projectId: string | null) => void; /** Set the active tenant */ setTenant: (tenantId: string | null) => void; /** Set all context values at once */ setContext: (context: Partial<{ organizationId: string | null; workspaceId: string | null; projectId: string | null; tenantId: string | null; }>) => void; /** Clear all context values */ clearContext: () => void; /** Whether URL sync is enabled */ urlSyncEnabled: boolean; } export interface ActiveContextProviderProps { children: ReactNode; /** Default organization ID */ defaultOrganizationId?: string | null; /** Default workspace ID */ defaultWorkspaceId?: string | null; /** Default project ID */ defaultProjectId?: string | null; /** Default tenant ID */ defaultTenantId?: string | null; /** Storage key prefix for localStorage */ storageKey?: string; /** Enable URL query param synchronization */ syncToUrl?: boolean; /** URL param names */ urlParams?: { organization?: string; workspace?: string; project?: string; tenant?: string; }; } export declare const CONTEXT_THRASH_WINDOW_MS = 500; export declare const CONTEXT_THRASH_MAX_FLIPS = 3; export interface ContextFieldThrashState { current: string | null; previous: string | null; flips: number; windowStart: number; suppressUntil: number; } export declare function createContextFieldThrashState(initial: string | null): ContextFieldThrashState; /** * Decides whether `nextValue` should be applied to a context field, mutating * `state` to reflect the decision. Returns `false` for a true no-op (value * unchanged), while an active suppression window is in effect, or when this * write is detected as the Nth rapid alternation back to the field's prior * value — the ping-pong signature described above. */ export declare function shouldApplyContextFieldWrite(fieldName: string, state: ContextFieldThrashState, nextValue: string | null, now: number): boolean; export interface ActiveContextBootstrapFields { organizationId: string | null; workspaceId: string | null; projectId: string | null; } /** * Fill-only merge of the persisted auth-store context into the values already * resolved from the URL / ActiveContext's own storage. * * Fill rules (mirroring microfe-auth's `useActiveContextBootstrapMirror`): * - a value already resolved from the URL or own storage always wins; * - a child axis is filled from the store only when its parent axis provably * matches the parent the stored child belongs to — the workspace only when * the resolved organization IS the store's organization, the project only * when the resolved workspace IS the store's workspace. Without that guard a * stale `?org=` deep link would be paired with a workspace from a different * organization, which is worse than no workspace at all. * - a child is never filled under an empty parent, for the same reason. * * Exported so this can be unit-tested directly: it is pure, while the provider * around it needs a DOM. */ export declare function applyAuthStoreContextFallback(resolved: ActiveContextBootstrapFields, authStore: AuthStoreContextSnapshot): ActiveContextBootstrapFields; /** * Resolve the organization / workspace / project the provider should start * from: URL (when synced) > ActiveContext's own storage > persisted auth store. * Defaults are applied by the caller, after this. */ export declare function resolveInitialActiveContext(options: { storageKey: string; syncToUrl: boolean; urlParams: ActiveContextUrlParams; }): ActiveContextBootstrapFields; /** * Decide what a browser Back/Forward (popstate) should do to ONE context field. * * BOFF-7248: the handler used to apply every URL value verbatim, so a history * entry whose URL carried no `?workspace=` cleared the active workspace. Such * entries are ordinary — the params are written with `replaceState` only after * the context resolves, and never on `/auth/*` routes — so going Back to one * dropped the user's workspace. The app shell then rendered its "pick a * workspace" empty state and the RBAC-gated sidebar entries disappeared, which * looks exactly like the page failing to load. * * An ABSENT param makes no statement about the field: the entry predates the * sync or is a route that never carries context. Leave the current value * alone. A PRESENT param is the context that entry was viewed in, so apply it. * Context is only ever cleared deliberately through `setWorkspace(null)` and * friends, never by navigating. */ export declare function resolvePopStateContextValue(urlValue: string | null, currentValue: string | null): { apply: true; value: string; } | { apply: false; }; /** * ActiveContextProvider * * Manages the active organization, workspace, project, and tenant context. * * Features: * - Persists context to localStorage * - Optional URL query param synchronization * - URL takes priority over localStorage when syncing * - Provides context to all children including microfrontends * * @example * ```tsx * * * * ``` */ export declare function ActiveContextProvider({ children, defaultOrganizationId, defaultWorkspaceId, defaultProjectId, defaultTenantId, storageKey, syncToUrl, urlParams, }: ActiveContextProviderProps): import("react/jsx-runtime").JSX.Element; /** * Hook to access the active context * * @example * ```tsx * const { organizationId, workspaceId, setWorkspace } = useActiveContext(); * ``` */ export declare function useActiveContext(): ActiveContextValue; //# sourceMappingURL=ActiveContextProvider.d.ts.map