isotone 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/id.ts","../src/core/index.ts"],"sourcesContent":["let fallbackCounter = 0;\n\n/** IDs must also work on HTTP development hosts, where randomUUID is absent. */\nexport function createNoteId(): string {\n const crypto = globalThis.crypto;\n if (typeof crypto?.randomUUID === 'function') return crypto.randomUUID();\n\n if (typeof crypto?.getRandomValues === 'function') {\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n }\n\n // Old browsers without Web Crypto still need unique, stable retry IDs.\n fallbackCounter += 1;\n return `${Date.now().toString(36)}-${fallbackCounter.toString(36)}-${Math.random().toString(36).slice(2)}`;\n}\n","/**\n * isotone — core.\n *\n * The framework-agnostic half of the internal feedback loop. Holds the note\n * model, the element descriptor, the dev-store client (file-backed via a Vite\n * plugin, `localStorage` fallback), and the route visibility matcher. This\n * module has no React and no Vite dependency and is fully unit-testable in a\n * `node` env.\n */\nimport { request } from './request';\nimport { FEEDBACK_STORAGE_KEY, readBrowserStorage, writeBrowserStorage } from './browser-storage';\nimport { MAX_FEEDBACK_LENGTH } from './limits';\nimport { createNoteId } from './id';\n\n/** A UI element picked with the panel's selector mode. */\nexport interface IterationElement {\n /** Lowercase tag name, e.g. \"button\". */\n tag: string;\n /** Trimmed text-content snippet (may be empty for visual elements). */\n text: string;\n /** Best-effort CSS path for locating the element in code. */\n selector: string;\n}\n\n/** A durable reference to a selector or one of its options, not a DOM path. */\nexport interface VariantReference {\n axisId: string;\n label: string;\n optionId?: string;\n optionLabel?: string;\n value?: string;\n}\n\nexport interface VariantRemoval {\n axisId: string;\n action: 'remove-option' | 'remove-axis' | 'keep-default';\n optionId?: string;\n}\n\nexport interface NoteContext {\n /** Pending instruction: deleting its note cancels it before the agent acts. */\n variantAction?: VariantRemoval;\n elements?: IterationElement[];\n variant?: VariantReference | null;\n}\n\n/** A note written in the panel, read and answered by the coding agent. */\nexport interface IterationNote extends NoteContext {\n id: string;\n /** The route the note was written on, e.g. \"/projects\". */\n route: string;\n feedback: string;\n element: IterationElement | null;\n done: boolean;\n doneAt?: string | null;\n createdAt: string;\n /** The window size the note was written at — layout feedback only means\n * something at the breakpoint it was seen on. */\n viewport?: { w: number; h: number };\n /** Host-specific context the store round-trips without interpreting — see\n * `StoredNote.payload`. */\n payload?: unknown;\n}\n\n/** The minimal structural surface the descriptor builder needs from a DOM\n * node. `children` is `readonly` so both a real `HTMLCollection` and a plain\n * test array satisfy it; the builder materializes it with `Array.from`. */\nexport interface NodeLike {\n tagName: string;\n id?: string | null;\n parentElement?: NodeLike | null;\n children?: readonly NodeLike[];\n textContent?: string | null;\n /** One-based child position, when the adapter can provide it cheaply. */\n indexInParent?: number;\n}\n\n/**\n * Best-effort descriptor of a picked element for locating it in code. Walks\n * up the tree collecting `#id` (preferred) or `tag:nth-child(n)` segments up\n * to a shallow depth, stopping at `BODY` or the depth cap. Kept framework-free\n * so it is unit-testable.\n */\nexport function describeIterationElement(\n el: NodeLike,\n maxDepth = 4,\n): IterationElement {\n const parts: string[] = [];\n let node: NodeLike | null | undefined = el;\n let depth = 0;\n while (node && node.tagName !== \"BODY\" && depth < maxDepth) {\n if (node.id) {\n // Escape every code point: valid for leading digits, punctuation and\n // framework-generated ids without depending on the host's CSS global.\n const id = Array.from(node.id, (c) => `\\\\${c.codePointAt(0)?.toString(16)} `).join('');\n parts.unshift(`#${/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(node.id) ? node.id : id}`);\n break;\n }\n let part = node.tagName.toLowerCase();\n const parent: NodeLike | null | undefined = node.parentElement;\n if (parent) {\n const index = node.indexInParent ?? Array.from(parent.children ?? []).indexOf(node) + 1;\n if (index > 0) part += `:nth-child(${index})`;\n }\n parts.unshift(part);\n node = parent;\n depth++;\n }\n return {\n tag: el.tagName.toLowerCase(),\n text: (el.textContent ?? \"\").trim().replace(/\\s+/g, \" \").slice(0, 80),\n selector: parts.join(\" > \"),\n };\n}\n\n/** Lazy DOM views: describing one element never walks unrelated subtrees. */\nexport function elementAsNode(el: Element): NodeLike {\n const cache = new WeakMap<Element, NodeLike>();\n const view = (n: Element | null | undefined): NodeLike | null => {\n if (!n) return null;\n const cached = cache.get(n);\n if (cached) return cached;\n const created: NodeLike = {\n tagName: n.tagName,\n id: n.id || null,\n get textContent() { return n.textContent; },\n get parentElement() { return view(n.parentElement); },\n get children() { return Array.from(n.children, (child) => view(child) as NodeLike); },\n get indexInParent() {\n let index = 1;\n for (let sibling = n.previousElementSibling; sibling; sibling = sibling.previousElementSibling) index++;\n return index;\n },\n };\n cache.set(n, created);\n return created;\n };\n const root = view(el);\n if (root === null) {\n throw new Error('unreachable: a real element always maps to a view');\n }\n return root;\n}\n\n/** A matcher over a console route: include a page only when it is not hidden\n * (denylist) or, when a visible list is given, only when it matches. */\nexport interface RouteGateOptions {\n /** Exact routes (or prefixes ending in `*`) to NEVER show the panel on,\n * e.g. the auth flow. Defaults to the auth/login/error pages. */\n hidden?: string[];\n /** When set, visible only on these routes/prefixes (allowlist). Overrides\n * `hidden` — used when a repo wants an explicit allow-list. */\n visible?: string[];\n /** Prefixes always barred regardless of `visible` (e.g. `/api`, `/auth`). */\n alwaysHidden?: string[];\n}\n\nconst DEFAULT_HIDDEN = [\"/login\", \"/logout\", \"/register\", \"/error\", \"/_error\"];\n\nfunction matchRoutePattern(route: string, pattern: string): boolean {\n if (pattern.endsWith('*')) return route.startsWith(pattern.slice(0, -1));\n if (pattern.endsWith(\"/\")) return route.startsWith(pattern);\n return route === pattern || route.startsWith(`${pattern}/`);\n}\n\n/** Whether the panel may mount on the given route. Pure and unit-testable. */\nexport function isRouteVisible(route: string, rule: RouteGateOptions = {}): boolean {\n const hidden = rule.hidden ?? DEFAULT_HIDDEN;\n const alwaysHidden = rule.alwaysHidden ?? [\"/api\", \"/auth\", \"/login\"];\n // A prefix match on the \"always\" list is unconditionally hidden — the panel\n // must never appear on auth or API surfaces regardless of allow-list.\n for (const p of alwaysHidden) {\n if (route === p || route.startsWith(`${p}/`)) return false;\n }\n if (rule.visible && rule.visible.length > 0) {\n return rule.visible.some((v) => matchRoutePattern(route, v));\n }\n return !hidden.some((h) => matchRoutePattern(route, h));\n}\n\n/* ---------------------------------------------------------------------------\n * Store: client transport for the notes. The Vite plugin serves the endpoint\n * (same-origin, dev-only); a `localStorage` fallback keeps notes when the\n * dev server is unreachable. The endpoint and storage namespace are\n * configurable so multi-app origins don't collide.\n * ------------------------------------------------------------------------- */\n\nexport interface IterationStoreConfig {\n /** Absolute or same-origin URL of the notes endpoint. Must be set (by the\n * Vite plugin's `iteration()` options or a framework preset) for a shared\n * file-backed store; when omitted, only localStorage persists. */\n endpoint?: string | null;\n /** localStorage namespace. Default-scoped to the package; set it per hosted\n * app when two apps share an origin. */\n key?: string;\n}\n\n/** Where the last `saveNote` landed; `null` before the first one. */\nexport type SaveLocation = \"server\" | \"local\";\n\nexport function createIterationStore(cfg: IterationStoreConfig = {}) {\n const endpoint = cfg.endpoint ?? null;\n const key = cfg.key ?? FEEDBACK_STORAGE_KEY;\n let lastSave: SaveLocation | null = null;\n let migration: Promise<void> | null = null;\n\n function parseLocal(raw: string | null): IterationNote[] {\n try {\n const items = JSON.parse(raw ?? '[]') as IterationNote[];\n if (!Array.isArray(items) || !items.every((note) => note && typeof note.id === 'string' &&\n typeof note.route === 'string' && typeof note.feedback === 'string' && typeof note.createdAt === 'string')) throw new Error('Invalid notes');\n return items.map((i) => ({ ...i, done: i.done ?? false })).map((i) => ({\n ...i,\n element: i.element ?? null,\n }));\n } catch (cause) {\n throw new Error('Could not read local feedback. Browser storage has been left untouched.', { cause });\n }\n }\n\n function localAll(): IterationNote[] {\n let storage: Storage;\n try { storage = window.localStorage; } catch { return []; }\n return parseLocal(readBrowserStorage(storage, key, (current, previous) => {\n const merged = new Map([...parseLocal(previous), ...parseLocal(current)].map(note => [note.id, note]));\n return JSON.stringify([...merged.values()]);\n }));\n }\n\n function localWrite(items: IterationNote[]) {\n writeBrowserStorage(window.localStorage, key, JSON.stringify(items));\n }\n\n function acknowledge(item: IterationNote): void {\n const current = localAll();\n const snapshot = JSON.stringify(item);\n const remaining = current.filter((note) => note.id !== item.id || JSON.stringify(note) !== snapshot);\n if (remaining.length !== current.length) localWrite(remaining);\n }\n\n async function flushLocalBacklog(): Promise<void> {\n if (!endpoint) return;\n const backlog = localAll();\n if (backlog.length === 0) return;\n for (const item of backlog) {\n // Notes saved by an older client may exceed today's server limit.\n // Keep them in the browser, but let valid notes behind them sync.\n if (item.feedback.trim().length > MAX_FEEDBACK_LENGTH) continue;\n let rejected = false;\n const saved = await request(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n id: item.id,\n createdAt: item.createdAt,\n route: item.route,\n feedback: item.feedback,\n element: item.element ?? null,\n ...(item.elements !== undefined ? { elements: item.elements } : {}),\n ...(item.variant !== undefined ? { variant: item.variant } : {}),\n ...(item.variantAction !== undefined ? { variantAction: item.variantAction } : {}),\n ...(item.payload !== undefined ? { payload: item.payload } : {}),\n ...(item.viewport ? { viewport: item.viewport } : {}),\n }),\n }, async (res) => {\n if (!res.ok) {\n rejected = res.status >= 400 && res.status < 500;\n return null;\n }\n return await res.json() as { id?: unknown };\n });\n if (!saved || typeof saved.id !== 'string' || !saved.id) {\n if (rejected) continue;\n return; // A transient failure or missing acknowledgement stops the retry.\n }\n // A save may have happened while the request was in flight. Remove\n // only the exact snapshot acknowledged by the server, never the queue.\n acknowledge(item);\n }\n }\n\n function migrateLocalBacklog(): Promise<void> {\n migration ??= flushLocalBacklog().finally(() => { migration = null; });\n return migration;\n }\n\n async function readRemote(url: string): Promise<IterationNote[]> {\n await migrateLocalBacklog().catch(() => {});\n const local = localAll();\n try {\n const remote = await request(url, undefined, async (res) => {\n if (!res.ok) throw new Error(`Loading iteration notes failed (${res.status})`);\n return await res.json() as IterationNote[];\n });\n const pendingIds = new Set(local.map((note) => note.id));\n return [...remote.filter((note) => !pendingIds.has(note.id)), ...local];\n } catch (error) {\n if (local.length) return local;\n throw error;\n }\n }\n\n /** Notes for one route, or every note on every route when `route` is omitted. */\n async function listNotes(route?: string): Promise<IterationNote[]> {\n if (!endpoint) {\n return localAll()\n .filter((i) => route === undefined || i.route === route)\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n }\n return (await readRemote(\n route === undefined ? `${endpoint}?all=1` : `${endpoint}?route=${encodeURIComponent(route)}`,\n )).filter((note) => route === undefined || note.route === route)\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n }\n\n /** Every still-open note across all routes, newest first. */\n async function pendingNotes(): Promise<IterationNote[]> {\n if (!endpoint) {\n return localAll()\n .filter((i) => !i.done)\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n }\n return (await readRemote(endpoint)).filter((note) => !note.done)\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n }\n\n function saveLocal(\n route: string,\n feedback: string,\n element: IterationElement | null,\n id: string | null,\n payload?: unknown,\n viewport?: { w: number; h: number },\n context?: NoteContext,\n ): string {\n const all = localAll();\n let savedId: string;\n const defaultsRequest = feedback.startsWith('[set-defaults]');\n const existing = id ? all.find((f) => f.id === id && f.route === route) : defaultsRequest ? all.filter(n => !n.done && n.route === route && n.feedback.startsWith('[set-defaults]')).sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0] : undefined;\n if (existing) {\n if (defaultsRequest && !id) {\n delete existing.elements;\n delete existing.variant;\n delete existing.variantAction;\n delete existing.payload;\n }\n existing.feedback = feedback;\n existing.element = element;\n if (payload !== undefined) existing.payload = payload;\n if (defaultsRequest) {\n existing.createdAt = new Date().toISOString();\n if (viewport) existing.viewport = viewport;\n }\n if (context) Object.assign(existing, context);\n if (existing.elements) existing.element = existing.elements[0] ?? null;\n savedId = existing.id;\n } else {\n savedId = id ?? createNoteId();\n all.push({\n id: savedId,\n route,\n feedback,\n element,\n done: false,\n createdAt: new Date().toISOString(),\n ...(viewport ? { viewport } : {}),\n ...(payload !== undefined ? { payload } : {}),\n ...context,\n ...(context?.elements ? { element: context.elements[0] ?? null } : {}),\n });\n }\n localWrite(defaultsRequest ? all.filter(n => n.id === savedId || n.done || n.route !== route || !n.feedback.startsWith('[set-defaults]')) : all);\n return savedId;\n }\n\n async function saveNote(\n route: string,\n feedback: string,\n element: IterationElement | null,\n id: string | null,\n /** Opaque host context, attached only when the caller sends it. */\n payload?: unknown,\n /** Window size at write time; attached on create only. */\n viewport?: { w: number; h: number },\n context?: NoteContext,\n ): Promise<string> {\n if (feedback.trim().length > MAX_FEEDBACK_LENGTH) {\n throw new RangeError(`Feedback must be at most ${MAX_FEEDBACK_LENGTH} characters.`);\n }\n if (!endpoint) {\n const savedId = saveLocal(route, feedback, element, id, payload, viewport, context);\n lastSave = \"local\";\n return savedId;\n }\n // Stable across a lost response and its retry: the server treats this as\n // an upsert, so reconnecting cannot create a second copy of the note.\n const savedId = id ?? (feedback.startsWith('[set-defaults]') ? null : createNoteId());\n const pendingBefore = localAll().find((note) => note.id === savedId);\n const saveOffline = () => {\n const localId = saveLocal(route, feedback, element, savedId, payload, viewport, context);\n lastSave = 'local';\n return localId;\n };\n let res: Response | undefined;\n let result: { id?: unknown; error?: unknown } | null;\n try {\n result = await request(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n id: savedId,\n route,\n feedback,\n element,\n ...context,\n ...(payload !== undefined ? { payload } : {}),\n ...(viewport ? { viewport } : {}),\n }),\n }, async (response) => {\n res = response;\n if (!response.ok && response.status >= 500) throw new Error(`Saving iteration note failed (${response.status})`);\n return await response.json().catch((error) => {\n if (response.ok) throw error;\n return null;\n }) as { id?: unknown; error?: unknown } | null;\n });\n } catch {\n if (res && !res.ok && res.status < 500) throw new Error(`Saving iteration note failed (${res.status})`);\n return saveOffline();\n }\n // Validation errors require a correction, not a permanently stuck queue.\n if (!res?.ok) {\n throw new Error(`Saving iteration note failed (${res?.status})${typeof result?.error === 'string' ? `: ${result.error}` : ''}`);\n }\n if (!result || typeof result.id !== 'string' || !result.id) return saveOffline();\n if (pendingBefore) acknowledge(pendingBefore);\n lastSave = localAll().length ? 'local' : 'server';\n return result.id;\n }\n\n async function deleteNote(id: string): Promise<void> {\n if (!endpoint) {\n localWrite(localAll().filter((f) => f.id !== id));\n return;\n }\n const res = await request(`${endpoint}?id=${encodeURIComponent(id)}`, {\n method: \"DELETE\",\n });\n if (!res.ok) throw new Error(`Deleting iteration note failed (${res.status})`);\n const local = localAll();\n if (local.some((note) => note.id === id)) localWrite(local.filter((note) => note.id !== id));\n }\n\n return {\n listNotes,\n pendingNotes,\n saveNote,\n deleteNote,\n endpoint,\n /** Where the last save landed, for the panel's sync notice. */\n get lastSave(): SaveLocation | null {\n return lastSave;\n },\n get hasUnsyncedNotes(): boolean {\n return localAll().length > 0;\n },\n };\n}\nexport * from './variants';\n"],"mappings":"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","names":["savedId"]}
1
+ {"version":3,"sources":["../src/core/id.ts","../src/core/index.ts"],"sourcesContent":["let fallbackCounter = 0;\n\n/** IDs must also work on HTTP development hosts, where randomUUID is absent. */\nexport function createNoteId(): string {\n const crypto = globalThis.crypto;\n if (typeof crypto?.randomUUID === 'function') return crypto.randomUUID();\n\n if (typeof crypto?.getRandomValues === 'function') {\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n }\n\n // Old browsers without Web Crypto still need unique, stable retry IDs.\n fallbackCounter += 1;\n return `${Date.now().toString(36)}-${fallbackCounter.toString(36)}-${Math.random().toString(36).slice(2)}`;\n}\n","/**\n * isotone — core.\n *\n * The framework-agnostic half of the internal feedback loop. Holds the note\n * model, the element descriptor, the dev-store client (file-backed via a Vite\n * plugin, `localStorage` fallback), and the route visibility matcher. This\n * module has no React and no Vite dependency and is fully unit-testable in a\n * `node` env.\n */\nimport { request } from './request';\nimport { FEEDBACK_STORAGE_KEY, readBrowserStorage, writeBrowserStorage } from './browser-storage';\nimport { MAX_FEEDBACK_LENGTH } from './limits';\nimport { createNoteId } from './id';\n\n/** A UI element picked with the panel's selector mode. */\nexport interface IterationElement {\n /** Lowercase tag name, e.g. \"button\". */\n tag: string;\n /** Trimmed text-content snippet (may be empty for visual elements). */\n text: string;\n /** Best-effort CSS path for locating the element in code. */\n selector: string;\n}\n\n/** A durable reference to a selector or one of its options, not a DOM path. */\nexport interface VariantReference {\n axisId: string;\n label: string;\n optionId?: string;\n optionLabel?: string;\n value?: string;\n}\n\nexport interface VariantRemoval {\n axisId: string;\n action: 'remove-option' | 'remove-axis' | 'keep-default';\n optionId?: string;\n}\n\nexport interface NoteContext {\n /** Pending instruction: deleting its note cancels it before the agent acts. */\n variantAction?: VariantRemoval;\n elements?: IterationElement[];\n variant?: VariantReference | null;\n}\n\n/** A note written in the panel, read and answered by the coding agent. */\nexport interface IterationNote extends NoteContext {\n id: string;\n /** The route the note was written on, e.g. \"/projects\". */\n route: string;\n feedback: string;\n element: IterationElement | null;\n done: boolean;\n doneAt?: string | null;\n createdAt: string;\n /** The window size the note was written at — layout feedback only means\n * something at the breakpoint it was seen on. */\n viewport?: { w: number; h: number };\n /** Host-specific context the store round-trips without interpreting — see\n * `StoredNote.payload`. */\n payload?: unknown;\n}\n\n/** The minimal structural surface the descriptor builder needs from a DOM\n * node. `children` is `readonly` so both a real `HTMLCollection` and a plain\n * test array satisfy it; the builder materializes it with `Array.from`. */\nexport interface NodeLike {\n tagName: string;\n id?: string | null;\n parentElement?: NodeLike | null;\n children?: readonly NodeLike[];\n textContent?: string | null;\n /** One-based child position, when the adapter can provide it cheaply. */\n indexInParent?: number;\n}\n\n/**\n * Best-effort descriptor of a picked element for locating it in code. Walks\n * up the tree collecting `#id` (preferred) or `tag:nth-child(n)` segments up\n * to a shallow depth, stopping at `BODY` or the depth cap. Kept framework-free\n * so it is unit-testable.\n */\nexport function describeIterationElement(\n el: NodeLike,\n maxDepth = 4,\n): IterationElement {\n const parts: string[] = [];\n let node: NodeLike | null | undefined = el;\n let depth = 0;\n while (node && node.tagName !== \"BODY\" && depth < maxDepth) {\n if (node.id) {\n // Escape every code point: valid for leading digits, punctuation and\n // framework-generated ids without depending on the host's CSS global.\n const id = Array.from(node.id, (c) => `\\\\${c.codePointAt(0)?.toString(16)} `).join('');\n parts.unshift(`#${/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(node.id) ? node.id : id}`);\n break;\n }\n let part = node.tagName.toLowerCase();\n const parent: NodeLike | null | undefined = node.parentElement;\n if (parent) {\n const index = node.indexInParent ?? Array.from(parent.children ?? []).indexOf(node) + 1;\n if (index > 0) part += `:nth-child(${index})`;\n }\n parts.unshift(part);\n node = parent;\n depth++;\n }\n return {\n tag: el.tagName.toLowerCase(),\n text: (el.textContent ?? \"\").trim().replace(/\\s+/g, \" \").slice(0, 80),\n selector: parts.join(\" > \"),\n };\n}\n\n/** Lazy DOM views: describing one element never walks unrelated subtrees. */\nexport function elementAsNode(el: Element): NodeLike {\n const cache = new WeakMap<Element, NodeLike>();\n const view = (n: Element | null | undefined): NodeLike | null => {\n if (!n) return null;\n const cached = cache.get(n);\n if (cached) return cached;\n const created: NodeLike = {\n tagName: n.tagName,\n id: n.id || null,\n get textContent() { return n.textContent; },\n get parentElement() { return view(n.parentElement); },\n get children() { return Array.from(n.children, (child) => view(child) as NodeLike); },\n get indexInParent() {\n let index = 1;\n for (let sibling = n.previousElementSibling; sibling; sibling = sibling.previousElementSibling) index++;\n return index;\n },\n };\n cache.set(n, created);\n return created;\n };\n const root = view(el);\n if (root === null) {\n throw new Error('unreachable: a real element always maps to a view');\n }\n return root;\n}\n\n/** A matcher over a console route: include a page only when it is not hidden\n * (denylist) or, when a visible list is given, only when it matches. */\nexport interface RouteGateOptions {\n /** Exact routes (or prefixes ending in `*`) to NEVER show the panel on,\n * e.g. the auth flow. Defaults to the auth/login/error pages. */\n hidden?: string[];\n /** When set, visible only on these routes/prefixes (allowlist). Overrides\n * `hidden` — used when a repo wants an explicit allow-list. */\n visible?: string[];\n /** Prefixes always barred regardless of `visible` (e.g. `/api`, `/auth`). */\n alwaysHidden?: string[];\n}\n\nconst DEFAULT_HIDDEN = [\"/login\", \"/logout\", \"/register\", \"/error\", \"/_error\"];\n\nfunction matchRoutePattern(route: string, pattern: string): boolean {\n if (pattern.endsWith('*')) return route.startsWith(pattern.slice(0, -1));\n if (pattern.endsWith(\"/\")) return route.startsWith(pattern);\n return route === pattern || route.startsWith(`${pattern}/`);\n}\n\n/** Whether the panel may mount on the given route. Pure and unit-testable. */\nexport function isRouteVisible(route: string, rule: RouteGateOptions = {}): boolean {\n const hidden = rule.hidden ?? DEFAULT_HIDDEN;\n const alwaysHidden = rule.alwaysHidden ?? [\"/api\", \"/auth\", \"/login\"];\n // A prefix match on the \"always\" list is unconditionally hidden — the panel\n // must never appear on auth or API surfaces regardless of allow-list.\n for (const p of alwaysHidden) {\n if (route === p || route.startsWith(`${p}/`)) return false;\n }\n if (rule.visible && rule.visible.length > 0) {\n return rule.visible.some((v) => matchRoutePattern(route, v));\n }\n return !hidden.some((h) => matchRoutePattern(route, h));\n}\n\n/* ---------------------------------------------------------------------------\n * Store: client transport for the notes. The Vite plugin serves the endpoint\n * (same-origin, dev-only); a `localStorage` fallback keeps notes when the\n * dev server is unreachable. The endpoint and storage namespace are\n * configurable so multi-app origins don't collide.\n * ------------------------------------------------------------------------- */\n\nexport interface IterationStoreConfig {\n /** Absolute or same-origin URL of the notes endpoint. Must be set (by the\n * Vite plugin's `iteration()` options or a framework preset) for a shared\n * file-backed store; when omitted, only localStorage persists. */\n endpoint?: string | null;\n /** localStorage namespace. Default-scoped to the package; set it per hosted\n * app when two apps share an origin. */\n key?: string;\n}\n\n/** Where the last `saveNote` landed; `null` before the first one. */\nexport type SaveLocation = \"server\" | \"local\";\n\nexport function createIterationStore(cfg: IterationStoreConfig = {}) {\n const endpoint = cfg.endpoint ?? null;\n const key = cfg.key ?? FEEDBACK_STORAGE_KEY;\n let lastSave: SaveLocation | null = null;\n let migration: Promise<void> | null = null;\n\n function parseLocal(raw: string | null): IterationNote[] {\n try {\n const items = JSON.parse(raw ?? '[]') as IterationNote[];\n if (!Array.isArray(items) || !items.every((note) => note && typeof note.id === 'string' &&\n typeof note.route === 'string' && typeof note.feedback === 'string' && typeof note.createdAt === 'string')) throw new Error('Invalid notes');\n return items.map((i) => ({ ...i, done: i.done ?? false })).map((i) => ({\n ...i,\n element: i.element ?? null,\n }));\n } catch (cause) {\n throw new Error('Could not read local feedback. Browser storage has been left untouched.', { cause });\n }\n }\n\n function localAll(): IterationNote[] {\n let storage: Storage;\n try { storage = window.localStorage; } catch { return []; }\n return parseLocal(readBrowserStorage(storage, key, (current, previous) => {\n const merged = new Map([...parseLocal(previous), ...parseLocal(current)].map(note => [note.id, note]));\n return JSON.stringify([...merged.values()]);\n }));\n }\n\n function localWrite(items: IterationNote[]) {\n writeBrowserStorage(window.localStorage, key, JSON.stringify(items));\n }\n\n function acknowledge(item: IterationNote): void {\n const current = localAll();\n const snapshot = JSON.stringify(item);\n const remaining = current.filter((note) => note.id !== item.id || JSON.stringify(note) !== snapshot);\n if (remaining.length !== current.length) localWrite(remaining);\n }\n\n async function flushLocalBacklog(): Promise<void> {\n if (!endpoint) return;\n const backlog = localAll();\n if (backlog.length === 0) return;\n for (const item of backlog) {\n // Notes saved by an older client may exceed today's server limit.\n // Keep them in the browser, but let valid notes behind them sync.\n if (item.feedback.trim().length > MAX_FEEDBACK_LENGTH) continue;\n let rejected = false;\n const saved = await request(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n id: item.id,\n createdAt: item.createdAt,\n route: item.route,\n feedback: item.feedback,\n element: item.element ?? null,\n ...(item.elements !== undefined ? { elements: item.elements } : {}),\n ...(item.variant !== undefined ? { variant: item.variant } : {}),\n ...(item.variantAction !== undefined ? { variantAction: item.variantAction } : {}),\n ...(item.payload !== undefined ? { payload: item.payload } : {}),\n ...(item.viewport ? { viewport: item.viewport } : {}),\n }),\n }, async (res) => {\n if (!res.ok) {\n rejected = res.status >= 400 && res.status < 500;\n return null;\n }\n return await res.json() as { id?: unknown };\n });\n if (!saved || typeof saved.id !== 'string' || !saved.id) {\n if (rejected) continue;\n return; // A transient failure or missing acknowledgement stops the retry.\n }\n // A save may have happened while the request was in flight. Remove\n // only the exact snapshot acknowledged by the server, never the queue.\n acknowledge(item);\n }\n }\n\n function migrateLocalBacklog(): Promise<void> {\n migration ??= flushLocalBacklog().finally(() => { migration = null; });\n return migration;\n }\n\n async function readRemote(url: string): Promise<IterationNote[]> {\n await migrateLocalBacklog().catch(() => {});\n const local = localAll();\n try {\n const remote = await request(url, undefined, async (res) => {\n if (!res.ok) throw new Error(`Loading iteration notes failed (${res.status})`);\n return await res.json() as IterationNote[];\n });\n const pendingIds = new Set(local.map((note) => note.id));\n return [...remote.filter((note) => !pendingIds.has(note.id)), ...local];\n } catch (error) {\n if (local.length) return local;\n throw error;\n }\n }\n\n /** Notes for one route, or every note on every route when `route` is omitted. */\n async function listNotes(route?: string): Promise<IterationNote[]> {\n if (!endpoint) {\n return localAll()\n .filter((i) => route === undefined || i.route === route)\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n }\n return (await readRemote(\n route === undefined ? `${endpoint}?all=1` : `${endpoint}?route=${encodeURIComponent(route)}`,\n )).filter((note) => route === undefined || note.route === route)\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n }\n\n /** Every still-open note across all routes, newest first. */\n async function pendingNotes(): Promise<IterationNote[]> {\n if (!endpoint) {\n return localAll()\n .filter((i) => !i.done)\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n }\n return (await readRemote(endpoint)).filter((note) => !note.done)\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n }\n\n function saveLocal(\n route: string,\n feedback: string,\n element: IterationElement | null,\n id: string | null,\n payload?: unknown,\n viewport?: { w: number; h: number },\n context?: NoteContext,\n ): string {\n const all = localAll();\n let savedId: string;\n const defaultsRequest = feedback.startsWith('[set-defaults]');\n const existing = id ? all.find((f) => f.id === id && f.route === route) : defaultsRequest ? all.filter(n => !n.done && n.route === route && n.feedback.startsWith('[set-defaults]')).sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0] : undefined;\n if (existing) {\n if (defaultsRequest && !id) {\n delete existing.elements;\n delete existing.variant;\n delete existing.variantAction;\n delete existing.payload;\n }\n existing.feedback = feedback;\n existing.element = element;\n if (payload !== undefined) existing.payload = payload;\n if (defaultsRequest) {\n existing.createdAt = new Date().toISOString();\n if (viewport) existing.viewport = viewport;\n }\n if (context) Object.assign(existing, context);\n if (existing.elements) existing.element = existing.elements[0] ?? null;\n savedId = existing.id;\n } else {\n savedId = id ?? createNoteId();\n all.push({\n id: savedId,\n route,\n feedback,\n element,\n done: false,\n createdAt: new Date().toISOString(),\n ...(viewport ? { viewport } : {}),\n ...(payload !== undefined ? { payload } : {}),\n ...context,\n ...(context?.elements ? { element: context.elements[0] ?? null } : {}),\n });\n }\n localWrite(defaultsRequest ? all.filter(n => n.id === savedId || n.done || n.route !== route || !n.feedback.startsWith('[set-defaults]')) : all);\n return savedId;\n }\n\n async function saveNote(\n route: string,\n feedback: string,\n element: IterationElement | null,\n id: string | null,\n /** Opaque host context, attached only when the caller sends it. */\n payload?: unknown,\n /** Window size at write time; attached on create only. */\n viewport?: { w: number; h: number },\n context?: NoteContext,\n ): Promise<string> {\n if (feedback.trim().length > MAX_FEEDBACK_LENGTH) {\n throw new RangeError(`Feedback must be at most ${MAX_FEEDBACK_LENGTH} characters.`);\n }\n if (!endpoint) {\n const savedId = saveLocal(route, feedback, element, id, payload, viewport, context);\n lastSave = \"local\";\n return savedId;\n }\n // Stable across a lost response and its retry: the server treats this as\n // an upsert, so reconnecting cannot create a second copy of the note.\n const savedId = id ?? (feedback.startsWith('[set-defaults]') ? null : createNoteId());\n const pendingBefore = localAll().find((note) => note.id === savedId);\n const saveOffline = () => {\n const localId = saveLocal(route, feedback, element, savedId, payload, viewport, context);\n lastSave = 'local';\n return localId;\n };\n let res: Response | undefined;\n let result: { id?: unknown; error?: unknown } | null;\n try {\n result = await request(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n id: savedId,\n route,\n feedback,\n element,\n ...context,\n ...(payload !== undefined ? { payload } : {}),\n ...(viewport ? { viewport } : {}),\n }),\n }, async (response) => {\n res = response;\n if (!response.ok && response.status >= 500) throw new Error(`Saving iteration note failed (${response.status})`);\n return await response.json().catch((error) => {\n if (response.ok) throw error;\n return null;\n }) as { id?: unknown; error?: unknown } | null;\n });\n } catch {\n if (res && !res.ok && res.status < 500) throw new Error(`Saving iteration note failed (${res.status})`);\n return saveOffline();\n }\n // Validation errors require a correction, not a permanently stuck queue.\n if (!res?.ok) {\n throw new Error(`Saving iteration note failed (${res?.status})${typeof result?.error === 'string' ? `: ${result.error}` : ''}`);\n }\n if (!result || typeof result.id !== 'string' || !result.id) return saveOffline();\n if (pendingBefore) acknowledge(pendingBefore);\n lastSave = 'server';\n return result.id;\n }\n\n async function deleteNote(id: string): Promise<void> {\n if (!endpoint) {\n localWrite(localAll().filter((f) => f.id !== id));\n return;\n }\n const res = await request(`${endpoint}?id=${encodeURIComponent(id)}`, {\n method: \"DELETE\",\n });\n if (!res.ok) throw new Error(`Deleting iteration note failed (${res.status})`);\n const local = localAll();\n if (local.some((note) => note.id === id)) localWrite(local.filter((note) => note.id !== id));\n }\n\n return {\n listNotes,\n pendingNotes,\n saveNote,\n deleteNote,\n endpoint,\n /** Where the last save landed, for the panel's sync notice. */\n get lastSave(): SaveLocation | null {\n return lastSave;\n },\n get hasUnsyncedNotes(): boolean {\n return localAll().length > 0;\n },\n };\n}\nexport * from './variants';\n"],"mappings":"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","names":["savedId"]}
package/dist/cli.js CHANGED
@@ -13,14 +13,14 @@ import {
13
13
  syncVariant,
14
14
  variantDir,
15
15
  writeActive
16
- } from "./chunk-ZBULKB3S.js";
16
+ } from "./chunk-DD6BKC3R.js";
17
17
  import {
18
18
  overlayGroups,
19
19
  variantAppliesTo
20
- } from "./chunk-SNGBNVH2.js";
20
+ } from "./chunk-OSETWKGE.js";
21
21
  import {
22
22
  checkUpdate
23
- } from "./chunk-UMVPMPVE.js";
23
+ } from "./chunk-LHCQL5PA.js";
24
24
  import {
25
25
  readNotes,
26
26
  updateNotes
package/dist/core.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  describeIterationElement,
4
4
  elementAsNode,
5
5
  isRouteVisible
6
- } from "./chunk-YWMOXPE7.js";
6
+ } from "./chunk-UYMMI2IH.js";
7
7
  import {
8
8
  KNOBS_STORAGE_KEY,
9
9
  OVERLAY_DEFAULT_OPTION,
@@ -28,7 +28,7 @@ import {
28
28
  serializeVariantParam,
29
29
  variantAppliesTo,
30
30
  visibleAxes
31
- } from "./chunk-SNGBNVH2.js";
31
+ } from "./chunk-OSETWKGE.js";
32
32
  import "./chunk-A4DX2V4C.js";
