isotone 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -1
- package/CHANGELOG.md +4 -0
- package/README.md +6 -2
- package/dist/{chunk-UYMMI2IH.js → chunk-JGIX4XEH.js} +21 -1
- package/dist/chunk-JGIX4XEH.js.map +1 -0
- package/dist/{chunk-DD6BKC3R.js → chunk-UT2NLGBM.js} +2 -2
- package/dist/{chunk-DBIKWXBE.js → chunk-WMNR7KVD.js} +2 -2
- package/dist/{chunk-DBIKWXBE.js.map → chunk-WMNR7KVD.js.map} +1 -1
- package/dist/{chunk-AP7O357L.js → chunk-X6V2AKJL.js} +25 -5
- package/dist/chunk-X6V2AKJL.js.map +1 -0
- package/dist/cli.js +40 -10
- package/dist/cli.js.map +1 -1
- package/dist/core.d.ts +6 -0
- package/dist/core.js +1 -1
- package/dist/index.js +141 -9
- package/dist/index.js.map +1 -1
- package/dist/isotone.global.js +32 -20
- package/dist/serve.js +2 -2
- package/dist/server.d.ts +3 -0
- package/dist/server.js +2 -2
- package/dist/vite-plugin.js +3 -3
- package/docs/integrations.md +14 -1
- package/package.json +1 -1
- package/dist/chunk-AP7O357L.js.map +0 -1
- package/dist/chunk-UYMMI2IH.js.map +0 -1
- /package/dist/{chunk-DD6BKC3R.js.map → chunk-UT2NLGBM.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
describeIterationElement,
|
|
5
5
|
elementAsNode,
|
|
6
6
|
isRouteVisible
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-JGIX4XEH.js";
|
|
8
8
|
import {
|
|
9
9
|
FEEDBACK_STORAGE_KEY,
|
|
10
10
|
KNOBS_STORAGE_KEY,
|
|
@@ -878,6 +878,17 @@ ${PILL_ACTIVE_CSS}
|
|
|
878
878
|
surrounds a 16px face without reserving trailing space in every row. */
|
|
879
879
|
.isotone-note > .isotone-x { position: absolute; right: -8px; top: -8px; width: 24px; height: 24px; isolation: isolate; opacity: .6; background: none; box-shadow: none; }
|
|
880
880
|
.isotone-x::before { content: ''; position: absolute; inset: 4px; border-radius: 50%; z-index: -1; background: var(--iso-bg); box-shadow: 0 0 0 1px var(--iso-edge); }
|
|
881
|
+
/* One chip per page that has open notes, this page first. */
|
|
882
|
+
.isotone-pages { display: flex; flex: none; min-width: 0; border-top: 0; border-bottom: 0; border-inline: 0; gap: 4px; overflow-x: auto; overscroll-behavior: contain; margin: 0 calc(0px - var(--iso-space)); padding: 0 var(--iso-space); }
|
|
883
|
+
.isotone-page { flex: 0 1 auto; display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 160px; height: 22px; padding: 0 7px; border-radius: var(--iso-control-radius); border: 1px solid var(--iso-edge); background: transparent; color: var(--iso-muted); font-size: 10px; }
|
|
884
|
+
.isotone-page > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
885
|
+
.isotone-page small { color: var(--iso-faint); font-size: 9px; font-variant-numeric: tabular-nums; }
|
|
886
|
+
.isotone-page[aria-pressed='true'] { color: var(--iso-fg); background: var(--iso-label-face); box-shadow: var(--iso-label-lift-shadow); }
|
|
887
|
+
/* An agent has said it is working on a note: a soft pulse on the note, and a ring on the launcher.
|
|
888
|
+
Both are driven by one shared clock (see syncPulse), so they beat together. */
|
|
889
|
+
.isotone-note-hit { position: relative; }
|
|
890
|
+
.isotone-working { position: absolute; left: 3px; top: 5px; width: 6px; height: 6px; border-radius: 50%; background: var(--iso-focus); }
|
|
891
|
+
.isotone-ring { position: absolute; inset: 0; border-radius: 999px; pointer-events: none; }
|
|
881
892
|
.isotone-list:empty { display: none; }
|
|
882
893
|
${FEEDBACK_LAYOUT_CSS}
|
|
883
894
|
.isotone-compose { flex: none; display: flex; flex-direction: column; }
|
|
@@ -924,6 +935,7 @@ ${FEEDBACK_LAYOUT_CSS}
|
|
|
924
935
|
}
|
|
925
936
|
@media (max-height: 580px) { .isotone-list { --iso-list-height: 72px; } .isotone-root .isotone-textarea { max-height: calc(64px + var(--iso-textarea-inset)); } }
|
|
926
937
|
@media (prefers-reduced-motion: reduce) {
|
|
938
|
+
.isotone-ring { box-shadow: 0 0 0 2px color-mix(in srgb, var(--iso-focus) 50%, transparent); }
|
|
927
939
|
.isotone-root .isotone-rail-track { transition: none; }
|
|
928
940
|
.isotone-root .isotone-panel, .isotone-root .isotone-axis-menu, .isotone-root .isotone-rail-tools { transition-property: opacity, visibility; }
|
|
929
941
|
.isotone-root button { transition: opacity 125ms ease; }
|
|
@@ -1611,10 +1623,11 @@ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-run
|
|
|
1611
1623
|
function SummaryTag({ label }) {
|
|
1612
1624
|
return /* @__PURE__ */ jsx4("span", { className: "isotone-chip", children: /* @__PURE__ */ jsx4("span", { onPointerOver: showOverflowTitle, children: label }) });
|
|
1613
1625
|
}
|
|
1614
|
-
function NoteSummary({ note }) {
|
|
1626
|
+
function NoteSummary({ note, page }) {
|
|
1615
1627
|
if (!note.feedback.startsWith("[set-defaults]")) {
|
|
1616
1628
|
const elements = note.elements ?? (note.element ? [note.element] : []);
|
|
1617
1629
|
const references = [
|
|
1630
|
+
...page ? [{ key: `page:${page}`, label: page }] : [],
|
|
1618
1631
|
...note.variant ? [{
|
|
1619
1632
|
key: `variant:${note.variant.axisId}`,
|
|
1620
1633
|
label: `${note.variant.label}${note.variant.optionLabel ? ` / ${note.variant.optionLabel}` : ""}`
|
|
@@ -1646,7 +1659,8 @@ function NoteSummary({ note }) {
|
|
|
1646
1659
|
}
|
|
1647
1660
|
return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1648
1661
|
/* @__PURE__ */ jsx4("p", { children: note.feedback }),
|
|
1649
|
-
tags.length > 0 && /* @__PURE__ */ jsxs3("span", { className: "isotone-meta", children: [
|
|
1662
|
+
(page || tags.length > 0) && /* @__PURE__ */ jsxs3("span", { className: "isotone-meta", children: [
|
|
1663
|
+
page && /* @__PURE__ */ jsx4(SummaryTag, { label: page }),
|
|
1650
1664
|
tags.slice(0, 3).map((tag) => /* @__PURE__ */ jsx4(SummaryTag, { label: `${tag.name} ${tag.value}` }, `${tag.name}-${tag.value}`)),
|
|
1651
1665
|
tags.length > 3 && /* @__PURE__ */ jsxs3("span", { className: "isotone-request-more", children: [
|
|
1652
1666
|
"+",
|
|
@@ -1757,6 +1771,33 @@ function useDockMotion(ref, visible) {
|
|
|
1757
1771
|
|
|
1758
1772
|
// src/react/IterationPanel.tsx
|
|
1759
1773
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1774
|
+
var PULSE_MS = 2e3;
|
|
1775
|
+
function syncPulse(spread) {
|
|
1776
|
+
return (node) => {
|
|
1777
|
+
if (!node || typeof node.animate !== "function") return;
|
|
1778
|
+
if (node.closest('[data-instant="true"]') || typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
1779
|
+
const tint = "color-mix(in srgb, var(--iso-focus) 55%, transparent)";
|
|
1780
|
+
const pulse = node.animate([
|
|
1781
|
+
{ boxShadow: `0 0 0 0 ${tint}`, offset: 0 },
|
|
1782
|
+
{ boxShadow: `0 0 0 ${spread}px transparent`, offset: 0.7 },
|
|
1783
|
+
{ boxShadow: `0 0 0 ${spread}px transparent`, offset: 1 }
|
|
1784
|
+
], { duration: PULSE_MS, iterations: Infinity, easing: "ease-out" });
|
|
1785
|
+
pulse.startTime = 0;
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
var dotPulse = syncPulse(6);
|
|
1789
|
+
var ringPulse = syncPulse(8);
|
|
1790
|
+
var POLL_MS = 2500;
|
|
1791
|
+
var POLL_MAX_MS = 3e4;
|
|
1792
|
+
function sameNotes(a, b) {
|
|
1793
|
+
if (a.length !== b.length) return false;
|
|
1794
|
+
const byId = new Map(a.map((note) => [note.id, note]));
|
|
1795
|
+
return b.every((note) => {
|
|
1796
|
+
const old = byId.get(note.id);
|
|
1797
|
+
return old !== void 0 && JSON.stringify(old) === JSON.stringify(note);
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
var WORKING_FRESH_MS = 20 * 60 * 1e3;
|
|
1760
1801
|
var DEFAULT_SHORTCUT = {
|
|
1761
1802
|
code: "Period",
|
|
1762
1803
|
mod: true,
|
|
@@ -1789,6 +1830,9 @@ function IterationPanel({
|
|
|
1789
1830
|
const [rects, setRects] = useState3([]);
|
|
1790
1831
|
const [text, setText] = useState3("");
|
|
1791
1832
|
const [notes, setNotes] = useState3([]);
|
|
1833
|
+
const [scope, setScope] = useState3(null);
|
|
1834
|
+
const [clock, setClock] = useState3(() => Math.floor(Date.now() / 6e4) * 6e4);
|
|
1835
|
+
const pollRev = useRef4(null);
|
|
1792
1836
|
const noteRevision = useRef4(0);
|
|
1793
1837
|
const pendingWrites = useRef4(0);
|
|
1794
1838
|
const noteRequest = useRef4(0);
|
|
@@ -1870,7 +1914,25 @@ function IterationPanel({
|
|
|
1870
1914
|
if (active === fabRef.current) rootRef.current?.querySelector(".isotone-rail-button")?.focus();
|
|
1871
1915
|
}, [railOpen, instant]);
|
|
1872
1916
|
const draftKey = `${nsKey}::draft::${route}`;
|
|
1873
|
-
const
|
|
1917
|
+
const openNotes = useMemo3(() => notes.filter((n) => !n.done), [notes]);
|
|
1918
|
+
const pages = useMemo3(() => {
|
|
1919
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1920
|
+
for (const n of openNotes) counts.set(n.route, (counts.get(n.route) ?? 0) + 1);
|
|
1921
|
+
const others = [...counts.keys()].filter((r) => r !== route).sort();
|
|
1922
|
+
return { counts, others };
|
|
1923
|
+
}, [openNotes, route]);
|
|
1924
|
+
const selectedPages = useMemo3(() => {
|
|
1925
|
+
const chosen = (scope ?? [route]).filter((r) => r === route || pages.counts.has(r));
|
|
1926
|
+
return new Set(chosen.length ? chosen : [route]);
|
|
1927
|
+
}, [scope, route, pages]);
|
|
1928
|
+
const visibleNotes = useMemo3(() => openNotes.filter((n) => selectedPages.has(n.route)).sort((a, b) => a.createdAt.localeCompare(b.createdAt)), [openNotes, selectedPages]);
|
|
1929
|
+
const working = useMemo3(() => new Set(visibleNotes.filter((n) => n.workingAt && clock - Date.parse(n.workingAt) < WORKING_FRESH_MS).map((n) => n.id)), [visibleNotes, clock]);
|
|
1930
|
+
function togglePage(page) {
|
|
1931
|
+
const next = new Set(selectedPages);
|
|
1932
|
+
if (next.has(page)) next.delete(page);
|
|
1933
|
+
else next.add(page);
|
|
1934
|
+
setScope(next.size ? [...next] : [route]);
|
|
1935
|
+
}
|
|
1874
1936
|
const pendingActions = useMemo3(() => notes.flatMap((n) => !n.done && n.route === route && n.variantAction ? [n.variantAction] : []), [notes, route]);
|
|
1875
1937
|
const shortcutLabel = shortcut ? [
|
|
1876
1938
|
shortcut.mod ? "\u2318/Ctrl" : "",
|
|
@@ -1882,8 +1944,9 @@ function IterationPanel({
|
|
|
1882
1944
|
if (!notesAvailable) return;
|
|
1883
1945
|
const request = ++noteRequest.current;
|
|
1884
1946
|
const revision = noteRevision.current;
|
|
1885
|
-
void store.
|
|
1947
|
+
void store.pollNotes(null).then((result) => ({ rev: result.rev, notes: result.notes ?? [] })).catch(() => store.pendingNotes().then((fresh) => ({ rev: null, notes: fresh }))).then(({ rev, notes: fresh }) => {
|
|
1886
1948
|
if (request !== noteRequest.current || revision !== noteRevision.current || pendingWrites.current) return;
|
|
1949
|
+
pollRev.current = rev;
|
|
1887
1950
|
setNotes(fresh);
|
|
1888
1951
|
}).catch(() => {
|
|
1889
1952
|
});
|
|
@@ -2050,6 +2113,55 @@ function IterationPanel({
|
|
|
2050
2113
|
useEffect3(() => {
|
|
2051
2114
|
loadNotes();
|
|
2052
2115
|
}, [notesOpen, variantsOpen, route]);
|
|
2116
|
+
useEffect3(() => {
|
|
2117
|
+
if (!notesAvailable || !visible) return;
|
|
2118
|
+
let stopped = false;
|
|
2119
|
+
let running = false;
|
|
2120
|
+
let failures = 0;
|
|
2121
|
+
let timer;
|
|
2122
|
+
const schedule = () => {
|
|
2123
|
+
clearTimeout(timer);
|
|
2124
|
+
if (stopped || document.hidden) return;
|
|
2125
|
+
timer = setTimeout(() => void tick(), Math.min(POLL_MS * 2 ** failures, POLL_MAX_MS));
|
|
2126
|
+
};
|
|
2127
|
+
const tick = async () => {
|
|
2128
|
+
if (stopped || running || document.hidden) return;
|
|
2129
|
+
running = true;
|
|
2130
|
+
const request = noteRequest.current;
|
|
2131
|
+
const revision = noteRevision.current;
|
|
2132
|
+
try {
|
|
2133
|
+
const result = await store.pollNotes(pollRev.current);
|
|
2134
|
+
failures = 0;
|
|
2135
|
+
if (stopped || request !== noteRequest.current || revision !== noteRevision.current || pendingWrites.current) return;
|
|
2136
|
+
pollRev.current = result.rev;
|
|
2137
|
+
const fresh = result.notes;
|
|
2138
|
+
if (fresh) setNotes((prev) => sameNotes(prev, fresh) ? prev : fresh);
|
|
2139
|
+
setClock(Math.floor(Date.now() / 6e4) * 6e4);
|
|
2140
|
+
} catch {
|
|
2141
|
+
failures = Math.min(failures + 1, 8);
|
|
2142
|
+
} finally {
|
|
2143
|
+
running = false;
|
|
2144
|
+
schedule();
|
|
2145
|
+
}
|
|
2146
|
+
};
|
|
2147
|
+
const wake = () => {
|
|
2148
|
+
if (document.hidden) {
|
|
2149
|
+
clearTimeout(timer);
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
failures = 0;
|
|
2153
|
+
void tick();
|
|
2154
|
+
};
|
|
2155
|
+
schedule();
|
|
2156
|
+
document.addEventListener("visibilitychange", wake);
|
|
2157
|
+
window.addEventListener("focus", wake);
|
|
2158
|
+
return () => {
|
|
2159
|
+
stopped = true;
|
|
2160
|
+
clearTimeout(timer);
|
|
2161
|
+
document.removeEventListener("visibilitychange", wake);
|
|
2162
|
+
window.removeEventListener("focus", wake);
|
|
2163
|
+
};
|
|
2164
|
+
}, [notesAvailable, visible, store]);
|
|
2053
2165
|
useEffect3(() => {
|
|
2054
2166
|
if (variants && notesOpen && !variantsOpen) refreshSource(variants);
|
|
2055
2167
|
}, [notesOpen, variantsOpen, variants]);
|
|
@@ -2217,7 +2329,7 @@ function IterationPanel({
|
|
|
2217
2329
|
const shadow = rootRef.current?.getRootNode();
|
|
2218
2330
|
if (!(shadow instanceof ShadowRoot)) return;
|
|
2219
2331
|
const stop = (e) => e.stopPropagation();
|
|
2220
|
-
const types = ["pointerdown", "
|
|
2332
|
+
const types = ["pointerdown", "mousedown", "touchstart", "click"];
|
|
2221
2333
|
for (const type of types) shadow.addEventListener(type, stop);
|
|
2222
2334
|
return () => {
|
|
2223
2335
|
for (const type of types) shadow.removeEventListener(type, stop);
|
|
@@ -2328,20 +2440,38 @@ function IterationPanel({
|
|
|
2328
2440
|
}
|
|
2329
2441
|
)
|
|
2330
2442
|
] }),
|
|
2443
|
+
pages.counts.size > 1 && /* @__PURE__ */ jsx5("fieldset", { className: "isotone-pages", "aria-label": "Pages", children: [route, ...pages.others].map((page) => /* @__PURE__ */ jsxs4(
|
|
2444
|
+
"button",
|
|
2445
|
+
{
|
|
2446
|
+
type: "button",
|
|
2447
|
+
className: "isotone-page",
|
|
2448
|
+
"aria-pressed": selectedPages.has(page),
|
|
2449
|
+
title: page === route ? `This page \xB7 ${page}` : page,
|
|
2450
|
+
onClick: () => togglePage(page),
|
|
2451
|
+
children: [
|
|
2452
|
+
/* @__PURE__ */ jsx5("span", { onPointerOver: showOverflowTitle, children: page === route ? "This page" : page }),
|
|
2453
|
+
/* @__PURE__ */ jsx5("small", { children: pages.counts.get(page) ?? 0 })
|
|
2454
|
+
]
|
|
2455
|
+
},
|
|
2456
|
+
page
|
|
2457
|
+
)) }),
|
|
2331
2458
|
/* @__PURE__ */ jsx5("ul", { ref: listRef, className: "isotone-list", children: visibleNotes.map((note) => /* @__PURE__ */ jsxs4(
|
|
2332
2459
|
"li",
|
|
2333
2460
|
{
|
|
2334
2461
|
"data-motion-key": note.id,
|
|
2335
2462
|
className: `isotone-note${editing?.id === note.id ? " isotone-note-editing" : ""}`,
|
|
2336
2463
|
children: [
|
|
2337
|
-
/* @__PURE__ */
|
|
2464
|
+
/* @__PURE__ */ jsxs4(
|
|
2338
2465
|
"button",
|
|
2339
2466
|
{
|
|
2340
2467
|
type: "button",
|
|
2341
2468
|
className: "isotone-note-hit",
|
|
2342
2469
|
"aria-current": editing?.id === note.id ? true : void 0,
|
|
2343
2470
|
onClick: () => beginEdit(note),
|
|
2344
|
-
children:
|
|
2471
|
+
children: [
|
|
2472
|
+
working.has(note.id) && /* @__PURE__ */ jsx5("span", { className: "isotone-working", ref: dotPulse, role: "img", "aria-label": "An agent is working on this", title: "An agent is working on this" }),
|
|
2473
|
+
/* @__PURE__ */ jsx5(NoteSummary, { note, page: note.route !== route ? note.route : void 0 })
|
|
2474
|
+
]
|
|
2345
2475
|
}
|
|
2346
2476
|
),
|
|
2347
2477
|
/* @__PURE__ */ jsx5(
|
|
@@ -2498,6 +2628,7 @@ function IterationPanel({
|
|
|
2498
2628
|
"data-tools": Number(Boolean(variants)) + Number(notesAvailable),
|
|
2499
2629
|
"data-instant": instant,
|
|
2500
2630
|
"data-notes-open": notesOpen,
|
|
2631
|
+
"data-working": working.size > 0,
|
|
2501
2632
|
"data-open": open,
|
|
2502
2633
|
className: "isotone-root",
|
|
2503
2634
|
children: [
|
|
@@ -2634,7 +2765,7 @@ function IterationPanel({
|
|
|
2634
2765
|
"aria-hidden": railOpen,
|
|
2635
2766
|
tabIndex: railOpen ? -1 : 0,
|
|
2636
2767
|
"aria-label": notesAvailable ? "Isotone" : "Variants",
|
|
2637
|
-
title: `Isotone${shortcutLabel ? ` \xB7 ${shortcutLabel}` : ""}`,
|
|
2768
|
+
title: `Isotone${working.size ? ` \xB7 ${working.size} in progress` : ""}${shortcutLabel ? ` \xB7 ${shortcutLabel}` : ""}`,
|
|
2638
2769
|
"aria-expanded": railOpen,
|
|
2639
2770
|
onClick: () => {
|
|
2640
2771
|
if (open) closeAll();
|
|
@@ -2642,6 +2773,7 @@ function IterationPanel({
|
|
|
2642
2773
|
else openInitial();
|
|
2643
2774
|
},
|
|
2644
2775
|
children: [
|
|
2776
|
+
working.size > 0 && /* @__PURE__ */ jsx5("span", { className: "isotone-ring", ref: ringPulse }),
|
|
2645
2777
|
/* @__PURE__ */ jsx5(Icon2, { name: "iterate" }),
|
|
2646
2778
|
visibleNotes.length > 0 && /* @__PURE__ */ jsx5("span", { className: "isotone-badge", children: visibleNotes.length })
|
|
2647
2779
|
]
|