isotone 0.4.0 → 0.4.1
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.
- package/CHANGELOG.md +13 -0
- package/dist/chunk-A4DX2V4C.js +7 -0
- package/dist/chunk-A4DX2V4C.js.map +1 -0
- package/dist/{chunk-DD767R7E.js → chunk-AP7O357L.js} +6 -2
- package/dist/chunk-AP7O357L.js.map +1 -0
- package/dist/{chunk-QT27DAPQ.js → chunk-UMVPMPVE.js} +2 -3
- package/dist/{chunk-QT27DAPQ.js.map → chunk-UMVPMPVE.js.map} +1 -1
- package/dist/{chunk-GDVZ3B6N.js → chunk-YWMOXPE7.js} +43 -6
- package/dist/chunk-YWMOXPE7.js.map +1 -0
- package/dist/{chunk-7WGLOXRQ.js → chunk-ZBULKB3S.js} +3 -45
- package/dist/chunk-ZBULKB3S.js.map +1 -0
- package/dist/cli.js +10 -309
- package/dist/cli.js.map +1 -1
- package/dist/core.d.ts +1 -0
- package/dist/core.js +2 -1
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/isotone.global.js +18 -18
- package/dist/serve.js +3 -2
- package/dist/serve.js.map +1 -1
- package/dist/server.js +3 -2
- package/dist/server.js.map +1 -1
- package/dist/vite-plugin.js +4 -3
- package/dist/vite-plugin.js.map +1 -1
- package/docs/migration.md +31 -83
- package/docs/updates.md +3 -3
- package/package.json +1 -1
- package/dist/chunk-7WGLOXRQ.js.map +0 -1
- package/dist/chunk-DD767R7E.js.map +0 -1
- package/dist/chunk-GDVZ3B6N.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.1
|
|
4
|
+
|
|
5
|
+
- Keep long feedback in the composer with a clear 4,000-character limit; older
|
|
6
|
+
overlong or rejected offline notes no longer block later notes from syncing.
|
|
7
|
+
- Save notes on HTTP development hosts where `crypto.randomUUID` is unavailable.
|
|
8
|
+
- Remove the unused selector deletion endpoint and its direct `knobs.json` write.
|
|
9
|
+
Selector removal remains a feedback request for the coding agent.
|
|
10
|
+
- Replace the automatic project migration command with a short manual guide.
|
|
11
|
+
|
|
3
12
|
## 0.4.0
|
|
4
13
|
|
|
5
14
|
- Rename the package and CLI to Isotone (`isotone`), with a previewable migration
|
|
@@ -37,6 +46,10 @@
|
|
|
37
46
|
- Serialize variant edits across agent processes to preserve concurrent additions.
|
|
38
47
|
- Make `init --help` read-only even when combined with setup flags.
|
|
39
48
|
|
|
49
|
+
0.4.0 behavior changes: notes over 4,000 characters are rejected rather than
|
|
50
|
+
silently cut, and the standalone `serve` command binds to loopback only; it no
|
|
51
|
+
longer accepts `0.0.0.0`.
|
|
52
|
+
|
|
40
53
|
## 0.3.4
|
|
41
54
|
|
|
42
55
|
- The Vite plugin's mount loader goes in the document `<head>` instead of
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/limits.ts"],"sourcesContent":["/** Maximum feedback length after trimming, shared by browser and server. */\nexport const MAX_FEEDBACK_LENGTH = 4000;\n"],"mappings":";AACO,IAAM,sBAAsB;","names":[]}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_FEEDBACK_LENGTH
|
|
3
|
+
} from "./chunk-A4DX2V4C.js";
|
|
4
|
+
|
|
1
5
|
// src/store.ts
|
|
2
6
|
import { execFileSync } from "child_process";
|
|
3
7
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -212,7 +216,7 @@ function handleNotesRequest(file, req) {
|
|
|
212
216
|
if (cleanFeedback === "") {
|
|
213
217
|
return json(400, { error: "feedback is required" });
|
|
214
218
|
}
|
|
215
|
-
if (cleanFeedback.length >
|
|
219
|
+
if (cleanFeedback.length > MAX_FEEDBACK_LENGTH) return json(400, { error: `feedback must be at most ${MAX_FEEDBACK_LENGTH} characters` });
|
|
216
220
|
if (payload !== void 0 && !withinPayloadCap(payload)) {
|
|
217
221
|
return json(400, {
|
|
218
222
|
error: `payload exceeds ${PAYLOAD_MAX} bytes or is not serializable`
|
|
@@ -348,4 +352,4 @@ export {
|
|
|
348
352
|
notesResponse,
|
|
349
353
|
NOTES_ENDPOINT
|
|
350
354
|
};
|
|
351
|
-
//# sourceMappingURL=chunk-
|
|
355
|
+
//# sourceMappingURL=chunk-AP7O357L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store.ts","../src/file-store.ts"],"sourcesContent":["/**\n * File-backed store for iteration notes — shared by the Vite plugin (dev\n * server) and the CLI. Notes live in a gitignored JSON file on disk, no\n * database. The path comes from the plugin/CLI options.\n */\nimport { execFileSync } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\n\nimport type { IterationElement, NoteContext, VariantReference, VariantRemoval } from './core/index';\nimport { MAX_FEEDBACK_LENGTH } from './core/limits';\nimport { withFileLock, writeJson } from './file-store';\n\nexport interface StoredNote extends NoteContext {\n id: string;\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. Feedback about layout only\n * means something at the breakpoint it was seen on, so the agent reads\n * this next to the selector. Captured on create, kept on edit. */\n viewport?: { w: number; h: number };\n /** Host-specific context, stored and returned verbatim and never\n * interpreted here. A host with state that gives a note its meaning — the\n * knob settings a design note was written against, a feature-flag set, the\n * selected tenant — nests it under this key so the note survives a\n * round-trip through a store that knows nothing about it. Capped at\n * {@link PAYLOAD_MAX} serialized bytes. */\n payload?: unknown;\n}\n\n/** Serialized-size ceiling for a note's opaque `payload`, in bytes. Generous\n * next to normal review context and small\n * enough that a runaway host is rejected rather than bloating the file. */\nexport const PAYLOAD_MAX = 65_536;\n\nexport function readNotes(file: string): StoredNote[] {\n if (!existsSync(file)) return [];\n try {\n const raw = readFileSync(file, 'utf8').trim();\n if (!raw) return [];\n const data = JSON.parse(raw);\n if (!Array.isArray(data) || !data.every((note) => note && typeof note === 'object' &&\n typeof note.id === 'string' && typeof note.route === 'string' &&\n typeof note.feedback === 'string' && typeof note.createdAt === 'string')) {\n throw new Error('expected an array of notes');\n }\n return data as StoredNote[];\n } catch (cause) {\n throw new Error(`Could not read notes from ${file}; the file has been left untouched.`, { cause });\n }\n}\n\nexport function writeNotes(file: string, notes: StoredNote[]): void {\n // A `file` option can point into a directory that does not exist yet\n // (`notes/here.json`); the first note creates it.\n writeJson(file, notes);\n}\n\nexport function updateNotes(file: string, update: (notes: StoredNote[]) => StoredNote[]): void {\n withFileLock(file, () => writeNotes(file, update(readNotes(file))));\n}\n\n/**\n * Ensure the feedback file path is ignored by git via the repo-local\n * `.git/info/exclude` (never committed), so a consumer needs no `.gitignore`\n * edit. Idempotent and non-destructive; never fails the dev server.\n */\nexport function ensureFileIgnored(file: string): void {\n try {\n // Resolve through symlinks so the path is comparable with git's answer,\n // which is already canonical (macOS's /var → /private/var, for one).\n // The file itself may not exist yet, so canonicalize its directory.\n const dir = realpathSync(dirname(resolve(file)));\n const absolute = join(dir, basename(file));\n const gitRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], {\n cwd: dir,\n encoding: 'utf8',\n // Outside a repo git writes \"fatal: not a git repository\" to stderr, and\n // inheriting it prints that over the server's own startup banner. The\n // throw is the signal we act on; the message is noise.\n stdio: ['ignore', 'pipe', 'ignore'],\n }).trim();\n const pattern = excludePattern(gitRoot, absolute);\n if (!pattern) return;\n const infoExclude = excludeFile(dir);\n const existing = existsSync(infoExclude) ? readFileSync(infoExclude, 'utf8') : '';\n if (hasExcludePattern(existing, pattern)) return;\n const marker = existing && !existing.endsWith('\\n') ? '\\n' : '';\n // One comment header per exclude file, however many patterns land under it.\n const heading = '# isotone (local dev feedback loop)';\n const comment = existing.includes(heading) ? '' : `${heading}\\n`;\n mkdirSync(dirname(infoExclude), { recursive: true });\n writeFileSync(infoExclude, `${existing}${marker}${comment}${pattern}\\n`);\n } catch {\n // A stray dev-only feedback file is non-fatal if we can't ignore it.\n }\n}\n\n/**\n * Like {@link ensureFileIgnored}, for a directory: the pattern gets a trailing\n * slash so it matches the tree, not a file of the same name. Used for the\n * overlay variants directory when a host opts out of committing it.\n */\nexport function ensureDirIgnored(dir: string): void {\n try {\n const absolute = realpathSync(resolve(dir));\n const gitRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], {\n cwd: absolute,\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n }).trim();\n const base = excludePattern(gitRoot, absolute);\n if (!base) return;\n const pattern = `${base}/`;\n const infoExclude = excludeFile(absolute);\n const existing = existsSync(infoExclude) ? readFileSync(infoExclude, 'utf8') : '';\n if (hasExcludePattern(existing, pattern) || hasExcludePattern(existing, base)) return;\n const marker = existing && !existing.endsWith('\\n') ? '\\n' : '';\n mkdirSync(dirname(infoExclude), { recursive: true });\n writeFileSync(\n infoExclude,\n `${existing}${marker}# isotone (local overlay variants)\\n${pattern}\\n`,\n );\n } catch {\n // Non-fatal, as above.\n }\n}\n\n/**\n * Where git reads the repo-local exclude file from, asked from `cwd`. Not\n * `<toplevel>/.git/info/exclude`: in a worktree `.git` is a file pointing at\n * the shared repository, and the exclude file lives in that common dir. Git\n * answers relative to the cwd when the file is under it, so resolve.\n */\nfunction excludeFile(cwd: string): string {\n const reported = execFileSync('git', ['rev-parse', '--git-path', 'info/exclude'], {\n cwd,\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n }).trim();\n return resolve(cwd, reported);\n}\n\n/**\n * The gitignore pattern that matches exactly `absolute` from `gitRoot`: its\n * path relative to the root with a leading slash, since a leading slash\n * anchors a pattern to the root of the repository. An absolute filesystem\n * path is read the same way and so matches nothing when the dev server runs\n * in a subdirectory of the checkout. Null when the file is outside the repo,\n * where no exclude entry could cover it.\n */\nexport function excludePattern(gitRoot: string, absolute: string): string | null {\n const rel = relative(gitRoot, absolute);\n if (!rel || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {\n return null;\n }\n return `/${rel.split(sep).join('/')}`;\n}\n\n/** Whether `exclude` already carries `pattern`, with or without the anchor.\n * A hand-written bare `app/console/x.json` covers the file too (a pattern\n * with a slash inside it is root-relative either way), so it counts. */\nexport function hasExcludePattern(exclude: string, pattern: string): boolean {\n const bare = pattern.replace(/^\\//, '');\n return exclude\n .split(/\\r?\\n/)\n .some((line) => line.trim() === pattern || line.trim() === bare);\n}\n\nfunction toItem(n: StoredNote) {\n const item: {\n id: string;\n route: string;\n feedback: string;\n element: IterationElement | null;\n done: boolean;\n createdAt: string;\n viewport?: { w: number; h: number };\n payload?: unknown;\n } = {\n id: n.id,\n route: n.route,\n feedback: n.feedback,\n element: n.element,\n done: n.done,\n createdAt: n.createdAt,\n };\n // Absent stays absent: a host that never sends a payload should not start\n // seeing a null one appear in its notes.\n if (n.viewport !== undefined) item.viewport = n.viewport;\n if (n.payload !== undefined) item.payload = n.payload;\n return { ...item, ...(n.elements !== undefined ? { elements: n.elements } : {}),\n ...(n.variant !== undefined ? { variant: n.variant } : {}),\n ...(n.variantAction !== undefined ? { variantAction: n.variantAction } : {}) };\n}\n\n/** The transport-agnostic result of a notes request — any runtime (Vite\n * middleware, Next route handler, Bun serve, Vercel) can render it. */\nexport interface NotesResponse {\n status: number;\n /** Response headers object (frameworks set them on their own res). */\n headers: Record<string, string>;\n /** The JSON payload to send. */\n body: unknown;\n}\n\n/** A request the pure handler understands (framework-neutral). */\nexport interface NotesRequest {\n method?: string;\n url?: string;\n body?: unknown;\n}\n\n/**\n * The transport-agnostic core of the notes endpoint. Reads/writes the local\n * file and returns a `{ status, headers, body }` the caller renders. Works\n * under Vite middleware, Next route handler, Bun.serve, or Vercel.\n */\nexport function notesResponse(file: string, req: NotesRequest): NotesResponse {\n if (req.method === 'POST' || req.method === 'DELETE') {\n return withFileLock(file, () => handleNotesRequest(file, req));\n }\n return handleNotesRequest(file, req);\n}\n\nfunction handleNotesRequest(file: string, req: NotesRequest): NotesResponse {\n const method = req.method ?? '';\n const query = Object.fromEntries(\n new URL(req.url ?? '/', 'http://localhost').searchParams,\n );\n const json = (status: number, body: unknown): NotesResponse => ({\n status,\n headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' },\n body,\n });\n\n if (method === 'GET') {\n const route = String(query.route ?? '');\n const all = query.all !== undefined && String(query.all) !== '' && String(query.all) !== '0';\n const notes = readNotes(file);\n // `?all=1` is the panel's list: every note on every route, done ones\n // included, so feedback accumulates in one place whatever page is open.\n // `?route=` scopes to a page; neither means the agent's pending backlog.\n const rows = (\n all\n ? notes\n : route\n ? notes.filter((n) => n.route === route)\n : notes.filter((n) => !n.done)\n )\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt))\n .map(toItem);\n return json(200, rows);\n }\n\n if (method === 'DELETE') {\n const id = String(query.id ?? '');\n if (!id) return json(400, { error: 'id is required' });\n writeNotes(file, readNotes(file).filter((n) => n.id !== id));\n return json(200, { ok: true });\n }\n\n if (method === 'POST') {\n const body = (req.body ?? {}) as {\n id?: unknown;\n route?: unknown;\n feedback?: unknown;\n element?: unknown;\n payload?: unknown;\n viewport?: unknown;\n elements?: unknown;\n variant?: unknown;\n variantAction?: unknown;\n createdAt?: unknown;\n };\n const { id, route, feedback, element, payload, viewport } = body;\n if (id !== undefined && id !== null && (typeof id !== 'string' || !id || id.length > 200)) {\n return json(400, { error: 'id must be a non-empty string of at most 200 characters' });\n }\n if (typeof route !== 'string' || route === '') {\n return json(400, { error: 'route is required' });\n }\n const cleanFeedback = typeof feedback === 'string' ? feedback.trim() : '';\n if (cleanFeedback === '') {\n return json(400, { error: 'feedback is required' });\n }\n if (cleanFeedback.length > MAX_FEEDBACK_LENGTH) return json(400, { error: `feedback must be at most ${MAX_FEEDBACK_LENGTH} characters` });\n if (payload !== undefined && !withinPayloadCap(payload)) {\n return json(400, {\n error: `payload exceeds ${PAYLOAD_MAX} bytes or is not serializable`,\n });\n }\n const cleanViewport = viewportOf(viewport);\n if (body.elements !== undefined && (!Array.isArray(body.elements) || body.elements.length > 50)) {\n return json(400, { error: 'elements must be an array of at most 50 elements' });\n }\n const elements = body.elements === undefined ? undefined : (body.elements as unknown[]).map(elementOf).filter((el): el is IterationElement => el !== null);\n const variant = body.variant === undefined ? undefined : variantOf(body.variant);\n const variantAction = removalOf(body.variantAction);\n if (body.variantAction !== undefined && !variantAction) return json(400, { error: 'Invalid variant action' });\n let cleanElement: IterationElement | null = null;\n if (element && typeof element === 'object' && !Array.isArray(element)) {\n cleanElement = {\n tag: cap(String((element as { tag?: unknown }).tag ?? ''), 30),\n text: cap(String((element as { text?: unknown }).text ?? ''), 120),\n selector: cap(\n String((element as { selector?: unknown }).selector ?? ''),\n 300,\n ),\n };\n }\n const notes = readNotes(file);\n const defaultsRequest = cleanFeedback.startsWith('[set-defaults]');\n const previousDefaults = defaultsRequest && !notes.some((note) => note.id === id)\n ? notes.filter(n => !n.done && n.route === route && n.feedback.startsWith('[set-defaults]')).sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0]\n : undefined;\n const updateId = previousDefaults?.id ?? id;\n if (typeof updateId === 'string' && updateId !== '') {\n const existing = notes.find((n) => n.id === updateId && n.route === route);\n if (existing) {\n if (previousDefaults) {\n delete existing.elements;\n delete existing.variant;\n delete existing.variantAction;\n delete existing.payload;\n }\n existing.feedback = cleanFeedback;\n existing.element = cleanElement;\n if (elements !== undefined) {\n existing.elements = elements;\n existing.element = elements[0] ?? null;\n } else if (existing.elements) existing.element = existing.elements[0] ?? null;\n if (variant !== undefined) existing.variant = variant;\n if (variantAction) existing.variantAction = variantAction;\n // An omitted payload leaves the stored one alone, so a host that only\n // sends it on the first save does not lose it on the next autosave.\n if (payload !== undefined) existing.payload = payload;\n if (defaultsRequest && !existing.done) {\n existing.createdAt = new Date().toISOString();\n if (cleanViewport) existing.viewport = cleanViewport;\n writeNotes(file, notes.filter(n => n.id === existing.id || n.done || n.route !== route || !n.feedback.startsWith('[set-defaults]')));\n } else writeNotes(file, notes);\n return json(200, { id: existing.id });\n }\n }\n if (typeof id === 'string' && notes.some((note) => note.id === id)) {\n return json(409, { error: 'This note belongs to a different route' });\n }\n const newId = typeof id === 'string' ? id : randomUUID();\n const createdAt = typeof body.createdAt === 'string' && Number.isFinite(Date.parse(body.createdAt))\n ? new Date(body.createdAt).toISOString() : new Date().toISOString();\n notes.push({\n id: newId,\n route,\n feedback: cleanFeedback,\n element: cleanElement,\n done: false,\n doneAt: null,\n createdAt,\n ...(cleanViewport ? { viewport: cleanViewport } : {}),\n ...(payload !== undefined ? { payload } : {}),\n ...(elements !== undefined ? { elements, element: elements[0] ?? null } : {}),\n ...(variant !== undefined ? { variant } : {}),\n ...(variantAction ? { variantAction } : {}),\n });\n writeNotes(file, notes);\n return json(201, { id: newId });\n }\n\n return json(405, { error: 'method not allowed' });\n}\n\nfunction cap(v: string, max: number): string {\n return v.slice(0, max);\n}\n\nfunction elementOf(value: unknown): IterationElement | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null;\n const v = value as Partial<IterationElement>;\n if (typeof v.selector !== 'string' || !v.selector) return null;\n return { tag: cap(String(v.tag ?? ''), 30), text: cap(String(v.text ?? ''), 120), selector: cap(v.selector, 300) };\n}\n\nfunction variantOf(value: unknown): VariantReference | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null;\n const v = value as Partial<VariantReference>;\n if (typeof v.axisId !== 'string' || typeof v.label !== 'string') return null;\n return { axisId: cap(v.axisId, 200), label: cap(v.label, 200),\n ...(typeof v.optionId === 'string' ? { optionId: cap(v.optionId, 200) } : {}),\n ...(typeof v.optionLabel === 'string' ? { optionLabel: cap(v.optionLabel, 200) } : {}),\n ...(typeof v.value === 'string' ? { value: cap(v.value, 200) } : {}) };\n}\n\n/** A sane `{ w, h }` from an untrusted viewport value, else null. */\nfunction viewportOf(v: unknown): { w: number; h: number } | null {\n if (!v || typeof v !== 'object' || Array.isArray(v)) return null;\n const w = Math.round(Number((v as { w?: unknown }).w));\n const h = Math.round(Number((v as { h?: unknown }).h));\n if (!Number.isFinite(w) || !Number.isFinite(h)) return null;\n if (w <= 0 || h <= 0 || w > 100_000 || h > 100_000) return null;\n return { w, h };\n}\n\n/** Whether an opaque payload is serializable and inside {@link PAYLOAD_MAX}. */\nfunction withinPayloadCap(payload: unknown): boolean {\n try {\n return Buffer.byteLength(JSON.stringify(payload), 'utf8') <= PAYLOAD_MAX;\n } catch {\n return false; // circular or otherwise unserializable\n }\n}\n\n/** The endpoint path the client calls. */\nexport const NOTES_ENDPOINT = '/api/iteration/notes';\n\nfunction removalOf(value: unknown): VariantRemoval | undefined {\n if (!value || typeof value !== 'object') return;\n const v = value as Partial<VariantRemoval>;\n if (typeof v.axisId !== 'string' || !v.axisId || v.axisId.length > 200 || !['remove-option', 'remove-axis', 'keep-default'].includes(v.action ?? '')) return;\n if (v.action === 'remove-option' && (typeof v.optionId !== 'string' || !v.optionId || v.optionId.length > 200)) return;\n return { axisId: v.axisId, action: v.action as VariantRemoval['action'], ...(typeof v.optionId === 'string' ? { optionId: v.optionId.slice(0, 200) } : {}) };\n}\n","import { randomUUID } from 'node:crypto';\nimport { closeSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\n/** Readers see either the old document or the complete replacement. */\nexport function writeJson(file: string, value: unknown): void {\n mkdirSync(dirname(file), { recursive: true });\n const temporary = `${file}.${randomUUID()}.tmp`;\n try {\n writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\\n`, { flag: 'wx', mode: 0o600 });\n renameSync(temporary, file);\n } finally {\n rmSync(temporary, { force: true });\n }\n}\n\n/** Serialize read-modify-write across the CLI and the dev server. A dead\n * process's lock is recoverable; a busy store fails without discarding data. */\nexport function withFileLock<T>(file: string, work: () => T): T {\n mkdirSync(dirname(file), { recursive: true });\n const lock = `${file}.lock`;\n const deadline = Date.now() + 2_000;\n while (true) {\n try {\n const fd = openSync(lock, 'wx', 0o600);\n try { writeFileSync(fd, String(process.pid)); } finally { closeSync(fd); }\n break;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n try {\n const owner = Number(readFileSync(lock, 'utf8'));\n if (Number.isSafeInteger(owner) && owner > 0) {\n try { process.kill(owner, 0); } catch (cause) {\n if ((cause as NodeJS.ErrnoException).code === 'ESRCH') { rmSync(lock, { force: true }); continue; }\n }\n }\n } catch { /* The other writer may have just released its lock. */ }\n if (Date.now() >= deadline) throw new Error(`Local store is busy: ${file}. Retry after the other writer finishes.`);\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);\n }\n }\n try { return work(); } finally { rmSync(lock, { force: true }); }\n}\n"],"mappings":"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","names":["randomUUID","mkdirSync","readFileSync","writeFileSync","dirname","readFileSync","dirname","mkdirSync","writeFileSync","randomUUID"]}
|
|
@@ -7,7 +7,7 @@ import { randomUUID } from "crypto";
|
|
|
7
7
|
|
|
8
8
|
// package.json
|
|
9
9
|
var name = "isotone";
|
|
10
|
-
var version = "0.4.
|
|
10
|
+
var version = "0.4.1";
|
|
11
11
|
|
|
12
12
|
// src/update.ts
|
|
13
13
|
var UPDATE_ENDPOINT = "/api/iteration/update";
|
|
@@ -224,9 +224,8 @@ function startUpdateCheck(cwd, enabled = true, options = {}) {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
export {
|
|
227
|
-
version,
|
|
228
227
|
UPDATE_ENDPOINT,
|
|
229
228
|
checkUpdate,
|
|
230
229
|
startUpdateCheck
|
|
231
230
|
};
|
|
232
|
-
//# sourceMappingURL=chunk-
|
|
231
|
+
//# sourceMappingURL=chunk-UMVPMPVE.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/update.ts","../package.json"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, open, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport type { UpdateInfo } from './core/update';\nimport { name as packageName, version as currentVersion } from '../package.json';\n\nexport const UPDATE_ENDPOINT = '/api/iteration/update';\nconst REGISTRY_URL = `https://registry.npmjs.org/${packageName}/latest`;\nconst CHECK_TIMEOUT = 5_000;\nconst CACHE_TTL = 24 * 60 * 60 * 1000;\nconst CACHE_FILE = join(tmpdir(), `isotone-update-${process.getuid?.() ?? 'user'}-v1.json`);\n\ntype Manager = 'npm' | 'pnpm' | 'yarn';\nexport type { UpdateInfo } from './core/update';\n\ninterface Version { parts: bigint[]; prerelease: string[] }\nfunction parseVersion(value: string): Version {\n if (value.length > 256) throw new Error('Invalid version.');\n const match = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-([\\da-zA-Z-]+(?:\\.[\\da-zA-Z-]+)*))?(?:\\+([\\da-zA-Z-]+(?:\\.[\\da-zA-Z-]+)*))?$/.exec(value);\n if (!match) throw new Error('Invalid version.');\n const prerelease = match[4]?.split('.') ?? [];\n if (prerelease.some((part) => /^0\\d+$/.test(part))) throw new Error('Invalid version.');\n return { parts: match.slice(1, 4).map(BigInt), prerelease };\n}\n\n/** SemVer precedence, including numeric prereleases and ignored build metadata. */\nexport function compareVersions(a: string, b: string): number {\n const left = parseVersion(a), right = parseVersion(b);\n for (let i = 0; i < 3; i++) {\n if (left.parts[i] !== right.parts[i]) return left.parts[i] > right.parts[i] ? 1 : -1;\n }\n if (!left.prerelease.length || !right.prerelease.length) {\n return left.prerelease.length === right.prerelease.length ? 0 : left.prerelease.length ? -1 : 1;\n }\n for (let i = 0; i < Math.max(left.prerelease.length, right.prerelease.length); i++) {\n const x = left.prerelease[i], y = right.prerelease[i];\n if (x === y) continue;\n if (x === undefined || y === undefined) return x === undefined ? -1 : 1;\n const nx = /^\\d+$/.test(x), ny = /^\\d+$/.test(y);\n if (nx && ny) return BigInt(x) > BigInt(y) ? 1 : -1;\n if (nx !== ny) return nx ? -1 : 1;\n return x > y ? 1 : -1;\n }\n return 0;\n}\n\nfunction manifest(directory: string): Record<string, unknown> | null {\n try {\n const value = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));\n return value && typeof value === 'object' && !Array.isArray(value) ? value : null;\n } catch { return null; }\n}\n\nfunction findProject(cwd: string): { directory: string; json: Record<string, unknown> | null } {\n let directory = resolve(cwd);\n while (true) {\n const json = manifest(directory);\n if (json) return { directory, json };\n if (dirname(directory) === directory || existsSync(join(directory, '.git'))) return { directory: resolve(cwd), json: null };\n directory = dirname(directory);\n }\n}\n\nfunction dependencyOwner(project: ReturnType<typeof findProject>): ReturnType<typeof findProject> {\n let directory = project.directory;\n while (true) {\n const json = manifest(directory);\n if (['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'].some((key) => {\n const dependencies = json?.[key];\n return dependencies && typeof dependencies === 'object' && Object.hasOwn(dependencies, packageName);\n })) return { directory, json };\n if (dirname(directory) === directory || existsSync(join(directory, '.git'))) return project;\n directory = dirname(directory);\n }\n}\n\nexport function upgradePlan(cwd: string, latest: string, override?: Manager): Pick<UpdateInfo, 'upgrade' | 'message'> {\n parseVersion(latest); // Only a validated version can enter a copied command.\n const project = dependencyOwner(findProject(cwd));\n if (project.json?.name === packageName) return { upgrade: null, message: 'This is the source checkout; update its source instead of adding itself as a dependency.' };\n let manager = override;\n let directory = project.directory;\n const locks = new Set<Manager>();\n while (!manager) {\n const declared = manifest(directory)?.packageManager;\n if (typeof declared === 'string') {\n const match = /^(npm|pnpm|yarn)@/.exec(declared);\n if (!match) return { upgrade: null, message: 'The declared package manager is not supported by this command. Upgrade with your project’s package manager.' };\n manager = match[1] as Manager;\n break;\n }\n if (['bun.lock', 'bun.lockb'].some((file) => existsSync(join(directory, file)))) return { upgrade: null, message: 'This project uses Bun. Upgrade with the project’s package manager.' };\n if (existsSync(join(directory, 'pnpm-lock.yaml'))) locks.add('pnpm');\n if (existsSync(join(directory, 'yarn.lock'))) locks.add('yarn');\n if (['package-lock.json', 'npm-shrinkwrap.json'].some((file) => existsSync(join(directory, file)))) locks.add('npm');\n if (locks.size || dirname(directory) === directory || existsSync(join(directory, '.git'))) break;\n directory = dirname(directory);\n }\n if (!manager && locks.size > 1) return { upgrade: null, message: 'Multiple package-manager lockfiles found. Pass --manager npm, pnpm or yarn.' };\n manager ??= [...locks][0] ?? 'npm';\n const production = project.json?.dependencies as Record<string, unknown> | undefined;\n const optional = project.json?.optionalDependencies as Record<string, unknown> | undefined;\n const peers = project.json?.peerDependencies as Record<string, unknown> | undefined;\n if (optional?.[packageName] || peers?.[packageName]) return { upgrade: null, message: 'Review this optional/peer dependency manually with your package manager.' };\n const dev = production?.[packageName] ? '' : ' -D';\n let workspace = manager === 'pnpm' && existsSync(join(project.directory, 'pnpm-workspace.yaml')) ? ' -w' : '';\n if (manager === 'yarn' && project.json?.workspaces) {\n const declared = project.json.packageManager;\n if (typeof declared !== 'string' || !/^yarn@\\d+\\./.test(declared)) return { upgrade: null, message: 'Declare the Yarn version in packageManager before updating a workspace-root dependency; Yarn Classic and modern Yarn use different root flags.' };\n if (declared.startsWith('yarn@1.')) workspace = ' -W';\n }\n const command = manager === 'npm'\n ? `npm install${dev} --save-exact ${packageName}@${latest}`\n : `${manager} add${dev}${workspace} --exact ${packageName}@${latest}`;\n return { upgrade: { manager, command, cwd: project.directory } };\n}\n\nexport async function fetchLatest(fetcher: typeof fetch = fetch, signal?: AbortSignal): Promise<string> {\n const controller = new AbortController();\n const abort = () => controller.abort();\n if (signal?.aborted) abort();\n else signal?.addEventListener('abort', abort, { once: true });\n const timer = setTimeout(abort, CHECK_TIMEOUT);\n try {\n if (controller.signal.aborted) throw new Error('Update check cancelled.');\n const response = await fetcher(REGISTRY_URL, { signal: controller.signal, redirect: 'error', headers: { accept: 'application/json' } });\n if (!response.ok) throw new Error(`Registry returned HTTP ${response.status}.`);\n if (!response.body) throw new Error('Registry returned an empty response.');\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let bytes = 0;\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n bytes += value.byteLength;\n if (bytes > 64 * 1024) {\n await reader.cancel();\n throw new Error('Registry response was too large.');\n }\n chunks.push(value);\n }\n } finally { reader.releaseLock(); }\n const data = JSON.parse(Buffer.concat(chunks).toString('utf8'));\n if (typeof data.version !== 'string') throw new Error('Registry returned no version.');\n parseVersion(data.version);\n return data.version;\n } finally { clearTimeout(timer); signal?.removeEventListener('abort', abort); }\n}\n\nexport function updateInfo(latest: string, cwd: string, manager?: Manager): UpdateInfo {\n const order = compareVersions(latest, currentVersion);\n return {\n currentVersion, latestVersion: latest,\n status: order > 0 ? 'available' : order < 0 ? 'ahead' : 'current',\n ...(order > 0 ? upgradePlan(cwd, latest, manager) : { upgrade: null }),\n };\n}\n\nexport async function checkUpdate(cwd: string, manager?: Manager, fetcher?: typeof fetch, signal?: AbortSignal): Promise<UpdateInfo> {\n try { return updateInfo(await fetchLatest(fetcher, signal), cwd, manager); }\n catch { return { currentVersion, latestVersion: null, status: 'unavailable', upgrade: null, message: 'Could not check npm for updates. The installed version remains usable; try again when online.' }; }\n}\n\ninterface CachedCheck { checkedAt: number; latestVersion: string | null }\nasync function readCache(file: string): Promise<CachedCheck | null> {\n try {\n if ((await stat(file)).size > 4096) return null;\n const data = JSON.parse(await readFile(file, 'utf8'));\n if (!Number.isFinite(data.checkedAt) || data.checkedAt > Date.now() || Date.now() - data.checkedAt >= CACHE_TTL) return null;\n if (data.latestVersion !== null) parseVersion(data.latestVersion);\n return data;\n } catch { return null; }\n}\n\nasync function saveCache(file: string, latestVersion: string | null): Promise<void> {\n const temporary = `${file}.${randomUUID()}.tmp`;\n try {\n await writeFile(temporary, JSON.stringify({ checkedAt: Date.now(), latestVersion }), { mode: 0o600, flag: 'wx' });\n await rename(temporary, file);\n } finally { await rm(temporary, { force: true }); }\n}\n\n/** One bounded request a day, including failed attempts, shared across dev servers. */\nexport function startUpdateCheck(cwd: string, enabled = true, options: { cacheFile?: string; fetcher?: typeof fetch; env?: NodeJS.ProcessEnv } = {}): { getSnapshot(): UpdateInfo; ready: Promise<void>; dispose(): void } {\n const controller = new AbortController();\n const env = options.env ?? process.env;\n const off = !enabled || env.ISOTONE_UPDATE_CHECK === '0' || (!!env.CI && env.CI !== 'false' && env.CI !== '0') || env.NODE_ENV === 'test' || !!env.VITEST;\n let info: UpdateInfo = { currentVersion, latestVersion: null, status: off ? 'disabled' : 'checking', upgrade: null };\n const ready = off ? Promise.resolve() : (async () => {\n const file = options.cacheFile ?? CACHE_FILE;\n let locked = false;\n try {\n const cache = await readCache(file);\n if (cache) { info = cache.latestVersion ? updateInfo(cache.latestVersion, cwd) : { ...info, status: 'unavailable' }; return; }\n await mkdir(dirname(file), { recursive: true });\n try { await (await open(`${file}.lock`, 'wx', 0o600)).close(); locked = true; }\n catch {\n // A concurrent server owns the request. Recover an abandoned lease next time.\n if (Date.now() - (await stat(`${file}.lock`)).mtimeMs > CHECK_TIMEOUT * 3) await rm(`${file}.lock`, { force: true });\n info = { ...info, status: 'unavailable' };\n return;\n }\n const refreshed = await readCache(file);\n if (refreshed) { info = refreshed.latestVersion ? updateInfo(refreshed.latestVersion, cwd) : { ...info, status: 'unavailable' }; return; }\n await saveCache(file, null);\n info = await checkUpdate(cwd, undefined, options.fetcher, controller.signal);\n if (info.latestVersion) await saveCache(file, info.latestVersion);\n } catch { info = { currentVersion, latestVersion: null, status: 'unavailable', upgrade: null }; }\n finally { if (locked) await rm(`${file}.lock`, { force: true }).catch(() => {}); }\n })();\n return { getSnapshot: () => info, ready, dispose: () => controller.abort() };\n}\n","{\n \"name\": \"isotone\",\n \"version\": \"0.4.0\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"description\": \"Local feedback and design variants for your running web app. Pin elements, leave notes, and give your coding agent the context to make the change.\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"exports\": {\n \"./react\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./core\": {\n \"types\": \"./dist/core.d.ts\",\n \"import\": \"./dist/core.js\"\n },\n \"./server\": {\n \"types\": \"./dist/server.d.ts\",\n \"import\": \"./dist/server.js\"\n },\n \"./serve\": {\n \"types\": \"./dist/serve.d.ts\",\n \"import\": \"./dist/serve.js\"\n },\n \"./vite\": {\n \"types\": \"./dist/vite-plugin.d.ts\",\n \"import\": \"./dist/vite-plugin.js\"\n },\n \"./cli\": {\n \"types\": \"./dist/cli.d.ts\",\n \"import\": \"./dist/cli.js\"\n }\n },\n \"files\": [\n \"dist\",\n \"docs\",\n \"AGENTS.md\",\n \"CHANGELOG.md\"\n ],\n \"bin\": {\n \"isotone\": \"./dist/cli.js\"\n },\n \"scripts\": {\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"biome lint .\",\n \"lint:fix\": \"biome lint --write .\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"build\": \"tsup\",\n \"check\": \"pnpm typecheck && pnpm lint && pnpm build && pnpm test\",\n \"prepublishOnly\": \"pnpm check\",\n \"dev\": \"vite --config examples/vite.config.ts\",\n \"check:package\": \"node scripts/check-package.mjs\"\n },\n \"peerDependencies\": {\n \"react\": \"^18 || ^19\",\n \"react-dom\": \"^18 || ^19\",\n \"vite\": \"^5 || ^6 || ^7 || ^8\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"2.5.7\",\n \"@types/node\": \"24.13.3\",\n \"@types/react\": \"^19\",\n \"@types/react-dom\": \"^19\",\n \"jsdom\": \"^30.0.1\",\n \"react\": \"^19\",\n \"react-dom\": \"^19\",\n \"tsup\": \"^8\",\n \"typescript\": \"^5\",\n \"vite\": \"^7\",\n \"vitest\": \"^4.1.10\",\n \"lucide-react\": \"^1.55.0\"\n },\n \"keywords\": [\n \"feedback\",\n \"review\",\n \"dev-tool\",\n \"vite-plugin\",\n \"coding-agent\",\n \"annotations\",\n \"design-review\",\n \"variants\",\n \"iteration\"\n ],\n \"sideEffects\": false,\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/aaroi/isotone.git\"\n },\n \"homepage\": \"https://github.com/aaroi/isotone#readme\",\n \"bugs\": \"https://github.com/aaroi/isotone/issues\",\n \"peerDependenciesMeta\": {\n \"react\": {\n \"optional\": true\n },\n \"react-dom\": {\n \"optional\": true\n },\n \"vite\": {\n \"optional\": true\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/update.ts","../package.json"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, open, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport type { UpdateInfo } from './core/update';\nimport { name as packageName, version as currentVersion } from '../package.json';\n\nexport const UPDATE_ENDPOINT = '/api/iteration/update';\nconst REGISTRY_URL = `https://registry.npmjs.org/${packageName}/latest`;\nconst CHECK_TIMEOUT = 5_000;\nconst CACHE_TTL = 24 * 60 * 60 * 1000;\nconst CACHE_FILE = join(tmpdir(), `isotone-update-${process.getuid?.() ?? 'user'}-v1.json`);\n\ntype Manager = 'npm' | 'pnpm' | 'yarn';\nexport type { UpdateInfo } from './core/update';\n\ninterface Version { parts: bigint[]; prerelease: string[] }\nfunction parseVersion(value: string): Version {\n if (value.length > 256) throw new Error('Invalid version.');\n const match = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-([\\da-zA-Z-]+(?:\\.[\\da-zA-Z-]+)*))?(?:\\+([\\da-zA-Z-]+(?:\\.[\\da-zA-Z-]+)*))?$/.exec(value);\n if (!match) throw new Error('Invalid version.');\n const prerelease = match[4]?.split('.') ?? [];\n if (prerelease.some((part) => /^0\\d+$/.test(part))) throw new Error('Invalid version.');\n return { parts: match.slice(1, 4).map(BigInt), prerelease };\n}\n\n/** SemVer precedence, including numeric prereleases and ignored build metadata. */\nexport function compareVersions(a: string, b: string): number {\n const left = parseVersion(a), right = parseVersion(b);\n for (let i = 0; i < 3; i++) {\n if (left.parts[i] !== right.parts[i]) return left.parts[i] > right.parts[i] ? 1 : -1;\n }\n if (!left.prerelease.length || !right.prerelease.length) {\n return left.prerelease.length === right.prerelease.length ? 0 : left.prerelease.length ? -1 : 1;\n }\n for (let i = 0; i < Math.max(left.prerelease.length, right.prerelease.length); i++) {\n const x = left.prerelease[i], y = right.prerelease[i];\n if (x === y) continue;\n if (x === undefined || y === undefined) return x === undefined ? -1 : 1;\n const nx = /^\\d+$/.test(x), ny = /^\\d+$/.test(y);\n if (nx && ny) return BigInt(x) > BigInt(y) ? 1 : -1;\n if (nx !== ny) return nx ? -1 : 1;\n return x > y ? 1 : -1;\n }\n return 0;\n}\n\nfunction manifest(directory: string): Record<string, unknown> | null {\n try {\n const value = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));\n return value && typeof value === 'object' && !Array.isArray(value) ? value : null;\n } catch { return null; }\n}\n\nfunction findProject(cwd: string): { directory: string; json: Record<string, unknown> | null } {\n let directory = resolve(cwd);\n while (true) {\n const json = manifest(directory);\n if (json) return { directory, json };\n if (dirname(directory) === directory || existsSync(join(directory, '.git'))) return { directory: resolve(cwd), json: null };\n directory = dirname(directory);\n }\n}\n\nfunction dependencyOwner(project: ReturnType<typeof findProject>): ReturnType<typeof findProject> {\n let directory = project.directory;\n while (true) {\n const json = manifest(directory);\n if (['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'].some((key) => {\n const dependencies = json?.[key];\n return dependencies && typeof dependencies === 'object' && Object.hasOwn(dependencies, packageName);\n })) return { directory, json };\n if (dirname(directory) === directory || existsSync(join(directory, '.git'))) return project;\n directory = dirname(directory);\n }\n}\n\nexport function upgradePlan(cwd: string, latest: string, override?: Manager): Pick<UpdateInfo, 'upgrade' | 'message'> {\n parseVersion(latest); // Only a validated version can enter a copied command.\n const project = dependencyOwner(findProject(cwd));\n if (project.json?.name === packageName) return { upgrade: null, message: 'This is the source checkout; update its source instead of adding itself as a dependency.' };\n let manager = override;\n let directory = project.directory;\n const locks = new Set<Manager>();\n while (!manager) {\n const declared = manifest(directory)?.packageManager;\n if (typeof declared === 'string') {\n const match = /^(npm|pnpm|yarn)@/.exec(declared);\n if (!match) return { upgrade: null, message: 'The declared package manager is not supported by this command. Upgrade with your project’s package manager.' };\n manager = match[1] as Manager;\n break;\n }\n if (['bun.lock', 'bun.lockb'].some((file) => existsSync(join(directory, file)))) return { upgrade: null, message: 'This project uses Bun. Upgrade with the project’s package manager.' };\n if (existsSync(join(directory, 'pnpm-lock.yaml'))) locks.add('pnpm');\n if (existsSync(join(directory, 'yarn.lock'))) locks.add('yarn');\n if (['package-lock.json', 'npm-shrinkwrap.json'].some((file) => existsSync(join(directory, file)))) locks.add('npm');\n if (locks.size || dirname(directory) === directory || existsSync(join(directory, '.git'))) break;\n directory = dirname(directory);\n }\n if (!manager && locks.size > 1) return { upgrade: null, message: 'Multiple package-manager lockfiles found. Pass --manager npm, pnpm or yarn.' };\n manager ??= [...locks][0] ?? 'npm';\n const production = project.json?.dependencies as Record<string, unknown> | undefined;\n const optional = project.json?.optionalDependencies as Record<string, unknown> | undefined;\n const peers = project.json?.peerDependencies as Record<string, unknown> | undefined;\n if (optional?.[packageName] || peers?.[packageName]) return { upgrade: null, message: 'Review this optional/peer dependency manually with your package manager.' };\n const dev = production?.[packageName] ? '' : ' -D';\n let workspace = manager === 'pnpm' && existsSync(join(project.directory, 'pnpm-workspace.yaml')) ? ' -w' : '';\n if (manager === 'yarn' && project.json?.workspaces) {\n const declared = project.json.packageManager;\n if (typeof declared !== 'string' || !/^yarn@\\d+\\./.test(declared)) return { upgrade: null, message: 'Declare the Yarn version in packageManager before updating a workspace-root dependency; Yarn Classic and modern Yarn use different root flags.' };\n if (declared.startsWith('yarn@1.')) workspace = ' -W';\n }\n const command = manager === 'npm'\n ? `npm install${dev} --save-exact ${packageName}@${latest}`\n : `${manager} add${dev}${workspace} --exact ${packageName}@${latest}`;\n return { upgrade: { manager, command, cwd: project.directory } };\n}\n\nexport async function fetchLatest(fetcher: typeof fetch = fetch, signal?: AbortSignal): Promise<string> {\n const controller = new AbortController();\n const abort = () => controller.abort();\n if (signal?.aborted) abort();\n else signal?.addEventListener('abort', abort, { once: true });\n const timer = setTimeout(abort, CHECK_TIMEOUT);\n try {\n if (controller.signal.aborted) throw new Error('Update check cancelled.');\n const response = await fetcher(REGISTRY_URL, { signal: controller.signal, redirect: 'error', headers: { accept: 'application/json' } });\n if (!response.ok) throw new Error(`Registry returned HTTP ${response.status}.`);\n if (!response.body) throw new Error('Registry returned an empty response.');\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let bytes = 0;\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n bytes += value.byteLength;\n if (bytes > 64 * 1024) {\n await reader.cancel();\n throw new Error('Registry response was too large.');\n }\n chunks.push(value);\n }\n } finally { reader.releaseLock(); }\n const data = JSON.parse(Buffer.concat(chunks).toString('utf8'));\n if (typeof data.version !== 'string') throw new Error('Registry returned no version.');\n parseVersion(data.version);\n return data.version;\n } finally { clearTimeout(timer); signal?.removeEventListener('abort', abort); }\n}\n\nexport function updateInfo(latest: string, cwd: string, manager?: Manager): UpdateInfo {\n const order = compareVersions(latest, currentVersion);\n return {\n currentVersion, latestVersion: latest,\n status: order > 0 ? 'available' : order < 0 ? 'ahead' : 'current',\n ...(order > 0 ? upgradePlan(cwd, latest, manager) : { upgrade: null }),\n };\n}\n\nexport async function checkUpdate(cwd: string, manager?: Manager, fetcher?: typeof fetch, signal?: AbortSignal): Promise<UpdateInfo> {\n try { return updateInfo(await fetchLatest(fetcher, signal), cwd, manager); }\n catch { return { currentVersion, latestVersion: null, status: 'unavailable', upgrade: null, message: 'Could not check npm for updates. The installed version remains usable; try again when online.' }; }\n}\n\ninterface CachedCheck { checkedAt: number; latestVersion: string | null }\nasync function readCache(file: string): Promise<CachedCheck | null> {\n try {\n if ((await stat(file)).size > 4096) return null;\n const data = JSON.parse(await readFile(file, 'utf8'));\n if (!Number.isFinite(data.checkedAt) || data.checkedAt > Date.now() || Date.now() - data.checkedAt >= CACHE_TTL) return null;\n if (data.latestVersion !== null) parseVersion(data.latestVersion);\n return data;\n } catch { return null; }\n}\n\nasync function saveCache(file: string, latestVersion: string | null): Promise<void> {\n const temporary = `${file}.${randomUUID()}.tmp`;\n try {\n await writeFile(temporary, JSON.stringify({ checkedAt: Date.now(), latestVersion }), { mode: 0o600, flag: 'wx' });\n await rename(temporary, file);\n } finally { await rm(temporary, { force: true }); }\n}\n\n/** One bounded request a day, including failed attempts, shared across dev servers. */\nexport function startUpdateCheck(cwd: string, enabled = true, options: { cacheFile?: string; fetcher?: typeof fetch; env?: NodeJS.ProcessEnv } = {}): { getSnapshot(): UpdateInfo; ready: Promise<void>; dispose(): void } {\n const controller = new AbortController();\n const env = options.env ?? process.env;\n const off = !enabled || env.ISOTONE_UPDATE_CHECK === '0' || (!!env.CI && env.CI !== 'false' && env.CI !== '0') || env.NODE_ENV === 'test' || !!env.VITEST;\n let info: UpdateInfo = { currentVersion, latestVersion: null, status: off ? 'disabled' : 'checking', upgrade: null };\n const ready = off ? Promise.resolve() : (async () => {\n const file = options.cacheFile ?? CACHE_FILE;\n let locked = false;\n try {\n const cache = await readCache(file);\n if (cache) { info = cache.latestVersion ? updateInfo(cache.latestVersion, cwd) : { ...info, status: 'unavailable' }; return; }\n await mkdir(dirname(file), { recursive: true });\n try { await (await open(`${file}.lock`, 'wx', 0o600)).close(); locked = true; }\n catch {\n // A concurrent server owns the request. Recover an abandoned lease next time.\n if (Date.now() - (await stat(`${file}.lock`)).mtimeMs > CHECK_TIMEOUT * 3) await rm(`${file}.lock`, { force: true });\n info = { ...info, status: 'unavailable' };\n return;\n }\n const refreshed = await readCache(file);\n if (refreshed) { info = refreshed.latestVersion ? updateInfo(refreshed.latestVersion, cwd) : { ...info, status: 'unavailable' }; return; }\n await saveCache(file, null);\n info = await checkUpdate(cwd, undefined, options.fetcher, controller.signal);\n if (info.latestVersion) await saveCache(file, info.latestVersion);\n } catch { info = { currentVersion, latestVersion: null, status: 'unavailable', upgrade: null }; }\n finally { if (locked) await rm(`${file}.lock`, { force: true }).catch(() => {}); }\n })();\n return { getSnapshot: () => info, ready, dispose: () => controller.abort() };\n}\n","{\n \"name\": \"isotone\",\n \"version\": \"0.4.1\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"description\": \"Local feedback and design variants for your running web app. Pin elements, leave notes, and give your coding agent the context to make the change.\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"exports\": {\n \"./react\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./core\": {\n \"types\": \"./dist/core.d.ts\",\n \"import\": \"./dist/core.js\"\n },\n \"./server\": {\n \"types\": \"./dist/server.d.ts\",\n \"import\": \"./dist/server.js\"\n },\n \"./serve\": {\n \"types\": \"./dist/serve.d.ts\",\n \"import\": \"./dist/serve.js\"\n },\n \"./vite\": {\n \"types\": \"./dist/vite-plugin.d.ts\",\n \"import\": \"./dist/vite-plugin.js\"\n },\n \"./cli\": {\n \"types\": \"./dist/cli.d.ts\",\n \"import\": \"./dist/cli.js\"\n }\n },\n \"files\": [\n \"dist\",\n \"docs\",\n \"AGENTS.md\",\n \"CHANGELOG.md\"\n ],\n \"bin\": {\n \"isotone\": \"./dist/cli.js\"\n },\n \"scripts\": {\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"biome lint .\",\n \"lint:fix\": \"biome lint --write .\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"build\": \"tsup\",\n \"check\": \"pnpm typecheck && pnpm lint && pnpm build && pnpm test\",\n \"prepublishOnly\": \"pnpm check\",\n \"dev\": \"vite --config examples/vite.config.ts\",\n \"check:package\": \"node scripts/check-package.mjs\"\n },\n \"peerDependencies\": {\n \"react\": \"^18 || ^19\",\n \"react-dom\": \"^18 || ^19\",\n \"vite\": \"^5 || ^6 || ^7 || ^8\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"2.5.7\",\n \"@types/node\": \"24.13.3\",\n \"@types/react\": \"^19\",\n \"@types/react-dom\": \"^19\",\n \"jsdom\": \"^30.0.1\",\n \"react\": \"^19\",\n \"react-dom\": \"^19\",\n \"tsup\": \"^8\",\n \"typescript\": \"^5\",\n \"vite\": \"^7\",\n \"vitest\": \"^4.1.10\",\n \"lucide-react\": \"^1.55.0\"\n },\n \"keywords\": [\n \"feedback\",\n \"review\",\n \"dev-tool\",\n \"vite-plugin\",\n \"coding-agent\",\n \"annotations\",\n \"design-review\",\n \"variants\",\n \"iteration\"\n ],\n \"sideEffects\": false,\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/aaroi/isotone.git\"\n },\n \"homepage\": \"https://github.com/aaroi/isotone#readme\",\n \"bugs\": \"https://github.com/aaroi/isotone/issues\",\n \"peerDependenciesMeta\": {\n \"react\": {\n \"optional\": true\n },\n \"react-dom\": {\n \"optional\": true\n },\n \"vite\": {\n \"optional\": true\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -4,6 +4,25 @@ import {
|
|
|
4
4
|
request,
|
|
5
5
|
writeBrowserStorage
|
|
6
6
|
} from "./chunk-SNGBNVH2.js";
|
|
7
|
+
import {
|
|
8
|
+
MAX_FEEDBACK_LENGTH
|
|
9
|
+
} from "./chunk-A4DX2V4C.js";
|
|
10
|
+
|
|
11
|
+
// src/core/id.ts
|
|
12
|
+
var fallbackCounter = 0;
|
|
13
|
+
function createNoteId() {
|
|
14
|
+
const crypto = globalThis.crypto;
|
|
15
|
+
if (typeof crypto?.randomUUID === "function") return crypto.randomUUID();
|
|
16
|
+
if (typeof crypto?.getRandomValues === "function") {
|
|
17
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
18
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
19
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
20
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
21
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
22
|
+
}
|
|
23
|
+
fallbackCounter += 1;
|
|
24
|
+
return `${Date.now().toString(36)}-${fallbackCounter.toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
25
|
+
}
|
|
7
26
|
|
|
8
27
|
// src/core/index.ts
|
|
9
28
|
function describeIterationElement(el, maxDepth = 4) {
|
|
@@ -125,6 +144,8 @@ function createIterationStore(cfg = {}) {
|
|
|
125
144
|
const backlog = localAll();
|
|
126
145
|
if (backlog.length === 0) return;
|
|
127
146
|
for (const item of backlog) {
|
|
147
|
+
if (item.feedback.trim().length > MAX_FEEDBACK_LENGTH) continue;
|
|
148
|
+
let rejected = false;
|
|
128
149
|
const saved = await request(endpoint, {
|
|
129
150
|
method: "POST",
|
|
130
151
|
headers: { "Content-Type": "application/json" },
|
|
@@ -140,8 +161,17 @@ function createIterationStore(cfg = {}) {
|
|
|
140
161
|
...item.payload !== void 0 ? { payload: item.payload } : {},
|
|
141
162
|
...item.viewport ? { viewport: item.viewport } : {}
|
|
142
163
|
})
|
|
143
|
-
}, async (res) =>
|
|
144
|
-
|
|
164
|
+
}, async (res) => {
|
|
165
|
+
if (!res.ok) {
|
|
166
|
+
rejected = res.status >= 400 && res.status < 500;
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return await res.json();
|
|
170
|
+
});
|
|
171
|
+
if (!saved || typeof saved.id !== "string" || !saved.id) {
|
|
172
|
+
if (rejected) continue;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
145
175
|
acknowledge(item);
|
|
146
176
|
}
|
|
147
177
|
}
|
|
@@ -204,7 +234,7 @@ function createIterationStore(cfg = {}) {
|
|
|
204
234
|
if (existing.elements) existing.element = existing.elements[0] ?? null;
|
|
205
235
|
savedId = existing.id;
|
|
206
236
|
} else {
|
|
207
|
-
savedId = id ??
|
|
237
|
+
savedId = id ?? createNoteId();
|
|
208
238
|
all.push({
|
|
209
239
|
id: savedId,
|
|
210
240
|
route,
|
|
@@ -222,12 +252,15 @@ function createIterationStore(cfg = {}) {
|
|
|
222
252
|
return savedId;
|
|
223
253
|
}
|
|
224
254
|
async function saveNote(route, feedback, element, id, payload, viewport, context) {
|
|
255
|
+
if (feedback.trim().length > MAX_FEEDBACK_LENGTH) {
|
|
256
|
+
throw new RangeError(`Feedback must be at most ${MAX_FEEDBACK_LENGTH} characters.`);
|
|
257
|
+
}
|
|
225
258
|
if (!endpoint) {
|
|
226
259
|
const savedId2 = saveLocal(route, feedback, element, id, payload, viewport, context);
|
|
227
260
|
lastSave = "local";
|
|
228
261
|
return savedId2;
|
|
229
262
|
}
|
|
230
|
-
const savedId = id ?? (feedback.startsWith("[set-defaults]") ? null :
|
|
263
|
+
const savedId = id ?? (feedback.startsWith("[set-defaults]") ? null : createNoteId());
|
|
231
264
|
const pendingBefore = localAll().find((note) => note.id === savedId);
|
|
232
265
|
const saveOffline = () => {
|
|
233
266
|
const localId = saveLocal(route, feedback, element, savedId, payload, viewport, context);
|
|
@@ -266,7 +299,7 @@ function createIterationStore(cfg = {}) {
|
|
|
266
299
|
}
|
|
267
300
|
if (!result || typeof result.id !== "string" || !result.id) return saveOffline();
|
|
268
301
|
if (pendingBefore) acknowledge(pendingBefore);
|
|
269
|
-
lastSave = "server";
|
|
302
|
+
lastSave = localAll().length ? "local" : "server";
|
|
270
303
|
return result.id;
|
|
271
304
|
}
|
|
272
305
|
async function deleteNote(id) {
|
|
@@ -290,14 +323,18 @@ function createIterationStore(cfg = {}) {
|
|
|
290
323
|
/** Where the last save landed, for the panel's sync notice. */
|
|
291
324
|
get lastSave() {
|
|
292
325
|
return lastSave;
|
|
326
|
+
},
|
|
327
|
+
get hasUnsyncedNotes() {
|
|
328
|
+
return localAll().length > 0;
|
|
293
329
|
}
|
|
294
330
|
};
|
|
295
331
|
}
|
|
296
332
|
|
|
297
333
|
export {
|
|
334
|
+
createNoteId,
|
|
298
335
|
describeIterationElement,
|
|
299
336
|
elementAsNode,
|
|
300
337
|
isRouteVisible,
|
|
301
338
|
createIterationStore
|
|
302
339
|
};
|
|
303
|
-
//# sourceMappingURL=chunk-
|
|
340
|
+
//# sourceMappingURL=chunk-YWMOXPE7.js.map
|
|
@@ -0,0 +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":";;;;;;;;;;;AAAA,IAAI,kBAAkB;AAGf,SAAS,eAAuB;AACrC,QAAM,SAAS,WAAW;AAC1B,MAAI,OAAO,QAAQ,eAAe,WAAY,QAAO,OAAO,WAAW;AAEvE,MAAI,OAAO,QAAQ,oBAAoB,YAAY;AACjD,UAAM,QAAQ,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC;AACvD,UAAM,CAAC,IAAK,MAAM,CAAC,IAAI,KAAQ;AAC/B,UAAM,CAAC,IAAK,MAAM,CAAC,IAAI,KAAQ;AAC/B,UAAM,MAAM,MAAM,KAAK,OAAO,CAAC,SAAS,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACnF,WAAO,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC;AAAA,EAC1G;AAGA,qBAAmB;AACnB,SAAO,GAAG,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,gBAAgB,SAAS,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAC1G;;;ACiEO,SAAS,yBACd,IACA,WAAW,GACO;AAClB,QAAM,QAAkB,CAAC;AACzB,MAAI,OAAoC;AACxC,MAAI,QAAQ;AACZ,SAAO,QAAQ,KAAK,YAAY,UAAU,QAAQ,UAAU;AAC1D,QAAI,KAAK,IAAI;AAGX,YAAM,KAAK,MAAM,KAAK,KAAK,IAAI,CAAC,MAAM,KAAK,EAAE,YAAY,CAAC,GAAG,SAAS,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE;AACrF,YAAM,QAAQ,IAAI,4BAA4B,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,EAAE,EAAE;AAC5E;AAAA,IACF;AACA,QAAI,OAAO,KAAK,QAAQ,YAAY;AACpC,UAAM,SAAsC,KAAK;AACjD,QAAI,QAAQ;AACV,YAAM,QAAQ,KAAK,iBAAiB,MAAM,KAAK,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,IAAI,IAAI;AACtF,UAAI,QAAQ,EAAG,SAAQ,cAAc,KAAK;AAAA,IAC5C;AACA,UAAM,QAAQ,IAAI;AAClB,WAAO;AACP;AAAA,EACF;AACA,SAAO;AAAA,IACL,KAAK,GAAG,QAAQ,YAAY;AAAA,IAC5B,OAAO,GAAG,eAAe,IAAI,KAAK,EAAE,QAAQ,QAAQ,GAAG,EAAE,MAAM,GAAG,EAAE;AAAA,IACpE,UAAU,MAAM,KAAK,KAAK;AAAA,EAC5B;AACF;AAGO,SAAS,cAAc,IAAuB;AACnD,QAAM,QAAQ,oBAAI,QAA2B;AAC7C,QAAM,OAAO,CAAC,MAAmD;AAC/D,QAAI,CAAC,EAAG,QAAO;AACf,UAAM,SAAS,MAAM,IAAI,CAAC;AAC1B,QAAI,OAAQ,QAAO;AACnB,UAAM,UAAoB;AAAA,MACxB,SAAS,EAAE;AAAA,MACX,IAAI,EAAE,MAAM;AAAA,MACZ,IAAI,cAAc;AAAE,eAAO,EAAE;AAAA,MAAa;AAAA,MAC1C,IAAI,gBAAgB;AAAE,eAAO,KAAK,EAAE,aAAa;AAAA,MAAG;AAAA,MACpD,IAAI,WAAW;AAAE,eAAO,MAAM,KAAK,EAAE,UAAU,CAAC,UAAU,KAAK,KAAK,CAAa;AAAA,MAAG;AAAA,MACpF,IAAI,gBAAgB;AAClB,YAAI,QAAQ;AACZ,iBAAS,UAAU,EAAE,wBAAwB,SAAS,UAAU,QAAQ,uBAAwB;AAChG,eAAO;AAAA,MACT;AAAA,IACF;AACA,UAAM,IAAI,GAAG,OAAO;AACpB,WAAO;AAAA,EACT;AACA,QAAM,OAAO,KAAK,EAAE;AACpB,MAAI,SAAS,MAAM;AACjB,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AACA,SAAO;AACT;AAeA,IAAM,iBAAiB,CAAC,UAAU,WAAW,aAAa,UAAU,SAAS;AAE7E,SAAS,kBAAkB,OAAe,SAA0B;AAClE,MAAI,QAAQ,SAAS,GAAG,EAAG,QAAO,MAAM,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC;AACvE,MAAI,QAAQ,SAAS,GAAG,EAAG,QAAO,MAAM,WAAW,OAAO;AAC1D,SAAO,UAAU,WAAW,MAAM,WAAW,GAAG,OAAO,GAAG;AAC5D;AAGO,SAAS,eAAe,OAAe,OAAyB,CAAC,GAAY;AAClF,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,eAAe,KAAK,gBAAgB,CAAC,QAAQ,SAAS,QAAQ;AAGpE,aAAW,KAAK,cAAc;AAC5B,QAAI,UAAU,KAAK,MAAM,WAAW,GAAG,CAAC,GAAG,EAAG,QAAO;AAAA,EACvD;AACA,MAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,GAAG;AAC3C,WAAO,KAAK,QAAQ,KAAK,CAAC,MAAM,kBAAkB,OAAO,CAAC,CAAC;AAAA,EAC7D;AACA,SAAO,CAAC,OAAO,KAAK,CAAC,MAAM,kBAAkB,OAAO,CAAC,CAAC;AACxD;AAsBO,SAAS,qBAAqB,MAA4B,CAAC,GAAG;AACnE,QAAM,WAAW,IAAI,YAAY;AACjC,QAAM,MAAM,IAAI,OAAO;AACvB,MAAI,WAAgC;AACpC,MAAI,YAAkC;AAEtC,WAAS,WAAW,KAAqC;AACvD,QAAI;AACF,YAAM,QAAQ,KAAK,MAAM,OAAO,IAAI;AACpC,UAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,CAAC,MAAM,MAAM,CAAC,SAAS,QAAQ,OAAO,KAAK,OAAO,YAC7E,OAAO,KAAK,UAAU,YAAY,OAAO,KAAK,aAAa,YAAY,OAAO,KAAK,cAAc,QAAQ,EAAG,OAAM,IAAI,MAAM,eAAe;AAC7I,aAAO,MAAM,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,EAAE,QAAQ,MAAM,EAAE,EAAE,IAAI,CAAC,OAAO;AAAA,QACrE,GAAG;AAAA,QACH,SAAS,EAAE,WAAW;AAAA,MACxB,EAAE;AAAA,IACJ,SAAS,OAAO;AACd,YAAM,IAAI,MAAM,2EAA2E,EAAE,MAAM,CAAC;AAAA,IACtG;AAAA,EACF;AAEA,WAAS,WAA4B;AACnC,QAAI;AACJ,QAAI;AAAE,gBAAU,OAAO;AAAA,IAAc,QAAQ;AAAE,aAAO,CAAC;AAAA,IAAG;AAC1D,WAAO,WAAW,mBAAmB,SAAS,KAAK,CAAC,SAAS,aAAa;AACxE,YAAM,SAAS,IAAI,IAAI,CAAC,GAAG,WAAW,QAAQ,GAAG,GAAG,WAAW,OAAO,CAAC,EAAE,IAAI,UAAQ,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;AACrG,aAAO,KAAK,UAAU,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC;AAAA,IAC5C,CAAC,CAAC;AAAA,EACJ;AAEA,WAAS,WAAW,OAAwB;AAC1C,wBAAoB,OAAO,cAAc,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,EACrE;AAEA,WAAS,YAAY,MAA2B;AAC9C,UAAM,UAAU,SAAS;AACzB,UAAM,WAAW,KAAK,UAAU,IAAI;AACpC,UAAM,YAAY,QAAQ,OAAO,CAAC,SAAS,KAAK,OAAO,KAAK,MAAM,KAAK,UAAU,IAAI,MAAM,QAAQ;AACnG,QAAI,UAAU,WAAW,QAAQ,OAAQ,YAAW,SAAS;AAAA,EAC/D;AAEA,iBAAe,oBAAmC;AAChD,QAAI,CAAC,SAAU;AACf,UAAM,UAAU,SAAS;AACzB,QAAI,QAAQ,WAAW,EAAG;AAC1B,eAAW,QAAQ,SAAS;AAG1B,UAAI,KAAK,SAAS,KAAK,EAAE,SAAS,oBAAqB;AACvD,UAAI,WAAW;AACf,YAAM,QAAQ,MAAM,QAAQ,UAAU;AAAA,QACpC,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,IAAI,KAAK;AAAA,UACT,WAAW,KAAK;AAAA,UAChB,OAAO,KAAK;AAAA,UACZ,UAAU,KAAK;AAAA,UACf,SAAS,KAAK,WAAW;AAAA,UACzB,GAAI,KAAK,aAAa,SAAY,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,UACjE,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,UAC9D,GAAI,KAAK,kBAAkB,SAAY,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;AAAA,UAChF,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,UAC9D,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,QACrD,CAAC;AAAA,MACH,GAAG,OAAO,QAAQ;AAChB,YAAI,CAAC,IAAI,IAAI;AACX,qBAAW,IAAI,UAAU,OAAO,IAAI,SAAS;AAC7C,iBAAO;AAAA,QACT;AACA,eAAO,MAAM,IAAI,KAAK;AAAA,MACxB,CAAC;AACD,UAAI,CAAC,SAAS,OAAO,MAAM,OAAO,YAAY,CAAC,MAAM,IAAI;AACvD,YAAI,SAAU;AACd;AAAA,MACF;AAGA,kBAAY,IAAI;AAAA,IAClB;AAAA,EACF;AAEA,WAAS,sBAAqC;AAC5C,kBAAc,kBAAkB,EAAE,QAAQ,MAAM;AAAE,kBAAY;AAAA,IAAM,CAAC;AACrE,WAAO;AAAA,EACT;AAEA,iBAAe,WAAW,KAAuC;AAC/D,UAAM,oBAAoB,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC1C,UAAM,QAAQ,SAAS;AACvB,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,KAAK,QAAW,OAAO,QAAQ;AAC1D,YAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,mCAAmC,IAAI,MAAM,GAAG;AAC7E,eAAO,MAAM,IAAI,KAAK;AAAA,MACxB,CAAC;AACD,YAAM,aAAa,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,KAAK,EAAE,CAAC;AACvD,aAAO,CAAC,GAAG,OAAO,OAAO,CAAC,SAAS,CAAC,WAAW,IAAI,KAAK,EAAE,CAAC,GAAG,GAAG,KAAK;AAAA,IACxE,SAAS,OAAO;AACd,UAAI,MAAM,OAAQ,QAAO;AACzB,YAAM;AAAA,IACR;AAAA,EACF;AAGA,iBAAe,UAAU,OAA0C;AACjE,QAAI,CAAC,UAAU;AACb,aAAO,SAAS,EACb,OAAO,CAAC,MAAM,UAAU,UAAa,EAAE,UAAU,KAAK,EACtD,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAAA,IAC1D;AACA,YAAQ,MAAM;AAAA,MACZ,UAAU,SAAY,GAAG,QAAQ,WAAW,GAAG,QAAQ,UAAU,mBAAmB,KAAK,CAAC;AAAA,IAC5F,GAAG,OAAO,CAAC,SAAS,UAAU,UAAa,KAAK,UAAU,KAAK,EAC5D,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAAA,EAC1D;AAGA,iBAAe,eAAyC;AACtD,QAAI,CAAC,UAAU;AACb,aAAO,SAAS,EACb,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EACrB,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAAA,IAC1D;AACA,YAAQ,MAAM,WAAW,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI,EAC5D,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAAA,EAC1D;AAEA,WAAS,UACP,OACA,UACA,SACA,IACA,SACA,UACA,SACQ;AACR,UAAM,MAAM,SAAS;AACrB,QAAI;AACJ,UAAM,kBAAkB,SAAS,WAAW,gBAAgB;AAC5D,UAAM,WAAW,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,EAAE,UAAU,KAAK,IAAI,kBAAkB,IAAI,OAAO,OAAK,CAAC,EAAE,QAAQ,EAAE,UAAU,SAAS,EAAE,SAAS,WAAW,gBAAgB,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC,EAAE,CAAC,IAAI;AACjP,QAAI,UAAU;AACZ,UAAI,mBAAmB,CAAC,IAAI;AAC1B,eAAO,SAAS;AAChB,eAAO,SAAS;AAChB,eAAO,SAAS;AAChB,eAAO,SAAS;AAAA,MAClB;AACA,eAAS,WAAW;AACpB,eAAS,UAAU;AACnB,UAAI,YAAY,OAAW,UAAS,UAAU;AAC9C,UAAI,iBAAiB;AACnB,iBAAS,aAAY,oBAAI,KAAK,GAAE,YAAY;AAC5C,YAAI,SAAU,UAAS,WAAW;AAAA,MACpC;AACA,UAAI,QAAS,QAAO,OAAO,UAAU,OAAO;AAC5C,UAAI,SAAS,SAAU,UAAS,UAAU,SAAS,SAAS,CAAC,KAAK;AAClE,gBAAU,SAAS;AAAA,IACrB,OAAO;AACL,gBAAU,MAAM,aAAa;AAC7B,UAAI,KAAK;AAAA,QACP,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,QAC/B,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,QAC3C,GAAG;AAAA,QACH,GAAI,SAAS,WAAW,EAAE,SAAS,QAAQ,SAAS,CAAC,KAAK,KAAK,IAAI,CAAC;AAAA,MACtE,CAAC;AAAA,IACH;AACA,eAAW,kBAAkB,IAAI,OAAO,OAAK,EAAE,OAAO,WAAW,EAAE,QAAQ,EAAE,UAAU,SAAS,CAAC,EAAE,SAAS,WAAW,gBAAgB,CAAC,IAAI,GAAG;AAC/I,WAAO;AAAA,EACT;AAEA,iBAAe,SACb,OACA,UACA,SACA,IAEA,SAEA,UACA,SACiB;AACjB,QAAI,SAAS,KAAK,EAAE,SAAS,qBAAqB;AAChD,YAAM,IAAI,WAAW,4BAA4B,mBAAmB,cAAc;AAAA,IACpF;AACA,QAAI,CAAC,UAAU;AACb,YAAMA,WAAU,UAAU,OAAO,UAAU,SAAS,IAAI,SAAS,UAAU,OAAO;AAClF,iBAAW;AACX,aAAOA;AAAA,IACT;AAGA,UAAM,UAAU,OAAO,SAAS,WAAW,gBAAgB,IAAI,OAAO,aAAa;AACnF,UAAM,gBAAgB,SAAS,EAAE,KAAK,CAAC,SAAS,KAAK,OAAO,OAAO;AACnE,UAAM,cAAc,MAAM;AACxB,YAAM,UAAU,UAAU,OAAO,UAAU,SAAS,SAAS,SAAS,UAAU,OAAO;AACvF,iBAAW;AACX,aAAO;AAAA,IACT;AACA,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,QAAQ,UAAU;AAAA,QAC/B,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,IAAI;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA,GAAG;AAAA,UACH,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,UAC3C,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,QACjC,CAAC;AAAA,MACH,GAAG,OAAO,aAAa;AACrB,cAAM;AACN,YAAI,CAAC,SAAS,MAAM,SAAS,UAAU,IAAK,OAAM,IAAI,MAAM,iCAAiC,SAAS,MAAM,GAAG;AAC/G,eAAO,MAAM,SAAS,KAAK,EAAE,MAAM,CAAC,UAAU;AAC5C,cAAI,SAAS,GAAI,OAAM;AACvB,iBAAO;AAAA,QACT,CAAC;AAAA,MACH,CAAC;AAAA,IACH,QAAQ;AACN,UAAI,OAAO,CAAC,IAAI,MAAM,IAAI,SAAS,IAAK,OAAM,IAAI,MAAM,iCAAiC,IAAI,MAAM,GAAG;AACtG,aAAO,YAAY;AAAA,IACrB;AAEA,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,IAAI,MAAM,iCAAiC,KAAK,MAAM,IAAI,OAAO,QAAQ,UAAU,WAAW,KAAK,OAAO,KAAK,KAAK,EAAE,EAAE;AAAA,IAChI;AACA,QAAI,CAAC,UAAU,OAAO,OAAO,OAAO,YAAY,CAAC,OAAO,GAAI,QAAO,YAAY;AAC/E,QAAI,cAAe,aAAY,aAAa;AAC5C,eAAW,SAAS,EAAE,SAAS,UAAU;AACzC,WAAO,OAAO;AAAA,EAChB;AAEA,iBAAe,WAAW,IAA2B;AACnD,QAAI,CAAC,UAAU;AACb,iBAAW,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;AAChD;AAAA,IACF;AACA,UAAM,MAAM,MAAM,QAAQ,GAAG,QAAQ,OAAO,mBAAmB,EAAE,CAAC,IAAI;AAAA,MACpE,QAAQ;AAAA,IACV,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,mCAAmC,IAAI,MAAM,GAAG;AAC7E,UAAM,QAAQ,SAAS;AACvB,QAAI,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,EAAE,EAAG,YAAW,MAAM,OAAO,CAAC,SAAS,KAAK,OAAO,EAAE,CAAC;AAAA,EAC7F;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA,IAAI,WAAgC;AAClC,aAAO;AAAA,IACT;AAAA,IACA,IAAI,mBAA4B;AAC9B,aAAO,SAAS,EAAE,SAAS;AAAA,IAC7B;AAAA,EACF;AACF;","names":["savedId"]}
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
-
OVERLAY_GROUP_ID
|
|
3
|
-
overlayGroups,
|
|
4
|
-
variantAppliesTo
|
|
2
|
+
OVERLAY_GROUP_ID
|
|
5
3
|
} from "./chunk-SNGBNVH2.js";
|
|
6
4
|
import {
|
|
7
|
-
ensureDirIgnored,
|
|
8
5
|
ensureFileIgnored,
|
|
9
6
|
withFileLock,
|
|
10
7
|
writeJson
|
|
11
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-AP7O357L.js";
|
|
12
9
|
|
|
13
10
|
// src/variants-store.ts
|
|
14
11
|
import { execFileSync } from "child_process";
|
|
15
12
|
import { createHash, randomUUID } from "crypto";
|
|
16
13
|
import {
|
|
17
14
|
existsSync,
|
|
18
|
-
cpSync,
|
|
19
15
|
mkdirSync,
|
|
20
16
|
lstatSync,
|
|
21
17
|
readdirSync,
|
|
@@ -478,44 +474,6 @@ function variantsResponse(opts, req) {
|
|
|
478
474
|
if (method === "GET" && path === "/") {
|
|
479
475
|
return json(200, { variants: listVariants(opts), active: readActive(opts), knobs: readKnobs(opts) });
|
|
480
476
|
}
|
|
481
|
-
if (method === "POST" && path === "/manage") {
|
|
482
|
-
const body = req.body;
|
|
483
|
-
if (!body || typeof body.axisId !== "string" || !["remove-option", "remove-axis", "keep-default"].includes(String(body.action))) {
|
|
484
|
-
return json(400, { error: "A selector id and a valid action are required." });
|
|
485
|
-
}
|
|
486
|
-
const variants = listVariants(opts);
|
|
487
|
-
const group = overlayGroups(variants).find((g) => g.id === body.axisId);
|
|
488
|
-
const knobs = readKnobs(opts);
|
|
489
|
-
const knob = knobs.find((k) => k.id === body.axisId);
|
|
490
|
-
if (!group && !knob) return json(404, { error: "This selector no longer exists." });
|
|
491
|
-
let removed = (group?.members ?? []).filter((v) => typeof body.route !== "string" || variantAppliesTo(v.route, body.route));
|
|
492
|
-
let nextKnobs = knobs.filter((k) => k.id !== knob?.id);
|
|
493
|
-
if (body.action === "remove-option" && knob) {
|
|
494
|
-
if (knob.kind !== "color") return json(400, { error: "This selector has no removable options." });
|
|
495
|
-
const swatch = knob.swatches.find((s) => s.id === body.optionId);
|
|
496
|
-
if (!swatch || swatch.value.toLowerCase() === knob.defaultColor.toLowerCase()) return json(400, { error: "The default colour cannot be removed." });
|
|
497
|
-
nextKnobs = knobs.map((k) => k.id === knob.id ? { ...knob, swatches: knob.swatches.filter((s) => s.id !== body.optionId) } : k);
|
|
498
|
-
} else if (body.action === "remove-option") {
|
|
499
|
-
if (!group || typeof body.optionId !== "string" || !removed.some((v) => v.id === body.optionId)) {
|
|
500
|
-
return json(400, { error: "Only an alternative option can be removed. The default belongs to the source." });
|
|
501
|
-
}
|
|
502
|
-
removed = removed.filter((v) => v.id === body.optionId);
|
|
503
|
-
}
|
|
504
|
-
const recovery = join(resolve(opts.root), ".isotone", "removed", randomUUID());
|
|
505
|
-
try {
|
|
506
|
-
mkdirSync(recovery, { recursive: true });
|
|
507
|
-
ensureDirIgnored(dirname(recovery));
|
|
508
|
-
for (const v of removed) cpSync(variantDir(opts, v.id), join(recovery, v.id), { recursive: true });
|
|
509
|
-
if (knob) writeFileSync(join(recovery, "knobs.json"), JSON.stringify(knobs, null, 2));
|
|
510
|
-
const changedFiles = removed.flatMap((v) => Object.keys(v.files));
|
|
511
|
-
for (const v of removed) dropVariant(opts, v.id);
|
|
512
|
-
if (knob) writeFileSync(join(resolve(opts.root), KNOBS_FILE), `${JSON.stringify(nextKnobs, null, 2)}
|
|
513
|
-
`);
|
|
514
|
-
return json(200, { variants: listVariants(opts), active: readActive(opts), knobs: readKnobs(opts), changedFiles });
|
|
515
|
-
} catch (error) {
|
|
516
|
-
return json(500, { error: error instanceof Error ? error.message : "Could not remove the selector." });
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
477
|
if (method === "POST" && path === "/active") {
|
|
520
478
|
const body = req.body;
|
|
521
479
|
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
@@ -566,4 +524,4 @@ export {
|
|
|
566
524
|
writeActive,
|
|
567
525
|
variantsResponse
|
|
568
526
|
};
|
|
569
|
-
//# sourceMappingURL=chunk-
|
|
527
|
+
//# sourceMappingURL=chunk-ZBULKB3S.js.map
|