33
33
  export {
34
34
  KNOBS_STORAGE_KEY,
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  describeIterationElement,
5
5
  elementAsNode,
6
6
  isRouteVisible
7
- } from "./chunk-YWMOXPE7.js";
7
+ } from "./chunk-UYMMI2IH.js";
8
8
  import {
9
9
  FEEDBACK_STORAGE_KEY,
10
10
  KNOBS_STORAGE_KEY,
@@ -32,7 +32,7 @@ import {
32
32
  serializeVariantParam,
33
33
  visibleAxes,
34
34
  writeBrowserStorage
35
- } from "./chunk-SNGBNVH2.js";
35
+ } from "./chunk-OSETWKGE.js";
36
36
  import {
37
37
  MAX_FEEDBACK_LENGTH
38
38
  } from "./chunk-A4DX2V4C.js";
@@ -1894,7 +1894,7 @@ function IterationPanel({
1894
1894
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1895
1895
  ...context
1896
1896
  }]);
1897
- setUnsynced(store.lastSave === "local");
1897
+ setUnsynced(store.hasUnsyncedNotes);
1898
1898
  setNotesOpen(true);
1899
1899
  } finally {
1900
1900
  finish();
@@ -1949,7 +1949,7 @@ function IterationPanel({
1949
1949
  setNotes(
1950
1950
  (prev) => prev.map((n) => n.id === tempId ? { ...n, id: savedId } : n)
1951
1951
  );
1952
- setUnsynced(store.lastSave === "local");
1952
+ setUnsynced(store.hasUnsyncedNotes);
1953
1953
  } catch {
1954
1954
  setNotes(
1955
1955
  (prev) => previous ? prev.map((n) => n.id === tempId ? previous : n) : prev.filter((n) => n.id !== tempId)