isotone 1.0.6 → 1.0.8
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 +54 -48
- 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-AP7O357L.js → chunk-X6V2AKJL.js} +25 -5
- package/dist/chunk-X6V2AKJL.js.map +1 -0
- package/dist/{chunk-DBIKWXBE.js → chunk-YFQXGHNT.js} +2 -2
- package/dist/{chunk-DBIKWXBE.js.map → chunk-YFQXGHNT.js.map} +1 -1
- 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 +204 -28
- package/dist/index.js.map +1 -1
- package/dist/isotone.global.js +52 -24
- 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/assets/preview.jpg +0 -0
- 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.8
|
|
6
|
+
|
|
7
|
+
- feat: close either panel from a corner button
|
|
8
|
+
|
|
9
|
+
## 1.0.7
|
|
10
|
+
|
|
11
|
+
- feat: live notes, page scope and agent progress in the panel (#12)
|
|
12
|
+
|
|
5
13
|
## 1.0.6
|
|
6
14
|
|
|
7
15
|
- fix: stop the launcher flickering when hovered at the count badge (#11)
|
package/README.md
CHANGED
|
@@ -1,14 +1,41 @@
|
|
|
1
1
|
# Isotone
|
|
2
2
|
|
|
3
|
-
Point at
|
|
3
|
+
Point at the part of your app you want changed, say what should change, and your
|
|
4
|
+
coding agent gets the exact element, page and screen size with your note.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
Notes stay in your project with the route, viewport, and element references your
|
|
7
|
-
agent needs. No account, database, or hosted service.
|
|
6
|
+

|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
## Why
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
Coding agents write UI fast, but telling one which piece of UI you mean is slow.
|
|
11
|
+
You type "the label above the number in the first stats card", the agent guesses
|
|
12
|
+
a component, and the next round goes to correcting the guess. Comparing two
|
|
13
|
+
design directions is worse: you ask for one, review it, ask for the other, and
|
|
14
|
+
try to remember what the first looked like.
|
|
15
|
+
|
|
16
|
+
Some agents let you select an element, Claude Code among them, but only inside
|
|
17
|
+
the browser they ship with. You lose your own browser with its sign-ins,
|
|
18
|
+
extensions, devtools and window sizes, and each selection lives in a single chat
|
|
19
|
+
message.
|
|
20
|
+
|
|
21
|
+
Isotone is a purpose-built tool for design engineering with a coding agent. It
|
|
22
|
+
runs in your dev server and adds a small panel to the page you already have open:
|
|
23
|
+
|
|
24
|
+
- Pin one or more elements and write a note. The note records the route, the
|
|
25
|
+
viewport, and a selector and the visible text of every pinned element.
|
|
26
|
+
- Keep a backlog. Notes go to a JSON file in your project, so you can keep
|
|
27
|
+
writing while the agent works through them.
|
|
28
|
+
- Follow progress. The agent marks a note as started and closes it when the
|
|
29
|
+
change is verified, and the panel updates without a reload.
|
|
30
|
+
- Compare variants. The agent can build two or three versions of a component.
|
|
31
|
+
You switch between them on the live page, comment on one, or keep the default.
|
|
32
|
+
- Use any agent. Agents read notes with `npx isotone --json`, so nothing depends
|
|
33
|
+
on a particular agent, editor or browser.
|
|
34
|
+
|
|
35
|
+
There is no account, database or hosted service, and nothing is added to
|
|
36
|
+
production builds.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
12
39
|
|
|
13
40
|
Run in the project you want to review:
|
|
14
41
|
|
|
@@ -17,14 +44,11 @@ npm install -D isotone
|
|
|
17
44
|
npx isotone init --yes
|
|
18
45
|
```
|
|
19
46
|
|
|
20
|
-
|
|
21
|
-
workflow to `AGENTS.md
|
|
22
|
-
the printed instructions.
|
|
23
|
-
|
|
24
|
-
Already using the previous package? Follow the [migration guide](docs/migration.md)
|
|
25
|
-
to keep your notes, variants, and project setup.
|
|
47
|
+
`init` detects your setup, prints the integration to add, and adds a short
|
|
48
|
+
workflow to `AGENTS.md` so your agent knows the loop. It does not edit app code.
|
|
26
49
|
|
|
27
|
-
For **Vite**, add the plugin
|
|
50
|
+
For **Vite**, add the plugin next to your existing plugins. It works with any
|
|
51
|
+
Vite framework:
|
|
28
52
|
|
|
29
53
|
```ts
|
|
30
54
|
import { defineConfig } from 'vite';
|
|
@@ -35,9 +59,6 @@ export default defineConfig({
|
|
|
35
59
|
});
|
|
36
60
|
```
|
|
37
61
|
|
|
38
|
-
Works with any Vite framework. The panel brings its own React runtime and is
|
|
39
|
-
only injected by the development server.
|
|
40
|
-
|
|
41
62
|
For **any other dev server**, run `npx isotone serve` and add this to your
|
|
42
63
|
development HTML:
|
|
43
64
|
|
|
@@ -45,48 +66,33 @@ development HTML:
|
|
|
45
66
|
<script src="http://127.0.0.1:4123/isotone.js" defer></script>
|
|
46
67
|
```
|
|
47
68
|
|
|
48
|
-
[
|
|
49
|
-
|
|
69
|
+
The [integration guide](docs/integrations.md) covers Next and other route
|
|
70
|
+
handlers, React embeds, themes and production guards. Node.js 20 or later is
|
|
71
|
+
required. Coming from the previous package? See the [migration guide](docs/migration.md).
|
|
50
72
|
|
|
51
73
|
## The loop
|
|
52
74
|
|
|
53
|
-
1. Open Feedback with **⌘/Ctrl + Shift +
|
|
54
|
-
2. Your agent reads `npx isotone --json`,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Click a saved note to edit it. **Enter** sends; **Shift + Enter** adds a newline.
|
|
60
|
-
While selecting elements, click to pin or Tab to a page control and press Space.
|
|
61
|
-
Enter or Escape finishes selection. The selected element text stays attached to
|
|
62
|
-
the note; closing the panel preserves your draft.
|
|
63
|
-
|
|
64
|
-
## Themes
|
|
65
|
-
|
|
66
|
-
The panel follows the rendered page by default. Set `theme` on the Vite plugin,
|
|
67
|
-
`mountIteration`, or `IterationPanel`:
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
iteration({ theme: 'auto' }); // page background; the default
|
|
71
|
-
iteration({ theme: 'system' }); // operating system preference
|
|
72
|
-
iteration({ theme: 'light' }); // explicit light, or 'dark'
|
|
73
|
-
```
|
|
75
|
+
1. Open Feedback with **⌘/Ctrl + Shift + .**, pin elements, write a note and send.
|
|
76
|
+
2. Your agent reads `npx isotone --json`, marks the note it starts with
|
|
77
|
+
`npx isotone --working <id>`, makes and checks the change, then closes it
|
|
78
|
+
with `npx isotone --done <id>`.
|
|
79
|
+
3. Review the result on the page. For alternatives, ask for
|
|
80
|
+
[variants](docs/variants.md) and switch between them in place.
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
The panel lists the current page's notes. When other pages have open notes,
|
|
83
|
+
page chips let you include them. Click a saved note to edit it. **Enter** sends
|
|
84
|
+
and **Shift + Enter** adds a newline. While selecting, click to pin, hold
|
|
85
|
+
**Shift** to use the page normally, and press Enter or Escape to finish.
|
|
79
86
|
|
|
80
87
|
## Updates
|
|
81
88
|
|
|
82
|
-
The dev server checks
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
content. Use `checkForUpdates: false` to opt out. [Update details](docs/updates.md).
|
|
89
|
+
The dev server checks npm for a newer version at most once a day and shows the
|
|
90
|
+
upgrade command in the panel. The check sends no project content and stays quiet
|
|
91
|
+
offline. Set `checkForUpdates: false` to turn it off. [Update details](docs/updates.md).
|
|
86
92
|
|
|
87
93
|
## For maintainers
|
|
88
94
|
|
|
89
95
|
[Development and contributing](https://github.com/aaroi/isotone/blob/main/docs/development.md)
|
|
90
|
-
covers the source, design language, checks
|
|
96
|
+
covers the source, design language, checks and releases.
|
|
91
97
|
|
|
92
98
|
MIT.
|
|
@@ -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
|
|
@@ -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"]}
|
|
@@ -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.8";
|
|
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-YFQXGHNT.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.6\",\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":[]}
|
|
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.8\",\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":[]}
|