isotone 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/{chunk-BJ4UI73I.js → chunk-O6WJ5CTR.js} +2 -2
- package/dist/{chunk-BJ4UI73I.js.map → chunk-O6WJ5CTR.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.js +83 -24
- package/dist/index.js.map +1 -1
- package/dist/isotone.global.js +15 -16
- package/dist/serve.js +1 -1
- package/dist/vite-plugin.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.0.2
|
|
6
|
+
|
|
7
|
+
- feat: polish the variants dialog, select mode and feedback field (#7)
|
|
8
|
+
|
|
9
|
+
## 1.0.1
|
|
10
|
+
|
|
11
|
+
- ci: tag successful npm publishes before registry propagation (#6)
|
|
12
|
+
|
|
5
13
|
## 1.0.0
|
|
6
14
|
|
|
7
15
|
- Put host-project setup and feedback handling first in the agent guide, and
|
|
@@ -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.2";
|
|
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-O6WJ5CTR.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.0\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"description\": \"Local feedback and design variants for your running web app. Pin elements, leave notes, and give your coding agent the context to make the change.\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"exports\": {\n \"./react\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./core\": {\n \"types\": \"./dist/core.d.ts\",\n \"import\": \"./dist/core.js\"\n },\n \"./server\": {\n \"types\": \"./dist/server.d.ts\",\n \"import\": \"./dist/server.js\"\n },\n \"./serve\": {\n \"types\": \"./dist/serve.d.ts\",\n \"import\": \"./dist/serve.js\"\n },\n \"./vite\": {\n \"types\": \"./dist/vite-plugin.d.ts\",\n \"import\": \"./dist/vite-plugin.js\"\n },\n \"./cli\": {\n \"types\": \"./dist/cli.d.ts\",\n \"import\": \"./dist/cli.js\"\n }\n },\n \"files\": [\n \"dist\",\n \"docs/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.2\",\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":[]}
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -45,7 +45,7 @@ function showOverflowTitle(event) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// src/react/IterationPanel.tsx
|
|
48
|
-
import { useCallback, useEffect as useEffect3, useId as useId2, useLayoutEffect as
|
|
48
|
+
import { useCallback, useEffect as useEffect3, useId as useId2, useLayoutEffect as useLayoutEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState3 } from "react";
|
|
49
49
|
|
|
50
50
|
// src/react/appearance.ts
|
|
51
51
|
var darkPalette = {
|
|
@@ -788,14 +788,14 @@ ${APPEARANCE_CSS}
|
|
|
788
788
|
.isotone-root .isotone-vars > .isotone-panel-head:has(.isotone-save) { position: relative; z-index: 1; margin-top: calc(0px - var(--iso-space)); background: linear-gradient(to bottom, transparent, var(--iso-bg) var(--iso-space)); }
|
|
789
789
|
.isotone-root .isotone-vars > .isotone-panel-head:not(:has(.isotone-save)) { display: none; }
|
|
790
790
|
.isotone-root .isotone-vars > .isotone-panel-head :is(h2, .isotone-icon) { display: none; }
|
|
791
|
-
.isotone-root .isotone-axis-menu { top: 6px;
|
|
791
|
+
.isotone-root .isotone-axis-menu { top: var(--iso-menu-top, 6px); max-height: calc(100% - var(--iso-menu-top, 6px) - 6px); }
|
|
792
792
|
.isotone-root .isotone-save-state { top: auto; bottom: 27px; }
|
|
793
793
|
.isotone-panel-head h2 { font-size: 12px; font-weight: 600; letter-spacing: -.01em; }
|
|
794
794
|
.isotone-head-actions { display: flex; align-items: center; gap: 6px; margin-right: auto; }
|
|
795
795
|
.isotone-icon { width: 28px; height: 28px; display: inline-grid; place-items: center; flex: none; color: var(--iso-muted); border-radius: 6px; }
|
|
796
796
|
.isotone-count { margin-left: 5px; color: var(--iso-muted); font-size: 10px; font-variant-numeric: tabular-nums; }
|
|
797
797
|
.isotone-vars { flex: 1; display: flex; flex-direction: column; min-height: 0; position: relative; }
|
|
798
|
-
.isotone-
|
|
798
|
+
.isotone-vars { min-height: min(var(--iso-menu-need, 0px), calc(100dvh - 76px)); }
|
|
799
799
|
.isotone-vars-body { flex: 1; overflow-y: auto; min-height: 0; overscroll-behavior: contain; padding: var(--iso-space); display: flex; flex-direction: column; gap: var(--iso-space); }
|
|
800
800
|
/* The footer overlaps only the scroller's bottom padding: content fades beneath
|
|
801
801
|
it while scrolling, but the last control remains fully visible at the end. */
|
|
@@ -863,7 +863,7 @@ ${PILL_ACTIVE_CSS}
|
|
|
863
863
|
Cancelling margin/padding keeps the first row in place at scroll start; the
|
|
864
864
|
expanded height preserves the content allowance and composer position.
|
|
865
865
|
The side gutters contain the corner action and keyboard focus outlines. */
|
|
866
|
-
.isotone-list { display: flex; flex-direction: column; gap: 0; flex: 0 1 auto; max-height: calc(var(--iso-list-height) + 2 * var(--iso-space) - 8px); min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: calc(0px - var(--iso-space)) calc(-8px - var(--iso-note-outset)); padding: var(--iso-space) 8px; scroll-padding-block: var(--iso-space); mask-image: linear-gradient(to bottom, transparent, #000 var(--iso-space), #000 calc(100% - var(--iso-space)), transparent); }
|
|
866
|
+
.isotone-list { display: flex; flex-direction: column; gap: 0; flex: 0 1 auto; max-height: calc(var(--iso-list-height) + 2 * var(--iso-space) - 8px); min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: calc(0px - var(--iso-space)) calc(-8px - var(--iso-note-outset)) calc(-2px - var(--iso-space)); padding: var(--iso-space) 8px; scroll-padding-block: var(--iso-space); mask-image: linear-gradient(to bottom, transparent, #000 var(--iso-space), #000 calc(100% - var(--iso-space)), transparent); }
|
|
867
867
|
.isotone-empty { color: var(--iso-muted); font-size: 11px; line-height: 1.7; padding: 8px; }
|
|
868
868
|
.isotone-note { position: relative; flex: none; }
|
|
869
869
|
.isotone-request-heading { display: flex; align-items: center; gap: 8px; line-height: 14px; }
|
|
@@ -895,14 +895,13 @@ ${FEEDBACK_LAYOUT_CSS}
|
|
|
895
895
|
.isotone-context > .isotone-chip:not(:only-child) { max-width: calc(100% - 32px); }
|
|
896
896
|
.isotone-chip > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
897
897
|
.isotone-chip button { display: grid; place-items: center; flex: none; width: 16px; height: 16px; border-radius: 3px; background: transparent; box-shadow: none; }
|
|
898
|
-
.isotone-root .isotone-textarea { --iso-textarea-inset: 8px; display: block; width: 100%; flex: none; height:
|
|
898
|
+
.isotone-root .isotone-textarea { --iso-textarea-inset: 8px; display: block; width: 100%; flex: none; height: 22px; min-height: calc(22px + var(--iso-textarea-inset)); max-height: calc(120px + var(--iso-textarea-inset)); overflow-y: auto; resize: none; margin-top: calc(0px - var(--iso-textarea-inset)); padding: calc(var(--iso-textarea-inset) + 1px) 1px 1px; scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--iso-fg) 14%, transparent) transparent; color: var(--iso-fg); background: transparent; border: 0; border-radius: 0; font-size: 12px; line-height: 20px; box-shadow: none; cursor: text; }
|
|
899
899
|
/* Text scrolls through the gap below tags, or the field inset without tags. */
|
|
900
900
|
.isotone-root .isotone-context:empty + .isotone-textarea { --iso-textarea-inset: var(--iso-input-padding); }
|
|
901
901
|
.isotone-textarea::placeholder { color: var(--iso-faint); }
|
|
902
902
|
.isotone-root .isotone-textarea:focus-visible { outline: none; }
|
|
903
903
|
.isotone-textarea[data-scrolled='true'] { mask-image: linear-gradient(to bottom, transparent, #000 8px); }
|
|
904
904
|
.isotone-toolbar { display: flex; flex: none; align-items: center; gap: 7px; height: 28px; }
|
|
905
|
-
.isotone-key-hint { color: var(--iso-faint); font-size: 9px; }
|
|
906
905
|
.isotone-pick[aria-pressed='true'] { color: var(--iso-fg); }
|
|
907
906
|
.isotone-pick, .isotone-send { position: relative; width: 24px; height: 24px; }
|
|
908
907
|
/* Keep a 28px hit area around the quieter composer faces. */
|
|
@@ -949,7 +948,7 @@ function ensurePanelStyles(target) {
|
|
|
949
948
|
}
|
|
950
949
|
|
|
951
950
|
// src/react/VariantsView.tsx
|
|
952
|
-
import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef3, useState as useState2 } from "react";
|
|
951
|
+
import { useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef as useRef3, useState as useState2 } from "react";
|
|
953
952
|
|
|
954
953
|
// src/react/useLayoutMotion.ts
|
|
955
954
|
import { useLayoutEffect, useRef as useRef2 } from "react";
|
|
@@ -1084,6 +1083,45 @@ function VariantsView({
|
|
|
1084
1083
|
useEffect2(() => {
|
|
1085
1084
|
if (!menu) setMenuOpen(false);
|
|
1086
1085
|
}, [menu]);
|
|
1086
|
+
useLayoutEffect2(() => {
|
|
1087
|
+
const root = bodyRef.current?.parentElement;
|
|
1088
|
+
const dialog = menuRef.current;
|
|
1089
|
+
const scroll = dialog?.querySelector(".isotone-menu-scroll");
|
|
1090
|
+
if (!root) return;
|
|
1091
|
+
const clear = () => {
|
|
1092
|
+
root.style.removeProperty("--iso-menu-need");
|
|
1093
|
+
root.style.removeProperty("--iso-menu-top");
|
|
1094
|
+
};
|
|
1095
|
+
if (!menuOpen || !dialog || !scroll) {
|
|
1096
|
+
clear();
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const place = () => {
|
|
1100
|
+
const natural = dialog.offsetHeight + scroll.scrollHeight - scroll.clientHeight;
|
|
1101
|
+
root.style.setProperty("--iso-menu-need", `${natural + 12}px`);
|
|
1102
|
+
const heading = trigger.current?.isConnected ? trigger.current.getBoundingClientRect().top : 0;
|
|
1103
|
+
const top = heading - root.getBoundingClientRect().top - natural - 6;
|
|
1104
|
+
root.style.setProperty("--iso-menu-top", `${Number.isFinite(top) && top >= 6 ? top : 6}px`);
|
|
1105
|
+
};
|
|
1106
|
+
place();
|
|
1107
|
+
window.addEventListener("resize", place);
|
|
1108
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(place);
|
|
1109
|
+
observer?.observe(scroll);
|
|
1110
|
+
return () => {
|
|
1111
|
+
window.removeEventListener("resize", place);
|
|
1112
|
+
observer?.disconnect();
|
|
1113
|
+
clear();
|
|
1114
|
+
};
|
|
1115
|
+
}, [menuOpen, menu, menuOptions.length, error]);
|
|
1116
|
+
useEffect2(() => {
|
|
1117
|
+
if (!menuOpen) return;
|
|
1118
|
+
const onPointerDown = (e) => {
|
|
1119
|
+
const dialog = menuRef.current;
|
|
1120
|
+
if (dialog && !e.composedPath().includes(dialog)) closeMenu();
|
|
1121
|
+
};
|
|
1122
|
+
window.addEventListener("pointerdown", onPointerDown, true);
|
|
1123
|
+
return () => window.removeEventListener("pointerdown", onPointerDown, true);
|
|
1124
|
+
}, [menuOpen]);
|
|
1087
1125
|
useEffect2(() => {
|
|
1088
1126
|
if (menuOpen)
|
|
1089
1127
|
menuRef.current?.querySelector("button")?.focus();
|
|
@@ -1145,7 +1183,7 @@ function VariantsView({
|
|
|
1145
1183
|
node?.toggleAttribute("inert", menuOpen);
|
|
1146
1184
|
}, children: [
|
|
1147
1185
|
sourceError && /* @__PURE__ */ jsx3("p", { className: "isotone-danger", role: "alert", children: sourceError }),
|
|
1148
|
-
shown.length === 0 && /* @__PURE__ */ jsx3("p", { className: "isotone-empty", children: "No
|
|
1186
|
+
shown.length === 0 && /* @__PURE__ */ jsx3("p", { className: "isotone-empty", children: "No variants on this page yet. Ask your agent to create variants of any design and they will appear here." }),
|
|
1149
1187
|
shown.map((axis) => {
|
|
1150
1188
|
const thumbnails = axis.kind !== "range" && axis.kind !== "color" && axis.options.length > 0 && axis.options.every((option) => option.image);
|
|
1151
1189
|
return /* @__PURE__ */ jsxs2(
|
|
@@ -1598,9 +1636,9 @@ function NoteSummary({ note }) {
|
|
|
1598
1636
|
}
|
|
1599
1637
|
|
|
1600
1638
|
// src/react/useDockMotion.ts
|
|
1601
|
-
import { useLayoutEffect as
|
|
1639
|
+
import { useLayoutEffect as useLayoutEffect3 } from "react";
|
|
1602
1640
|
function useDockMotion(ref, visible) {
|
|
1603
|
-
|
|
1641
|
+
useLayoutEffect3(() => {
|
|
1604
1642
|
const root = ref.current;
|
|
1605
1643
|
if (!root || !visible) return;
|
|
1606
1644
|
const previous = /* @__PURE__ */ new Map();
|
|
@@ -1734,7 +1772,6 @@ function IterationPanel({
|
|
|
1734
1772
|
const pendingWrites = useRef4(0);
|
|
1735
1773
|
const noteRequest = useRef4(0);
|
|
1736
1774
|
const [editing, setEditing] = useState3(null);
|
|
1737
|
-
const [unsynced, setUnsynced] = useState3(false);
|
|
1738
1775
|
const [notice, setNotice] = useState3("");
|
|
1739
1776
|
const [theme, setTheme] = useState3(() => detectTheme(themePreference));
|
|
1740
1777
|
const [instant, setInstant] = useState3(false);
|
|
@@ -1755,13 +1792,13 @@ function IterationPanel({
|
|
|
1755
1792
|
variants && visibleAxes(variants.getAxes(), variants.getSelections()).length
|
|
1756
1793
|
);
|
|
1757
1794
|
const visible = isRouteVisible(route, rule) && (notesAvailable || variantsAvailable || variantsOpen);
|
|
1758
|
-
const hasContext = Boolean(editing || variantTarget || targets.length || notice
|
|
1759
|
-
|
|
1795
|
+
const hasContext = Boolean(editing || variantTarget || targets.length || notice);
|
|
1796
|
+
useLayoutEffect4(() => {
|
|
1760
1797
|
const field = textareaRef.current;
|
|
1761
1798
|
if (!field || !notesOpen || !visible) return;
|
|
1762
1799
|
const resize = () => {
|
|
1763
1800
|
field.style.height = "0px";
|
|
1764
|
-
field.style.height = `${text ? Math.max(
|
|
1801
|
+
field.style.height = `${text ? Math.max(22, field.scrollHeight) : 22}px`;
|
|
1765
1802
|
field.dataset.scrolled = String(field.scrollTop > 0);
|
|
1766
1803
|
};
|
|
1767
1804
|
resize();
|
|
@@ -1827,7 +1864,6 @@ function IterationPanel({
|
|
|
1827
1864
|
void store.listNotes().then((fresh) => {
|
|
1828
1865
|
if (request !== noteRequest.current || revision !== noteRevision.current || pendingWrites.current) return;
|
|
1829
1866
|
setNotes(fresh);
|
|
1830
|
-
setUnsynced(store.hasUnsyncedNotes);
|
|
1831
1867
|
}).catch(() => {
|
|
1832
1868
|
});
|
|
1833
1869
|
}
|
|
@@ -1839,6 +1875,15 @@ function IterationPanel({
|
|
|
1839
1875
|
pendingWrites.current--;
|
|
1840
1876
|
};
|
|
1841
1877
|
}
|
|
1878
|
+
const keepPicking = useRef4(false);
|
|
1879
|
+
const focusWhilePicking = useCallback(() => {
|
|
1880
|
+
cancelAnimationFrame(focusFrame.current);
|
|
1881
|
+
focusFrame.current = requestAnimationFrame(() => {
|
|
1882
|
+
keepPicking.current = true;
|
|
1883
|
+
textareaRef.current?.focus();
|
|
1884
|
+
keepPicking.current = false;
|
|
1885
|
+
});
|
|
1886
|
+
}, []);
|
|
1842
1887
|
const focusComposer = useCallback(() => {
|
|
1843
1888
|
cancelAnimationFrame(focusFrame.current);
|
|
1844
1889
|
focusFrame.current = requestAnimationFrame(
|
|
@@ -1894,7 +1939,6 @@ function IterationPanel({
|
|
|
1894
1939
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1895
1940
|
...context
|
|
1896
1941
|
}]);
|
|
1897
|
-
setUnsynced(store.hasUnsyncedNotes);
|
|
1898
1942
|
setNotesOpen(true);
|
|
1899
1943
|
} finally {
|
|
1900
1944
|
finish();
|
|
@@ -1949,7 +1993,6 @@ function IterationPanel({
|
|
|
1949
1993
|
setNotes(
|
|
1950
1994
|
(prev) => prev.map((n) => n.id === tempId ? { ...n, id: savedId } : n)
|
|
1951
1995
|
);
|
|
1952
|
-
setUnsynced(store.hasUnsyncedNotes);
|
|
1953
1996
|
} catch {
|
|
1954
1997
|
setNotes(
|
|
1955
1998
|
(prev) => previous ? prev.map((n) => n.id === tempId ? previous : n) : prev.filter((n) => n.id !== tempId)
|
|
@@ -2038,7 +2081,7 @@ function IterationPanel({
|
|
|
2038
2081
|
const el = listRef.current;
|
|
2039
2082
|
if (el) el.scrollTop = el.scrollHeight;
|
|
2040
2083
|
}, [notes, notesOpen]);
|
|
2041
|
-
|
|
2084
|
+
useLayoutEffect4(() => {
|
|
2042
2085
|
if (!visible) return;
|
|
2043
2086
|
const root = rootRef.current?.getRootNode();
|
|
2044
2087
|
if (root instanceof ShadowRoot || root instanceof Document)
|
|
@@ -2098,8 +2141,9 @@ function IterationPanel({
|
|
|
2098
2141
|
};
|
|
2099
2142
|
const onMove = (e) => {
|
|
2100
2143
|
cancelAnimationFrame(frame);
|
|
2144
|
+
const deferred = e.shiftKey;
|
|
2101
2145
|
frame = requestAnimationFrame(() => {
|
|
2102
|
-
const el = elementAt(e);
|
|
2146
|
+
const el = deferred ? null : elementAt(e);
|
|
2103
2147
|
setHoverRect(el ? el.getBoundingClientRect() : null);
|
|
2104
2148
|
});
|
|
2105
2149
|
};
|
|
@@ -2110,11 +2154,13 @@ function IterationPanel({
|
|
|
2110
2154
|
);
|
|
2111
2155
|
};
|
|
2112
2156
|
const onClick = (e) => {
|
|
2157
|
+
if (e.shiftKey) return;
|
|
2113
2158
|
const el = elementAt(e);
|
|
2114
2159
|
if (!el) return;
|
|
2115
2160
|
e.preventDefault();
|
|
2116
2161
|
e.stopImmediatePropagation();
|
|
2117
2162
|
toggleTarget(el);
|
|
2163
|
+
focusWhilePicking();
|
|
2118
2164
|
};
|
|
2119
2165
|
const onKey = (e) => {
|
|
2120
2166
|
if (e.key === " ") {
|
|
@@ -2145,7 +2191,17 @@ function IterationPanel({
|
|
|
2145
2191
|
window.removeEventListener("click", onClick, true);
|
|
2146
2192
|
window.removeEventListener("keydown", onKey, true);
|
|
2147
2193
|
};
|
|
2148
|
-
}, [picking, focusComposer]);
|
|
2194
|
+
}, [picking, focusComposer, focusWhilePicking]);
|
|
2195
|
+
useEffect3(() => {
|
|
2196
|
+
const shadow = rootRef.current?.getRootNode();
|
|
2197
|
+
if (!(shadow instanceof ShadowRoot)) return;
|
|
2198
|
+
const stop = (e) => e.stopPropagation();
|
|
2199
|
+
const types = ["pointerdown", "pointerup", "mousedown", "mouseup", "touchstart", "touchend", "click"];
|
|
2200
|
+
for (const type of types) shadow.addEventListener(type, stop);
|
|
2201
|
+
return () => {
|
|
2202
|
+
for (const type of types) shadow.removeEventListener(type, stop);
|
|
2203
|
+
};
|
|
2204
|
+
}, []);
|
|
2149
2205
|
useEffect3(() => {
|
|
2150
2206
|
if (!picking) {
|
|
2151
2207
|
setRects([]);
|
|
@@ -2347,7 +2403,7 @@ function IterationPanel({
|
|
|
2347
2403
|
},
|
|
2348
2404
|
target.selector
|
|
2349
2405
|
)),
|
|
2350
|
-
|
|
2406
|
+
notice && /* @__PURE__ */ jsx5("p", { role: "status", className: "isotone-notice", children: notice })
|
|
2351
2407
|
] }),
|
|
2352
2408
|
/* @__PURE__ */ jsx5(
|
|
2353
2409
|
"textarea",
|
|
@@ -2356,7 +2412,10 @@ function IterationPanel({
|
|
|
2356
2412
|
value: text,
|
|
2357
2413
|
"aria-label": "Feedback for the agent",
|
|
2358
2414
|
onChange: (e) => setText(e.target.value),
|
|
2359
|
-
onFocus: () =>
|
|
2415
|
+
onFocus: () => {
|
|
2416
|
+
if (!keepPicking.current) setPicking(false);
|
|
2417
|
+
},
|
|
2418
|
+
onPointerDown: () => setPicking(false),
|
|
2360
2419
|
onScroll: (event) => {
|
|
2361
2420
|
event.currentTarget.dataset.scrolled = String(event.currentTarget.scrollTop > 0);
|
|
2362
2421
|
},
|
|
@@ -2380,6 +2439,7 @@ function IterationPanel({
|
|
|
2380
2439
|
"aria-label": "Select elements",
|
|
2381
2440
|
"aria-pressed": picking,
|
|
2382
2441
|
className: "isotone-pick isotone-icon",
|
|
2442
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
2383
2443
|
onClick: () => {
|
|
2384
2444
|
cancelAnimationFrame(focusFrame.current);
|
|
2385
2445
|
setPicking((v) => !v);
|
|
@@ -2387,7 +2447,6 @@ function IterationPanel({
|
|
|
2387
2447
|
children: /* @__PURE__ */ jsx5(Icon2, { name: "pin", size: 14 })
|
|
2388
2448
|
}
|
|
2389
2449
|
),
|
|
2390
|
-
picking && /* @__PURE__ */ jsx5("span", { className: "isotone-key-hint", children: "Click or Space to pin \xB7 Enter to finish" }),
|
|
2391
2450
|
/* @__PURE__ */ jsx5(
|
|
2392
2451
|
"button",
|
|
2393
2452
|
{
|
|
@@ -2425,7 +2484,7 @@ function IterationPanel({
|
|
|
2425
2484
|
picking && /* @__PURE__ */ jsxs4("div", { className: "isotone-hint", "data-isotone": true, children: [
|
|
2426
2485
|
/* @__PURE__ */ jsxs4("span", { children: [
|
|
2427
2486
|
targets.length,
|
|
2428
|
-
" selected \xB7 click to add or remove"
|
|
2487
|
+
" selected \xB7 click to add or remove \xB7 hold \u21E7 to use the page"
|
|
2429
2488
|
] }),
|
|
2430
2489
|
/* @__PURE__ */ jsxs4(
|
|
2431
2490
|
"button",
|