/** React-free contracts between the slot host and an installed renderer. */ import type { ReactNode } from 'react' import type { SlotEntryDef, SlotSpec, StoredEntry, Translate } from './index.ts' /** * The locale face the render machinery consumes: namespace binding plus an * observable revision (getSnapshot/subscribe pair — the same HostObservable * currency as every other standard-kit source). The revision moves on every * active-locale or registry change; the renderer re-derives each entry's `t` * from (namespace, revision), so a locale switch hands out NEW function * references and memoized components re-render naturally. Implemented by the * locale plugin, installed through the runtime SlotsService (installLocale). * Install before the first render that needs the seat: outlets bind their * revision subscription at mount, and a face appearing later has no channel * to notify already-mounted outlets (the locale plugin is immediately-tier * infrastructure, so normal compositions install during boot). */ export interface LocaleFace extends HostObservable<{ revision: number }> { /** * Bind a namespace to a translate function reading the active locale at * call time. Identity may be stable per namespace — freshness of rendered * text is carried by the renderer's (ns, revision) seat derivation, not by * this binding. * @param ns - dictionary namespace. * @returns the namespace-bound translate function. */ bind(ns: string): Translate } /** Minimal observable surface for host-provided standard-kit data sources. */ export interface HostObservable { getSnapshot(): T subscribe(fn: () => void): () => void } /** * Type-erased store instance face at the render seam (the typed twin is * {@link StoreInstance}): a bare snapshot source plus the draft-stripped * action callbacks. No React hook crosses this seam — the render machinery * binds `useStore` from the source at its own side (cached per instance); * typing lands at the component seam via {@link PropsStore}. */ export interface StoreInstanceLike { getSnapshot(): unknown /** * Subscribe to state changes (uSES subscribe side). * @param fn - change callback. * @returns unsubscribe. */ subscribe(fn: () => void): () => void readonly actions: Record void> } /** * Per-session standard props resolved per session id (identity-stable per * session scope; a recreated scope yields a new info). Plugins contribute * members through the runtime `sessions.provide` seam; the render side binds * every `hooks` source into a `use` selector hook (hooks never appear * on the host contract) and spreads `props` verbatim. The runtime itself * contributes the first entry (`'session'` → `useSession`). */ export interface SessionMaybeProvideInfo { /** Current session id, absent while the application is in no-session mode. */ sessionId: string | undefined /** * Static hook roster. Each value is absent with the session; keys remain so * session-maybe entries always receive the same hook-shaped standard kit. */ hooks: Record | undefined> /** Static plain-member roster; values are undefined with the session. */ props: Record /** * Key-addressed projection value sources (the useProjection framework seat, * session-projection RFC). Unlike `hooks`, the key space is open — values * arrive from host-computed push frames — so the render side binds per * resolved key instead of per static roster member. Faces are always * defined per key (absence is an `undefined` snapshot); the whole member is * absent with the session. */ projections?: { faceOf(key: string): HostObservable } | undefined } /** Definite per-session standard props resolved for strict session slots. */ export interface SessionProvideInfo extends SessionMaybeProvideInfo { sessionId: string /** Bare observable sources, keyed by hook base name ('session' → useSession). */ hooks: Record> } /** renderSlot dispatch options at the machinery level: keyed dispatch key, list filtering, empty fallback. */ export interface RenderOpts { entryKey?: string only?: string fallback?: ReactNode } /** Host surface the runtime SlotsService presents to the installed renderer. */ export interface SlotRendererHost { /** * Subscribe to a key's registration changes (microtask-batched). * @param key - slot key. * @param fn - change callback. * @returns unsubscribe. */ subscribe(key: string, fn: () => void): () => void /** * Monotonic version for uSES pairing. * @param key - slot key. * @returns current version. */ getVersion(key: string): number /** * Snapshot the registered entries for a key (stable reference between mutations). * @param key - slot key. * @returns entries in registration (list: order) sequence. */ entriesOf(key: string): readonly StoredEntry[] /** * Declared runtime spec from the declarations ledger. * @param key - slot key. * @returns the spec, or undefined while the key is undeclared (outlets render empty). */ specOf(key: string): SlotSpec | undefined /** * Stale-authorization check: whether the entry is still in the ledger. * @param entry - a previously rendered entry. * @returns false once the entry's registration was disposed. */ isLive(entry: StoredEntry): boolean /** * Resolve (create or return cached) the store instance for an entry's * declared handle under a scope key; lifecycle rides the ledger axis. * @param entry - entry whose declaration carries the handle. * @param scopeKey - session id for session-scope slots, undefined for root scope. * @returns the instance, or undefined when the entry declares no store. */ storeOf(entry: StoredEntry, scopeKey: string | undefined): StoreInstanceLike | undefined /** Session-side standard-kit sources. */ sessions: { /** Session list source backing the useSessions standard hook. */ list: HostObservable /** * Atomic current-session provide projection used by SessionProvider: * selection changes and provider-roster changes publish through this one * source, so a stable current id cannot strand mounted entries on an * obsolete hook/prop schema. Carries the static roster with sessionId * undefined while no current session resolves. */ provideInfo: HostObservable } /** Workspace-side standard-kit sources. */ workspaces: { /** Workspace list source backing the useWorkspaces standard hook. */ list: HostObservable } /** * Installed locale face backing the `t` standard seat (absent until the * locale plugin installs one; rendering an entry that declared `locale:` * without it is an assembly failure). */ locale?: LocaleFace | undefined } /** The install seam: runtime owns install()/renderSlot(); web-react implements rendering. */ export interface SlotRenderer { /** * Render the root slot tree over the host surface (the only ctx-level entry). * @param host - the installing service's host surface. * @param ownerProps - owner props from the shell's renderSlot('root', ...) call. * @returns the rendered tree. */ renderRoot(host: SlotRendererHost, ownerProps: object): ReactNode } /** Thrown when a retained renderSlot binding is invoked after its declaring entry was disposed. */ export class StaleAuthorizationError extends Error {} /** * Thrown when a renderSlot binding is invoked for a key outside its entry's * children declaration (plain-JS backstop; typed callers are narrowed * statically). */ export class SlotOwnershipError extends Error {}