import type { ResolvedConfig } from "../types.js"; export interface Affordance { /** Stable within one harvest: "a1", "a2", ... in document order. */ id: string; tag: string; role: string; /** Accessible name, truncated to 80 chars. */ name: string; /** Best stable selector the harvester could derive. */ selector: string; /** Resolved absolute href for links, else null. */ href: string | null; inForm: boolean; submit: boolean; box: { x: number; y: number; w: number; h: number; }; } export interface RouteHarvest { /** sha256-12 of the sorted normalized inventory; the staleness key. */ signature: string; harvestedAt: string; affordances: Affordance[]; } export type NavOutcome = "overlay" | "in-page-change" | "navigation" | "focus" | "hover"; export type NavRisk = "safe" | "destructive" | "session-destructive"; /** * How a plan remembers which control it meant. The skill replies with * per-harvest ids ("a1"); the plan writer resolves them into this identity so * a later capture, whose fresh harvest numbers everything differently, can * still find the control (selector first, role+name as the fallback). */ export interface AffordanceRef { selector: string; role: string; name: string; href: string | null; } export interface PlannedState { /** Becomes the shot's state axis; validated by validStateName. */ name: string; affordance: AffordanceRef; outcome: NavOutcome; /** Metadata: orders execution (risky last), never blocks it. */ risk: NavRisk; why: string; } export interface PlannedCheck { affordance: AffordanceRef; /** Same-origin path the link claims to lead to, when the href states one. */ expectedPath?: string; } export interface RoutePlan { /** The harvest signature this plan was made against. */ signature: string; plannedAt: string; skillVersion: number; states: PlannedState[]; checks: PlannedCheck[]; skipped: { affordance: string; reason: string; }[]; /** Same-origin destinations not in the config; surfaced, never auto-added. */ suggestions: { path: string; label: string; }[]; } /** Both files key routes the way view groups do, minus the state axis. */ export declare function routeKey(target: string, route: string): string; export interface HarvestFile { version: 1; routes: Record; } export interface NavigationFile { version: 1; routes: Record; } export declare function harvestPath(resolved: ResolvedConfig): string; export declare function navigationPath(resolved: ResolvedConfig): string; export declare function loadHarvests(resolved: ResolvedConfig): Promise; export declare function saveHarvests(resolved: ResolvedConfig, file: HarvestFile): Promise; export declare function loadPlans(resolved: ResolvedConfig): Promise; export declare function savePlans(resolved: ResolvedConfig, file: NavigationFile): Promise; export declare function updateHarvests(resolved: ResolvedConfig, mutate: (file: HarvestFile) => T | Promise): Promise<{ file: HarvestFile; result: T; }>; export declare function updatePlans(resolved: ResolvedConfig, mutate: (file: NavigationFile) => T | Promise): Promise<{ file: NavigationFile; result: T; }>; /** * A synthesized state name must survive the unslugged filename * (`---.png`), the shot id, and the fingerprint, so the * gate is strict: kebab-case, 2-40 chars, and never the reserved "rest". */ export declare function validStateName(name: string): boolean; /** * Which synthesized states currently exist per route, for scope and pruning: * a shot whose state a plan still names is configured intent, the way a * route's declared `states` are. Invalid names and names a config recipe * already owns are excluded here for the same reason execution drops them: * a state this index admits that capture would never produce is a ghost that * pruning could then never retire. */ export declare function plannedStateIndex(resolved: ResolvedConfig): Promise>>;