/** * THE SCREAM SEAM. * * Every recovery path in this package is loud: a defective component row, a * failed resolver load, a kind whose component throws on a provisional value. * The package refuses to own where those screams land — matrx-frontend routes * them to the Error Inspector, the dashboard to its own log, the extension to * its background page. Each host binds one function. * * A host that binds a no-op has chosen silence, which is a defect in itself; * {@link consoleErrorReporter} is the honest default. */ interface ContentIrErrorReport { /** Always `"content-ir"` — hosts key their capture stores on it. */ source: "content-ir"; message: string; name?: string; stack?: string; /** Free-form grouping key (the host's `relation`), e.g. `"partial-kind"`. */ relation?: string; raw?: unknown; } type ContentIrErrorReporter = (report: ContentIrErrorReport) => void; /** The default when a host binds nothing: never silent. */ declare const consoleErrorReporter: ContentIrErrorReporter; /** * The (kind, platform, role) → component vocabulary — the Shape System's * resolver contract (rulings R1 + R6), stated once for every UI. * * Cross-repo system-of-record: * `common-docs/systems/architecture/content-ir/FEATURE.md` (package boundary) and * `matrx-frontend/features/content-ir/docs/SHAPE_SYSTEM.md` (the semantics). * * Nothing here knows where a row comes from. A host supplies rows from * `content_ir.kind_component` (Supabase, a REST call, a fixture); the resolver * only knows the shape. */ /** JSON object, restated locally so this package depends on no host type. */ type JsonObject = Record; /** * Which face of the shape a component draws. * * - `output` — the shape's finished value, the component a reader sees. * - `input` — the authoring form for a value of this shape. * - `loading` — the shape's LOADING face: what renders from the instant the * kind is identified until its output component has a renderable frame. * Authored per kind like any other component and fed the region's partial * value each frame, so it can PERFORM the arrival (surface real text as it * lands) instead of drawing a shapeless skeleton. Optional: a kind with no * `loading` component falls back to the host's loading library. */ type ComponentRole = "output" | "input" | "loading"; /** * One `content_ir.kind_component` row, projected to exactly what rendering * needs. Hosts map their own query result onto this. */ interface KindComponentRow { kind: string; platform: string; role: ComponentRole; componentKey: string; /** `source` column vocabulary: "bundled" | "db" (db = web sandbox only). */ source: string; config: JsonObject; isActive: boolean; componentSource: string | null; propsTransform: string | null; /** * Whether the row carries a real `component_source` body — supplied by the * host when its warm list is BODY-LESS (the lazy design: metadata travels * on the list, the body arrives per kind via the cold fetch). Omitted → * derived from `componentSource` itself, so eager hosts change nothing. */ hasComponentSource?: boolean; pinnedKindVersion: number | null; updatedAt: string | null; createdBy: string | null; /** * The row's primary key, when the host has it. Folded into the compile-cache * key and the refresh body-keep test, so two rows of one (kind, platform, * role) that share an `updated_at` (one seeding transaction) can never share * a compiled body. Optional: a host without ids keeps today's behavior. */ id?: string | null; } /** Whose view a layered row is: the viewer's own, or one of their organizations'. */ type ComponentLayerScope = "user" | "organization"; /** * One `content_ir.kind_component_layer` row — a personal ("My view") or * organization ("My organization's view") component for a kind slug, layered * ABOVE the shared `kind_component` tier. Same projection as * {@link KindComponentRow}, plus its identity and its ONE audience (exactly * one of the two audience ids is set — the table enforces the XOR). * * Layered rows travel WITH their body: the overlay read is small (my rows + * my organizations' rows), so there is no lazy cold fetch for this tier. */ interface KindComponentLayerRow extends KindComponentRow { id: string; audienceUserId: string | null; audienceOrganizationId: string | null; updatedAt: string; } /** Which layered row produced a resolution — present only for overlay answers. */ interface ComponentLayerRef { scope: ComponentLayerScope; /** `kind_component_layer.id` — the compile-cache and incident identity. */ rowId: string; } /** * The overlay a host hands the resolver: WHO is looking, which organizations * they belong to, and the layered rows the host read for them. The resolver * re-checks every row against the viewer (defense in depth): a user row for * anyone but `viewerId`, or an organization row for an organization not in * `organizationIds`, is dropped — never rendered. */ interface ComponentOverlayInput { viewerId: string; organizationIds: readonly string[]; rows: readonly KindComponentLayerRow[]; } /** * One COMPILED bootstrap entry — the trusted-at-boot floor a host ships in its * bundle, available synchronously from the first streamed byte. */ interface SystemComponentEntry { kind: string; platform: string; role: ComponentRole; componentKey: string; source: string; config: JsonObject; } /** The resolver's answer for one (kind, platform, role). */ interface ComponentResolution { /** The component key the renderer routes on (e.g. "flashcards"). */ componentKey: string; source: string; config: JsonObject; /** * Render-trust verdict (R6). Compiled entries are always active (trusted at * bootstrap); DB rows carry their own `is_active`. */ isActive: boolean; /** Which tier produced this answer — the verification hook's `by`. */ resolvedBy: "compiled" | "db"; componentSource: string | null; propsTransform: string | null; /** * TRUE when the row owns a real component body — even if `componentSource` * is null because the host's warm list is body-less and the cold fetch has * not landed it yet. THE routing gate: `routeToDbComponent` re-types on * this flag, never on body presence, so a body-less row still routes and * the renderer fetches/compiles the body lazily. */ hasComponentSource: boolean; pinnedKindVersion: number | null; /** The db row's `updated_at` — the compile-cache staleness key. */ updatedAt: string | null; createdBy: string | null; /** * The winning row's id when known (db tier: host-supplied; overlay: always). * Part of the compile-cache key — a body is never served for another row. */ rowId?: string | null; /** * Set ONLY when the answer came from the viewer's overlay (a personal or * organization layered row). Hosts MUST render a layered `source='db'` body * sandboxed regardless of any organization sandbox setting, and must never * file its failures against the shared kind. */ layer?: ComponentLayerRef | null; } /** * The read side of a component resolver, as the render layer consumes it. * The package ships {@link ComponentResolver}; a host with its own resolver * only has to satisfy this. */ interface ComponentResolutionSource { /** Synchronous — the render seam's per-block call. */ resolve(kind: string, platform: string, role: ComponentRole): ComponentResolution | null; /** * The SHARED tiers only (db → compiled), ignoring the viewer's overlay. * Optional so an existing host resolver satisfies the interface unchanged. */ resolveShared?(kind: string, platform: string, role: ComponentRole): ComponentResolution | null; /** Eager targeted fetch for one kind; fire-and-forget, never awaited on the hot path. */ requestComponent(kind: string, platform: string, role: ComponentRole): void; /** One list fetch per session. */ ensureWarm(): Promise; /** * Has the session's list load COMPLETED (successfully or not)? * * 🚨 THE COLD-VERDICT RULE. Before this existed, `resolve()` answering null * was ambiguous: it meant BOTH "this kind genuinely has no component" and * "I have not looked yet". The render route could not tell them apart, so a * block routed during the first paint of a fresh tab got stamped * `unregistered` — a durable, user-visible claim that the shape does not * exist — produced by a registry that had loaded nothing. Optional so an * existing host satisfies the interface unchanged; absent reads as settled, * which is the pre-existing behavior. */ hasSettled?(): boolean; /** * Has the lookup FOR THIS KIND settled — the session's list landed, or this kind's own read * answered? `hasSettled()` alone answers for the whole tier, and a host whose tier "settles" the * moment ANY row lands (one kind's per-kind read) closed the window for every other kind on the * page: the kind-registry Preview said brand_basics "isn't registered on this platform" on 7/7 * cold loads (2026-10-17). Optional; absent falls back to `hasSettled()`. */ hasSettledFor?(kind: string, platform: string): boolean; /** Refresh-on-view; rate-limited and deduped by the implementation. */ refresh(maxAgeMs?: number): Promise; /** Monotonic tier version — has anything at all changed? */ getVersion(): number; /** Monotonic per-kind version (+ wholesale epoch) — the granular repaint key. */ getKindVersion(kind: string): number; subscribe(listener: () => void): () => void; subscribeKind(kind: string, listener: () => void): () => void; } export { consoleErrorReporter as i }; export type { ComponentResolutionSource as C, JsonObject as J, KindComponentLayerRow as K, SystemComponentEntry as S, ComponentResolution as a, ContentIrErrorReporter as b, ComponentLayerRef as c, ComponentLayerScope as d, ComponentOverlayInput as e, ComponentRole as f, ContentIrErrorReport as g, KindComponentRow as h };