isotone 1.0.5 → 1.0.7
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/AGENTS.md +3 -1
- package/CHANGELOG.md +8 -0
- package/README.md +6 -2
- package/dist/{chunk-UYMMI2IH.js → chunk-JGIX4XEH.js} +21 -1
- package/dist/chunk-JGIX4XEH.js.map +1 -0
- package/dist/{chunk-DD6BKC3R.js → chunk-UT2NLGBM.js} +2 -2
- package/dist/{chunk-E5BF3ND2.js → chunk-WMNR7KVD.js} +2 -2
- package/dist/{chunk-E5BF3ND2.js.map → chunk-WMNR7KVD.js.map} +1 -1
- package/dist/{chunk-AP7O357L.js → chunk-X6V2AKJL.js} +25 -5
- package/dist/chunk-X6V2AKJL.js.map +1 -0
- package/dist/cli.js +40 -10
- package/dist/cli.js.map +1 -1
- package/dist/core.d.ts +6 -0
- package/dist/core.js +1 -1
- package/dist/index.js +142 -10
- package/dist/index.js.map +1 -1
- package/dist/isotone.global.js +33 -21
- package/dist/serve.js +2 -2
- package/dist/server.d.ts +3 -0
- package/dist/server.js +2 -2
- package/dist/vite-plugin.js +3 -3
- package/docs/integrations.md +14 -1
- package/package.json +1 -1
- package/dist/chunk-AP7O357L.js.map +0 -1
- package/dist/chunk-UYMMI2IH.js.map +0 -1
- /package/dist/{chunk-DD6BKC3R.js.map → chunk-UT2NLGBM.js.map} +0 -0
package/AGENTS.md
CHANGED
|
@@ -17,7 +17,9 @@ for Vite, standalone servers, React mounts, and other request handlers.
|
|
|
17
17
|
The loop for every note is act, verify, close. A fixed note stays open in the
|
|
18
18
|
reviewer's panel until you run `npx isotone --done <id>`.
|
|
19
19
|
|
|
20
|
-
Run `npx isotone --json` in the project under review.
|
|
20
|
+
Run `npx isotone --json` in the project under review. When several agents share a
|
|
21
|
+
dev server, list only your page's notes with `npx isotone <route> --json`, and run
|
|
22
|
+
`npx isotone --working <id>` on a note when you start it so the reviewer sees it in progress. Each note includes a route,
|
|
21
23
|
viewport, pinned elements, optional variant or host context, and a `doneCommand`
|
|
22
24
|
for open notes. Reproduce the request at that route and viewport, make the source
|
|
23
25
|
change, verify it, then run the note's `doneCommand`. A unique ID prefix works.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.0.7
|
|
6
|
+
|
|
7
|
+
- feat: live notes, page scope and agent progress in the panel (#12)
|
|
8
|
+
|
|
9
|
+
## 1.0.6
|
|
10
|
+
|
|
11
|
+
- fix: stop the launcher flickering when hovered at the count badge (#11)
|
|
12
|
+
|
|
5
13
|
## 1.0.5
|
|
6
14
|
|
|
7
15
|
- fix: render saved-defaults notes like every other note (#10)
|
package/README.md
CHANGED
|
@@ -51,11 +51,15 @@ configuration, and production guards. Node.js 20 or later is required.
|
|
|
51
51
|
## The loop
|
|
52
52
|
|
|
53
53
|
1. Open Feedback with **⌘/Ctrl + Shift + .** Write a note, pin elements, and send.
|
|
54
|
-
2. Your agent reads `npx isotone --json`,
|
|
55
|
-
|
|
54
|
+
2. Your agent reads `npx isotone --json`, marks the note it starts with
|
|
55
|
+
`npx isotone --working <id>`, makes and verifies the change, then closes it with
|
|
56
|
+
`npx isotone --done <id>`. The panel follows along without a reload.
|
|
56
57
|
3. Review the result. For alternatives, the agent creates a
|
|
57
58
|
[variant](docs/variants.md) you can switch to in the same page.
|
|
58
59
|
|
|
60
|
+
The panel lists the current page's notes. When other pages have open notes too,
|
|
61
|
+
a row of page chips lets you include them.
|
|
62
|
+
|
|
59
63
|
Click a saved note to edit it. **Enter** sends; **Shift + Enter** adds a newline.
|
|
60
64
|
While selecting elements, click to pin or Tab to a page control and press Space.
|
|
61
65
|
Enter or Escape finishes selection. The selected element text stays attached to
|
|
@@ -205,6 +205,25 @@ function createIterationStore(cfg = {}) {
|
|
|
205
205
|
route === void 0 ? `${endpoint}?all=1` : `${endpoint}?route=${encodeURIComponent(route)}`
|
|
206
206
|
)).filter((note) => route === void 0 || note.route === route).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
207
207
|
}
|
|
208
|
+
async function pollNotes(rev) {
|
|
209
|
+
if (!endpoint) return { rev: null, notes: localAll().filter((n) => !n.done) };
|
|
210
|
+
if (localAll().length) await migrateLocalBacklog().catch(() => {
|
|
211
|
+
});
|
|
212
|
+
const local = localAll();
|
|
213
|
+
const data = await request(`${endpoint}?rev=${encodeURIComponent(rev ?? "")}`, void 0, async (res) => {
|
|
214
|
+
if (!res.ok) throw new Error(`Loading iteration notes failed (${res.status})`);
|
|
215
|
+
return await res.json();
|
|
216
|
+
});
|
|
217
|
+
if (Array.isArray(data)) return { rev: null, notes: merge(data, local) };
|
|
218
|
+
const next = typeof data?.rev === "string" ? data.rev : null;
|
|
219
|
+
if (data?.unchanged === true && next !== null) return { rev: next, notes: null };
|
|
220
|
+
if (!Array.isArray(data?.notes)) throw new Error("Unexpected notes response");
|
|
221
|
+
return { rev: next, notes: merge(data.notes, local) };
|
|
222
|
+
}
|
|
223
|
+
function merge(remote, local) {
|
|
224
|
+
const pendingIds = new Set(local.map((note) => note.id));
|
|
225
|
+
return [...remote.filter((note) => !pendingIds.has(note.id)), ...local];
|
|
226
|
+
}
|
|
208
227
|
async function pendingNotes() {
|
|
209
228
|
if (!endpoint) {
|
|
210
229
|
return localAll().filter((i) => !i.done).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
@@ -316,6 +335,7 @@ function createIterationStore(cfg = {}) {
|
|
|
316
335
|
}
|
|
317
336
|
return {
|
|
318
337
|
listNotes,
|
|
338
|
+
pollNotes,
|
|
319
339
|
pendingNotes,
|
|
320
340
|
saveNote,
|
|
321
341
|
deleteNote,
|
|
@@ -337,4 +357,4 @@ export {
|
|
|
337
357
|
isRouteVisible,
|
|
338
358
|
createIterationStore
|
|
339
359
|
};
|
|
340
|
-
//# sourceMappingURL=chunk-
|
|
360
|
+
//# sourceMappingURL=chunk-JGIX4XEH.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 /** Set while an agent says it is working on the note. Stale after a while. */\n workingAt?: 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 /**\n * The open notes on every route, but cheaply: `rev` is the revision of the\n * last answer, and an unchanged server replies with no rows (`notes: null`).\n * Older servers ignore `rev` and return the list, which counts as changed.\n * Notes this browser kept while the server was away are pushed first, and\n * stay in the result until the server has them. Failures throw, so the\n * caller can back off.\n */\n async function pollNotes(rev: string | null): Promise<{ rev: string | null; notes: IterationNote[] | null }> {\n if (!endpoint) return { rev: null, notes: localAll().filter((n) => !n.done) };\n if (localAll().length) await migrateLocalBacklog().catch(() => {});\n const local = localAll();\n const data = await request(`${endpoint}?rev=${encodeURIComponent(rev ?? '')}`, undefined, async (res) => {\n if (!res.ok) throw new Error(`Loading iteration notes failed (${res.status})`);\n return await res.json() as IterationNote[] | { rev?: unknown; unchanged?: unknown; notes?: unknown };\n });\n if (Array.isArray(data)) return { rev: null, notes: merge(data, local) };\n const next = typeof data?.rev === 'string' ? data.rev : null;\n // The panel already holds its own local saves, so an unchanged server means\n // nothing to replace.\n if (data?.unchanged === true && next !== null) return { rev: next, notes: null };\n if (!Array.isArray(data?.notes)) throw new Error('Unexpected notes response');\n return { rev: next, notes: merge(data.notes as IterationNote[], local) };\n }\n\n function merge(remote: IterationNote[], local: IterationNote[]): IterationNote[] {\n const pendingIds = new Set(local.map((note) => note.id));\n return [...remote.filter((note) => !pendingIds.has(note.id)), ...local];\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 pollNotes,\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"]}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
ensureFileIgnored,
|
|
6
6
|
withFileLock,
|
|
7
7
|
writeJson
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-X6V2AKJL.js";
|
|
9
9
|
|
|
10
10
|
// src/variants-store.ts
|
|
11
11
|
import { execFileSync } from "child_process";
|
|
@@ -524,4 +524,4 @@ export {
|
|
|
524
524
|
writeActive,
|
|
525
525
|
variantsResponse
|
|
526
526
|
};
|
|
527
|
-
//# sourceMappingURL=chunk-
|
|
527
|
+
//# sourceMappingURL=chunk-UT2NLGBM.js.map
|
|
@@ -7,7 +7,7 @@ import { randomUUID } from "crypto";
|
|
|
7
7
|
|
|
8
8
|
// package.json
|
|
9
9
|
var name = "isotone";
|
|
10
|
-
var version = "1.0.
|
|
10
|
+
var version = "1.0.7";
|
|
11
11
|
|
|
12
12
|
// src/update.ts
|
|
13
13
|
var UPDATE_ENDPOINT = "/api/iteration/update";
|
|
@@ -228,4 +228,4 @@ export {
|
|
|
228
228
|
checkUpdate,
|
|
229
229
|
startUpdateCheck
|
|
230
230
|
};
|
|
231
|
-
//# sourceMappingURL=chunk-
|
|
231
|
+
//# sourceMappingURL=chunk-WMNR7KVD.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\": \"1.0.5\",\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/assets/preview.jpg\",\n \"docs/integrations.md\",\n \"docs/migration.md\",\n \"docs/updates.md\",\n \"docs/variants.md\",\n \"docs/third-party-notices.md\",\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":";AAAA,SAAS,YAAY,oBAAoB;AACzC,SAAS,OAAO,MAAM,UAAU,QAAQ,IAAI,MAAM,iBAAiB;AACnE,SAAS,SAAS,MAAM,eAAe;AACvC,SAAS,cAAc;AACvB,SAAS,kBAAkB;;;ACHzB,WAAQ;AACR,cAAW;;;ADMN,IAAM,kBAAkB;AAC/B,IAAM,eAAe,8BAA8B,IAAW;AAC9D,IAAM,gBAAgB;AACtB,IAAM,YAAY,KAAK,KAAK,KAAK;AACjC,IAAM,aAAa,KAAK,OAAO,GAAG,kBAAkB,QAAQ,SAAS,KAAK,MAAM,UAAU;AAM1F,SAAS,aAAa,OAAwB;AAC5C,MAAI,MAAM,SAAS,IAAK,OAAM,IAAI,MAAM,kBAAkB;AAC1D,QAAM,QAAQ,4HAA4H,KAAK,KAAK;AACpJ,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,kBAAkB;AAC9C,QAAM,aAAa,MAAM,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC;AAC5C,MAAI,WAAW,KAAK,CAAC,SAAS,SAAS,KAAK,IAAI,CAAC,EAAG,OAAM,IAAI,MAAM,kBAAkB;AACtF,SAAO,EAAE,OAAO,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,MAAM,GAAG,WAAW;AAC5D;AAGO,SAAS,gBAAgB,GAAW,GAAmB;AAC5D,QAAM,OAAO,aAAa,CAAC,GAAG,QAAQ,aAAa,CAAC;AACpD,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,QAAI,KAAK,MAAM,CAAC,MAAM,MAAM,MAAM,CAAC,EAAG,QAAO,KAAK,MAAM,CAAC,IAAI,MAAM,MAAM,CAAC,IAAI,IAAI;AAAA,EACpF;AACA,MAAI,CAAC,KAAK,WAAW,UAAU,CAAC,MAAM,WAAW,QAAQ;AACvD,WAAO,KAAK,WAAW,WAAW,MAAM,WAAW,SAAS,IAAI,KAAK,WAAW,SAAS,KAAK;AAAA,EAChG;AACA,WAAS,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,WAAW,QAAQ,MAAM,WAAW,MAAM,GAAG,KAAK;AAClF,UAAM,IAAI,KAAK,WAAW,CAAC,GAAG,IAAI,MAAM,WAAW,CAAC;AACpD,QAAI,MAAM,EAAG;AACb,QAAI,MAAM,UAAa,MAAM,OAAW,QAAO,MAAM,SAAY,KAAK;AACtE,UAAM,KAAK,QAAQ,KAAK,CAAC,GAAG,KAAK,QAAQ,KAAK,CAAC;AAC/C,QAAI,MAAM,GAAI,QAAO,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,IAAI;AACjD,QAAI,OAAO,GAAI,QAAO,KAAK,KAAK;AAChC,WAAO,IAAI,IAAI,IAAI;AAAA,EACrB;AACA,SAAO;AACT;AAEA,SAAS,SAAS,WAAmD;AACnE,MAAI;AACF,UAAM,QAAQ,KAAK,MAAM,aAAa,KAAK,WAAW,cAAc,GAAG,MAAM,CAAC;AAC9E,WAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAAI,QAAQ;AAAA,EAC/E,QAAQ;AAAE,WAAO;AAAA,EAAM;AACzB;AAEA,SAAS,YAAY,KAA0E;AAC7F,MAAI,YAAY,QAAQ,GAAG;AAC3B,SAAO,MAAM;AACX,UAAM,OAAO,SAAS,SAAS;AAC/B,QAAI,KAAM,QAAO,EAAE,WAAW,KAAK;AACnC,QAAI,QAAQ,SAAS,MAAM,aAAa,WAAW,KAAK,WAAW,MAAM,CAAC,EAAG,QAAO,EAAE,WAAW,QAAQ,GAAG,GAAG,MAAM,KAAK;AAC1H,gBAAY,QAAQ,SAAS;AAAA,EAC/B;AACF;AAEA,SAAS,gBAAgB,SAAyE;AAChG,MAAI,YAAY,QAAQ;AACxB,SAAO,MAAM;AACX,UAAM,OAAO,SAAS,SAAS;AAC/B,QAAI,CAAC,gBAAgB,mBAAmB,wBAAwB,kBAAkB,EAAE,KAAK,CAAC,QAAQ;AAChG,YAAM,eAAe,OAAO,GAAG;AAC/B,aAAO,gBAAgB,OAAO,iBAAiB,YAAY,OAAO,OAAO,cAAc,IAAW;AAAA,IACpG,CAAC,EAAG,QAAO,EAAE,WAAW,KAAK;AAC7B,QAAI,QAAQ,SAAS,MAAM,aAAa,WAAW,KAAK,WAAW,MAAM,CAAC,EAAG,QAAO;AACpF,gBAAY,QAAQ,SAAS;AAAA,EAC/B;AACF;AAEO,SAAS,YAAY,KAAa,QAAgB,UAA6D;AACpH,eAAa,MAAM;AACnB,QAAM,UAAU,gBAAgB,YAAY,GAAG,CAAC;AAChD,MAAI,QAAQ,MAAM,SAAS,KAAa,QAAO,EAAE,SAAS,MAAM,SAAS,2FAA2F;AACpK,MAAI,UAAU;AACd,MAAI,YAAY,QAAQ;AACxB,QAAM,QAAQ,oBAAI,IAAa;AAC/B,SAAO,CAAC,SAAS;AACf,UAAM,WAAW,SAAS,SAAS,GAAG;AACtC,QAAI,OAAO,aAAa,UAAU;AAChC,YAAM,QAAQ,oBAAoB,KAAK,QAAQ;AAC/C,UAAI,CAAC,MAAO,QAAO,EAAE,SAAS,MAAM,SAAS,mHAA8G;AAC3J,gBAAU,MAAM,CAAC;AACjB;AAAA,IACF;AACA,QAAI,CAAC,YAAY,WAAW,EAAE,KAAK,CAAC,SAAS,WAAW,KAAK,WAAW,IAAI,CAAC,CAAC,EAAG,QAAO,EAAE,SAAS,MAAM,SAAS,0EAAqE;AACvL,QAAI,WAAW,KAAK,WAAW,gBAAgB,CAAC,EAAG,OAAM,IAAI,MAAM;AACnE,QAAI,WAAW,KAAK,WAAW,WAAW,CAAC,EAAG,OAAM,IAAI,MAAM;AAC9D,QAAI,CAAC,qBAAqB,qBAAqB,EAAE,KAAK,CAAC,SAAS,WAAW,KAAK,WAAW,IAAI,CAAC,CAAC,EAAG,OAAM,IAAI,KAAK;AACnH,QAAI,MAAM,QAAQ,QAAQ,SAAS,MAAM,aAAa,WAAW,KAAK,WAAW,MAAM,CAAC,EAAG;AAC3F,gBAAY,QAAQ,SAAS;AAAA,EAC/B;AACA,MAAI,CAAC,WAAW,MAAM,OAAO,EAAG,QAAO,EAAE,SAAS,MAAM,SAAS,8EAA8E;AAC/I,cAAY,CAAC,GAAG,KAAK,EAAE,CAAC,KAAK;AAC7B,QAAM,aAAa,QAAQ,MAAM;AACjC,QAAM,WAAW,QAAQ,MAAM;AAC/B,QAAM,QAAQ,QAAQ,MAAM;AAC5B,MAAI,WAAW,IAAW,KAAK,QAAQ,IAAW,EAAG,QAAO,EAAE,SAAS,MAAM,SAAS,2EAA2E;AACjK,QAAM,MAAM,aAAa,IAAW,IAAI,KAAK;AAC7C,MAAI,YAAY,YAAY,UAAU,WAAW,KAAK,QAAQ,WAAW,qBAAqB,CAAC,IAAI,QAAQ;AAC3G,MAAI,YAAY,UAAU,QAAQ,MAAM,YAAY;AAClD,UAAM,WAAW,QAAQ,KAAK;AAC9B,QAAI,OAAO,aAAa,YAAY,CAAC,cAAc,KAAK,QAAQ,EAAG,QAAO,EAAE,SAAS,MAAM,SAAS,iJAAiJ;AACrP,QAAI,SAAS,WAAW,SAAS,EAAG,aAAY;AAAA,EAClD;AACA,QAAM,UAAU,YAAY,QACxB,cAAc,GAAG,iBAAiB,IAAW,IAAI,MAAM,KACvD,GAAG,OAAO,OAAO,GAAG,GAAG,SAAS,YAAY,IAAW,IAAI,MAAM;AACrE,SAAO,EAAE,SAAS,EAAE,SAAS,SAAS,KAAK,QAAQ,UAAU,EAAE;AACjE;AAEA,eAAsB,YAAY,UAAwB,OAAO,QAAuC;AACtG,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,MAAM,WAAW,MAAM;AACrC,MAAI,QAAQ,QAAS,OAAM;AAAA,MACtB,SAAQ,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;AAC5D,QAAM,QAAQ,WAAW,OAAO,aAAa;AAC7C,MAAI;AACF,QAAI,WAAW,OAAO,QAAS,OAAM,IAAI,MAAM,yBAAyB;AACxE,UAAM,WAAW,MAAM,QAAQ,cAAc,EAAE,QAAQ,WAAW,QAAQ,UAAU,SAAS,SAAS,EAAE,QAAQ,mBAAmB,EAAE,CAAC;AACtI,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,0BAA0B,SAAS,MAAM,GAAG;AAC9E,QAAI,CAAC,SAAS,KAAM,OAAM,IAAI,MAAM,sCAAsC;AAC1E,UAAM,SAAS,SAAS,KAAK,UAAU;AACvC,UAAM,SAAuB,CAAC;AAC9B,QAAI,QAAQ;AACZ,QAAI;AACF,aAAO,MAAM;AACX,cAAM,EAAE,OAAO,KAAK,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,KAAM;AACV,iBAAS,MAAM;AACf,YAAI,QAAQ,KAAK,MAAM;AACrB,gBAAM,OAAO,OAAO;AACpB,gBAAM,IAAI,MAAM,kCAAkC;AAAA,QACpD;AACA,eAAO,KAAK,KAAK;AAAA,MACnB;AAAA,IACF,UAAE;AAAU,aAAO,YAAY;AAAA,IAAG;AAClC,UAAM,OAAO,KAAK,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,CAAC;AAC9D,QAAI,OAAO,KAAK,YAAY,SAAU,OAAM,IAAI,MAAM,+BAA+B;AACrF,iBAAa,KAAK,OAAO;AACzB,WAAO,KAAK;AAAA,EACd,UAAE;AAAU,iBAAa,KAAK;AAAG,YAAQ,oBAAoB,SAAS,KAAK;AAAA,EAAG;AAChF;AAEO,SAAS,WAAW,QAAgB,KAAa,SAA+B;AACrF,QAAM,QAAQ,gBAAgB,QAAQ,OAAc;AACpD,SAAO;AAAA,IACL;AAAA,IAAgB,eAAe;AAAA,IAC/B,QAAQ,QAAQ,IAAI,cAAc,QAAQ,IAAI,UAAU;AAAA,IACxD,GAAI,QAAQ,IAAI,YAAY,KAAK,QAAQ,OAAO,IAAI,EAAE,SAAS,KAAK;AAAA,EACtE;AACF;AAEA,eAAsB,YAAY,KAAa,SAAmB,SAAwB,QAA2C;AACnI,MAAI;AAAE,WAAO,WAAW,MAAM,YAAY,SAAS,MAAM,GAAG,KAAK,OAAO;AAAA,EAAG,QACrE;AAAE,WAAO,EAAE,yBAAgB,eAAe,MAAM,QAAQ,eAAe,SAAS,MAAM,SAAS,gGAAgG;AAAA,EAAG;AAC1M;AAGA,eAAe,UAAU,MAA2C;AAClE,MAAI;AACF,SAAK,MAAM,KAAK,IAAI,GAAG,OAAO,KAAM,QAAO;AAC3C,UAAM,OAAO,KAAK,MAAM,MAAM,SAAS,MAAM,MAAM,CAAC;AACpD,QAAI,CAAC,OAAO,SAAS,KAAK,SAAS,KAAK,KAAK,YAAY,KAAK,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,aAAa,UAAW,QAAO;AACxH,QAAI,KAAK,kBAAkB,KAAM,cAAa,KAAK,aAAa;AAChE,WAAO;AAAA,EACT,QAAQ;AAAE,WAAO;AAAA,EAAM;AACzB;AAEA,eAAe,UAAU,MAAc,eAA6C;AAClF,QAAM,YAAY,GAAG,IAAI,IAAI,WAAW,CAAC;AACzC,MAAI;AACF,UAAM,UAAU,WAAW,KAAK,UAAU,EAAE,WAAW,KAAK,IAAI,GAAG,cAAc,CAAC,GAAG,EAAE,MAAM,KAAO,MAAM,KAAK,CAAC;AAChH,UAAM,OAAO,WAAW,IAAI;AAAA,EAC9B,UAAE;AAAU,UAAM,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;AAAA,EAAG;AACpD;AAGO,SAAS,iBAAiB,KAAa,UAAU,MAAM,UAAmF,CAAC,GAAyE;AACzN,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,MAAM,QAAQ,OAAO,QAAQ;AACnC,QAAM,MAAM,CAAC,WAAW,IAAI,yBAAyB,OAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,OAAO,WAAW,IAAI,OAAO,OAAQ,IAAI,aAAa,UAAU,CAAC,CAAC,IAAI;AACnJ,MAAI,OAAmB,EAAE,yBAAgB,eAAe,MAAM,QAAQ,MAAM,aAAa,YAAY,SAAS,KAAK;AACnH,QAAM,QAAQ,MAAM,QAAQ,QAAQ,KAAK,YAAY;AACnD,UAAM,OAAO,QAAQ,aAAa;AAClC,QAAI,SAAS;AACb,QAAI;AACF,YAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,UAAI,OAAO;AAAE,eAAO,MAAM,gBAAgB,WAAW,MAAM,eAAe,GAAG,IAAI,EAAE,GAAG,MAAM,QAAQ,cAAc;AAAG;AAAA,MAAQ;AAC7H,YAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,UAAI;AAAE,eAAO,MAAM,KAAK,GAAG,IAAI,SAAS,MAAM,GAAK,GAAG,MAAM;AAAG,iBAAS;AAAA,MAAM,QACxE;AAEJ,YAAI,KAAK,IAAI,KAAK,MAAM,KAAK,GAAG,IAAI,OAAO,GAAG,UAAU,gBAAgB,EAAG,OAAM,GAAG,GAAG,IAAI,SAAS,EAAE,OAAO,KAAK,CAAC;AACnH,eAAO,EAAE,GAAG,MAAM,QAAQ,cAAc;AACxC;AAAA,MACF;AACA,YAAM,YAAY,MAAM,UAAU,IAAI;AACtC,UAAI,WAAW;AAAE,eAAO,UAAU,gBAAgB,WAAW,UAAU,eAAe,GAAG,IAAI,EAAE,GAAG,MAAM,QAAQ,cAAc;AAAG;AAAA,MAAQ;AACzI,YAAM,UAAU,MAAM,IAAI;AAC1B,aAAO,MAAM,YAAY,KAAK,QAAW,QAAQ,SAAS,WAAW,MAAM;AAC3E,UAAI,KAAK,cAAe,OAAM,UAAU,MAAM,KAAK,aAAa;AAAA,IAClE,QAAQ;AAAE,aAAO,EAAE,yBAAgB,eAAe,MAAM,QAAQ,eAAe,SAAS,KAAK;AAAA,IAAG,UAChG;AAAU,UAAI,OAAQ,OAAM,GAAG,GAAG,IAAI,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAAG;AAAA,EACnF,GAAG;AACH,SAAO,EAAE,aAAa,MAAM,MAAM,OAAO,SAAS,MAAM,WAAW,MAAM,EAAE;AAC7E;","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\": \"1.0.7\",\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/assets/preview.jpg\",\n \"docs/integrations.md\",\n \"docs/migration.md\",\n \"docs/updates.md\",\n \"docs/variants.md\",\n \"docs/third-party-notices.md\",\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":[]}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
// src/store.ts
|
|
6
6
|
import { execFileSync } from "child_process";
|
|
7
7
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8
|
-
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, realpathSync, writeFileSync as writeFileSync2 } from "fs";
|
|
8
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, realpathSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
9
9
|
import { basename, dirname as dirname2, isAbsolute, join, relative, resolve, sep } from "path";
|
|
10
10
|
|
|
11
11
|
// src/file-store.ts
|
|
@@ -165,6 +165,7 @@ function toItem(n) {
|
|
|
165
165
|
done: n.done,
|
|
166
166
|
createdAt: n.createdAt
|
|
167
167
|
};
|
|
168
|
+
if (n.workingAt && !n.done) item.workingAt = n.workingAt;
|
|
168
169
|
if (n.viewport !== void 0) item.viewport = n.viewport;
|
|
169
170
|
if (n.payload !== void 0) item.payload = n.payload;
|
|
170
171
|
return {
|
|
@@ -191,11 +192,14 @@ function handleNotesRequest(file, req) {
|
|
|
191
192
|
body
|
|
192
193
|
});
|
|
193
194
|
if (method === "GET") {
|
|
195
|
+
const polled = query.rev !== void 0;
|
|
196
|
+
const rev = polled ? notesRevision(file) : "";
|
|
197
|
+
if (polled && rev === String(query.rev)) return json(200, { rev, unchanged: true });
|
|
194
198
|
const route = String(query.route ?? "");
|
|
195
199
|
const all = query.all !== void 0 && String(query.all) !== "" && String(query.all) !== "0";
|
|
196
|
-
const notes = readNotes(file);
|
|
197
|
-
const rows = (all ? notes : route ? notes.filter((n) => n.route === route) : notes.filter((n) => !n.done)).sort((a, b) => b.createdAt.localeCompare(a.createdAt)).map(toItem);
|
|
198
|
-
return json(200, rows);
|
|
200
|
+
const notes = polled ? readNotesAt(file, rev) : readNotes(file);
|
|
201
|
+
const rows = (all ? [...notes] : route ? notes.filter((n) => n.route === route) : notes.filter((n) => !n.done)).sort((a, b) => b.createdAt.localeCompare(a.createdAt)).map(toItem);
|
|
202
|
+
return json(200, polled ? { rev, notes: rows } : rows);
|
|
199
203
|
}
|
|
200
204
|
if (method === "DELETE") {
|
|
201
205
|
const id = String(query.id ?? "");
|
|
@@ -295,6 +299,22 @@ function handleNotesRequest(file, req) {
|
|
|
295
299
|
}
|
|
296
300
|
return json(405, { error: "method not allowed" });
|
|
297
301
|
}
|
|
302
|
+
var parsed = /* @__PURE__ */ new Map();
|
|
303
|
+
function readNotesAt(file, rev) {
|
|
304
|
+
const cached = parsed.get(file);
|
|
305
|
+
if (cached && cached.rev === rev && rev !== "none") return cached.notes;
|
|
306
|
+
const notes = readNotes(file);
|
|
307
|
+
parsed.set(file, { rev, notes });
|
|
308
|
+
return notes;
|
|
309
|
+
}
|
|
310
|
+
function notesRevision(file) {
|
|
311
|
+
try {
|
|
312
|
+
const stat = statSync(file, { bigint: true });
|
|
313
|
+
return `${stat.ino.toString(36)}.${stat.mtimeNs.toString(36)}.${stat.size.toString(36)}`;
|
|
314
|
+
} catch {
|
|
315
|
+
return "none";
|
|
316
|
+
}
|
|
317
|
+
}
|
|
298
318
|
function cap(v, max) {
|
|
299
319
|
return v.slice(0, max);
|
|
300
320
|
}
|
|
@@ -352,4 +372,4 @@ export {
|
|
|
352
372
|
notesResponse,
|
|
353
373
|
NOTES_ENDPOINT
|
|
354
374
|
};
|
|
355
|
-
//# sourceMappingURL=chunk-
|
|
375
|
+
//# sourceMappingURL=chunk-X6V2AKJL.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, statSync, 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 /** When an agent said it started on this note (`isotone --working <id>`).\n * Cleared when the note is closed or reopened. */\n workingAt?: 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 workingAt?: 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.workingAt && !n.done) item.workingAt = n.workingAt;\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 // `?rev=<last>` is the panel's poll. The revision comes from the file's\n // metadata, so an unchanged store answers without reading or parsing the\n // file, however many notes it holds or how many tabs are polling. Servers\n // older than this ignore the parameter and send the plain array, which the\n // client treats as changed.\n const polled = query.rev !== undefined;\n const rev = polled ? notesRevision(file) : '';\n if (polled && rev === String(query.rev)) return json(200, { rev, unchanged: true });\n const route = String(query.route ?? '');\n const all = query.all !== undefined && String(query.all) !== '' && String(query.all) !== '0';\n const notes = polled ? readNotesAt(file, rev) : readNotes(file);\n // `?all=1` lists every note on every route, done ones included. `?route=`\n // scopes to a page; neither means the open backlog, which is what the\n // panel shows and polls.\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, polled ? { rev, notes: rows } : 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\n// The last parse per file, keyed by revision: when the file changes, every\n// polling tab gets its answer from one read and one parse.\nconst parsed = new Map<string, { rev: string; notes: StoredNote[] }>();\nfunction readNotesAt(file: string, rev: string): StoredNote[] {\n const cached = parsed.get(file);\n if (cached && cached.rev === rev && rev !== 'none') return cached.notes;\n const notes = readNotes(file);\n parsed.set(file, { rev, notes });\n return notes;\n}\n\n/**\n * A cheap fingerprint of the notes file. Every store write replaces the file\n * by rename, which gives it a new inode; an in-place edit by hand changes the\n * nanosecond mtime or the size. A missing file has its own revision.\n */\nexport function notesRevision(file: string): string {\n try {\n const stat = statSync(file, { bigint: true });\n return `${stat.ino.toString(36)}.${stat.mtimeNs.toString(36)}.${stat.size.toString(36)}`;\n } catch {\n return 'none';\n }\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"]}
|
package/dist/cli.js
CHANGED
|
@@ -13,18 +13,18 @@ import {
|
|
|
13
13
|
syncVariant,
|
|
14
14
|
variantDir,
|
|
15
15
|
writeActive
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-UT2NLGBM.js";
|
|
17
17
|
import {
|
|
18
18
|
overlayGroups,
|
|
19
19
|
variantAppliesTo
|
|
20
20
|
} from "./chunk-OSETWKGE.js";
|
|
21
21
|
import {
|
|
22
22
|
checkUpdate
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-WMNR7KVD.js";
|
|
24
24
|
import {
|
|
25
25
|
readNotes,
|
|
26
26
|
updateNotes
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-X6V2AKJL.js";
|
|
28
28
|
import "./chunk-A4DX2V4C.js";
|
|
29
29
|
|
|
30
30
|
// src/cli/index.ts
|
|
@@ -352,9 +352,11 @@ function agentsParagraph(approach) {
|
|
|
352
352
|
AGENTS_MARKER,
|
|
353
353
|
"",
|
|
354
354
|
"Read open review notes with `npx isotone --json` at the start of a session.",
|
|
355
|
-
"
|
|
356
|
-
"`npx isotone --
|
|
357
|
-
"
|
|
355
|
+
"Several agents may share one dev server, so list only the page you are working",
|
|
356
|
+
"on: `npx isotone <route> --json`. For each note: run `npx isotone --working <id>`",
|
|
357
|
+
"(the panel shows it as in progress), reproduce it, fix it, verify it, then close",
|
|
358
|
+
"it with `npx isotone --done <id>` (each record\u2019s `doneCommand`). A note you fixed",
|
|
359
|
+
"but did not close stays open in the reviewer\u2019s panel.",
|
|
358
360
|
"Each record includes its route, viewport, pinned elements and optional variant.",
|
|
359
361
|
"A unique id prefix is enough for `--done`. Use `--all` to include",
|
|
360
362
|
"completed notes and `--days 30` for older feedback. Notes live in the local",
|
|
@@ -786,6 +788,7 @@ function printHelp(io) {
|
|
|
786
788
|
out(io, "Notes (the file is .iteration-feedback.json in the current directory)");
|
|
787
789
|
out(io, " isotone open notes, all routes");
|
|
788
790
|
out(io, " isotone <route> one route");
|
|
791
|
+
out(io, " isotone --working <id> tell the panel you started on a note (a,b for several)");
|
|
789
792
|
out(io, " isotone --done <id> mark addressed (an id prefix is enough)");
|
|
790
793
|
out(io, " isotone --undone <id> reopen");
|
|
791
794
|
out(io, " isotone --all include done notes");
|
|
@@ -861,9 +864,9 @@ async function runServe(args, io) {
|
|
|
861
864
|
function runNotes(argv, io) {
|
|
862
865
|
let routes;
|
|
863
866
|
try {
|
|
864
|
-
routes = parseArgs(argv, ["--days", "--file", "--done", "--undone"], ["--all", "--json"]);
|
|
867
|
+
routes = parseArgs(argv, ["--days", "--file", "--done", "--undone", "--working"], ["--all", "--json"]);
|
|
865
868
|
if (routes.length > 1) throw new Error("Give at most one route.");
|
|
866
|
-
if (has(argv, "--done")
|
|
869
|
+
if ([has(argv, "--done"), has(argv, "--undone"), has(argv, "--working")].filter(Boolean).length > 1) throw new Error("Use only one of --done, --undone and --working.");
|
|
867
870
|
} catch (error) {
|
|
868
871
|
errOut(io, error.message);
|
|
869
872
|
return 1;
|
|
@@ -880,6 +883,32 @@ function runNotes(argv, io) {
|
|
|
880
883
|
const route = routes[0];
|
|
881
884
|
const notes = readNotes(file);
|
|
882
885
|
const within = (iso, d) => Date.now() - new Date(iso).getTime() < d * 864e5;
|
|
886
|
+
const workingIdx = argv.indexOf("--working");
|
|
887
|
+
if (workingIdx >= 0) {
|
|
888
|
+
const prefixes = (argv[workingIdx + 1] ?? "").split(",").map((p) => p.trim()).filter(Boolean);
|
|
889
|
+
if (!prefixes.length) {
|
|
890
|
+
errOut(io, "Give the feedback id (prefix ok): isotone --working <id>");
|
|
891
|
+
return 1;
|
|
892
|
+
}
|
|
893
|
+
const claimed = [];
|
|
894
|
+
try {
|
|
895
|
+
updateNotes(file, (current) => {
|
|
896
|
+
claimed.length = 0;
|
|
897
|
+
for (const prefix of prefixes) {
|
|
898
|
+
const match = findNoteByPrefix(current, prefix);
|
|
899
|
+
match.workingAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
900
|
+
claimed.push(match);
|
|
901
|
+
}
|
|
902
|
+
return current;
|
|
903
|
+
});
|
|
904
|
+
} catch (err) {
|
|
905
|
+
errOut(io, err.message);
|
|
906
|
+
return 1;
|
|
907
|
+
}
|
|
908
|
+
if (has(argv, "--json")) out(io, JSON.stringify(claimed, null, 2));
|
|
909
|
+
else for (const match of claimed) out(io, `working: [${match.id.slice(0, 8)}] ${match.route} \u2014 ${match.feedback.slice(0, 60)}`);
|
|
910
|
+
return 0;
|
|
911
|
+
}
|
|
883
912
|
const flipIdx = doneIdx >= 0 ? doneIdx : undoneIdx;
|
|
884
913
|
if (flipIdx >= 0) {
|
|
885
914
|
const prefix = argv[flipIdx + 1];
|
|
@@ -893,6 +922,7 @@ function runNotes(argv, io) {
|
|
|
893
922
|
match = findNoteByPrefix(current, prefix);
|
|
894
923
|
match.done = doneIdx >= 0;
|
|
895
924
|
match.doneAt = match.done ? (/* @__PURE__ */ new Date()).toISOString() : null;
|
|
925
|
+
delete match.workingAt;
|
|
896
926
|
return current;
|
|
897
927
|
});
|
|
898
928
|
} catch (err) {
|
|
@@ -932,7 +962,7 @@ function runNotes(argv, io) {
|
|
|
932
962
|
}
|
|
933
963
|
const when = n.createdAt.slice(0, 16).replace("T", " ");
|
|
934
964
|
const at = n.viewport ? ` \xB7 ${n.viewport.w}\xD7${n.viewport.h}` : "";
|
|
935
|
-
const done = n.done ? " \u2713done" : "";
|
|
965
|
+
const done = n.done ? " \u2713done" : n.workingAt ? " \u2026working" : "";
|
|
936
966
|
out(io, `- [${n.id.slice(0, 8)}] (${when}${at})${done}`);
|
|
937
967
|
for (const element of n.elements ?? (n.element ? [n.element] : [])) {
|
|
938
968
|
const label = element.text ? `"${element.text}"` : "(no text)";
|
|
@@ -948,7 +978,7 @@ ${rows.length} iteration note(s).`);
|
|
|
948
978
|
if (rows.some((n) => !n.done)) out(io, DONE_HINT);
|
|
949
979
|
return 0;
|
|
950
980
|
}
|
|
951
|
-
var DONE_HINT = "After you fix and verify
|
|
981
|
+
var DONE_HINT = "When you start a note: npx isotone --working <id>. After you fix and verify it, close it: npx isotone --done <id>";
|
|
952
982
|
function doneCommand(id) {
|
|
953
983
|
return `npx isotone --done ${id}`;
|
|
954
984
|
}
|