@insitue/sdk 0.1.1 → 0.1.3
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/dist/capture-only.js +2 -2
- package/dist/{chunk-BYR4ZXVS.js → chunk-PRCHVT5A.js} +80 -1
- package/dist/{chunk-6SMY7D6U.js → chunk-RYS5Z2BU.js} +410 -43
- package/dist/{chunk-LGN4LKXD.js → chunk-VWPAKOUW.js} +221 -7
- package/dist/index.js +3 -3
- package/dist/overlay.js +2 -2
- package/package.json +1 -1
package/dist/capture-only.js
CHANGED
|
@@ -6,8 +6,11 @@ import {
|
|
|
6
6
|
d,
|
|
7
7
|
installRuntimeCollectors,
|
|
8
8
|
k,
|
|
9
|
+
onDisplayMediaChange,
|
|
10
|
+
retryDisplayMedia,
|
|
11
|
+
stopDisplayMedia,
|
|
9
12
|
y
|
|
10
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-RYS5Z2BU.js";
|
|
11
14
|
|
|
12
15
|
// src/capture-only.ts
|
|
13
16
|
var DEFAULT_INGEST = "https://www.insitue.com/api/v1/capture";
|
|
@@ -68,6 +71,32 @@ function CaptureOnlyApp(props) {
|
|
|
68
71
|
const [phase, setPhase] = d("idle");
|
|
69
72
|
const [bundle, setBundle] = d(null);
|
|
70
73
|
const [note, setNote] = d("");
|
|
74
|
+
const [tabCaptureActive, setTabCaptureActive] = d(false);
|
|
75
|
+
const [tabCaptureDenied, setTabCaptureDenied] = d(false);
|
|
76
|
+
y(() => {
|
|
77
|
+
return onDisplayMediaChange((active, reason) => {
|
|
78
|
+
setTabCaptureActive(active);
|
|
79
|
+
if (reason === "denied") setTabCaptureDenied(true);
|
|
80
|
+
if (reason === "granted") setTabCaptureDenied(false);
|
|
81
|
+
});
|
|
82
|
+
}, []);
|
|
83
|
+
const retryWithPixelPerfect = async () => {
|
|
84
|
+
if (!bundle?.target?.selector) {
|
|
85
|
+
await retryDisplayMedia();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const granted = await retryDisplayMedia();
|
|
89
|
+
if (granted) {
|
|
90
|
+
setPhase("picking");
|
|
91
|
+
const sel = await beginPick("element").catch(() => null);
|
|
92
|
+
if (sel) {
|
|
93
|
+
setBundle(await buildBundle(sel));
|
|
94
|
+
setPhase("compose");
|
|
95
|
+
} else {
|
|
96
|
+
setPhase("compose");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
71
100
|
const sink = new IssueTrackerSink(async (draft) => {
|
|
72
101
|
globalThis.__insitu_capture__ = {
|
|
73
102
|
title: draft.title,
|
|
@@ -235,6 +264,56 @@ function CaptureOnlyApp(props) {
|
|
|
235
264
|
},
|
|
236
265
|
"Screenshot unavailable \u2014 sending the rest."
|
|
237
266
|
) : null,
|
|
267
|
+
// Nudge: the screenshot is structurally OK but some content
|
|
268
|
+
// couldn't be embedded (non-CORS img / video / canvas). Offer
|
|
269
|
+
// a one-tap upgrade to pixel-perfect mode + re-pick.
|
|
270
|
+
bundle?.screenshot?.qualityNote && !tabCaptureActive ? k(
|
|
271
|
+
"div",
|
|
272
|
+
{
|
|
273
|
+
style: `display:flex;align-items:center;gap:8px;padding:9px 11px;background:#fff7ed;border:1px solid #fbd9b1;color:#8a4b00;border-radius:10px;font-size:12px;margin-bottom:12px`
|
|
274
|
+
},
|
|
275
|
+
[
|
|
276
|
+
k(
|
|
277
|
+
"span",
|
|
278
|
+
{ style: "flex:1" },
|
|
279
|
+
"Some content didn't capture cleanly. Enable tab capture for a pixel-perfect screenshot."
|
|
280
|
+
),
|
|
281
|
+
k(
|
|
282
|
+
"button",
|
|
283
|
+
{
|
|
284
|
+
onClick: () => void retryWithPixelPerfect(),
|
|
285
|
+
style: `all:unset;cursor:pointer;color:#5751e6;font-weight:600;padding:2px 8px;border:1px solid #c5c2ff;border-radius:6px;background:#fff`
|
|
286
|
+
},
|
|
287
|
+
"Enable"
|
|
288
|
+
)
|
|
289
|
+
]
|
|
290
|
+
) : null,
|
|
291
|
+
// Active badge — tells the user why the browser's red "tab
|
|
292
|
+
// sharing" indicator is on + lets them stop it.
|
|
293
|
+
tabCaptureActive ? k(
|
|
294
|
+
"div",
|
|
295
|
+
{
|
|
296
|
+
style: `display:flex;align-items:center;gap:8px;padding:7px 11px;background:#ecfdf5;border:1px solid #b6e6cf;color:#117a52;border-radius:10px;font-size:11.5px;margin-bottom:12px`
|
|
297
|
+
},
|
|
298
|
+
[
|
|
299
|
+
k("span", {
|
|
300
|
+
style: "width:8px;height:8px;border-radius:50%;background:#117a52;box-shadow:0 0 6px #117a52"
|
|
301
|
+
}),
|
|
302
|
+
k(
|
|
303
|
+
"span",
|
|
304
|
+
{ style: "flex:1" },
|
|
305
|
+
"Tab capture active \u2014 screenshots are pixel-perfect."
|
|
306
|
+
),
|
|
307
|
+
k(
|
|
308
|
+
"button",
|
|
309
|
+
{
|
|
310
|
+
onClick: () => stopDisplayMedia("user"),
|
|
311
|
+
style: `all:unset;cursor:pointer;color:#117a52;font-weight:600;padding:2px 6px`
|
|
312
|
+
},
|
|
313
|
+
"Stop"
|
|
314
|
+
)
|
|
315
|
+
]
|
|
316
|
+
) : null,
|
|
238
317
|
k("textarea", {
|
|
239
318
|
value: note,
|
|
240
319
|
rows: 3,
|
|
@@ -582,7 +582,7 @@ function resolveTarget(el) {
|
|
|
582
582
|
}
|
|
583
583
|
return source === void 0 ? { confidence, componentStack, selector } : { source, confidence, componentStack, selector };
|
|
584
584
|
}
|
|
585
|
-
var CAPTURE_SCHEMA_VERSION =
|
|
585
|
+
var CAPTURE_SCHEMA_VERSION = 3;
|
|
586
586
|
var PROTOCOL_VERSION = 4;
|
|
587
587
|
function toIssueDraft(bundle) {
|
|
588
588
|
const t3 = bundle.target;
|
|
@@ -597,7 +597,7 @@ function toIssueDraft(bundle) {
|
|
|
597
597
|
`**Viewport:** ${bundle.viewport.w}\xD7${bundle.viewport.h}${bundle.viewport.breakpoint ? ` (${bundle.viewport.breakpoint})` : ""}`,
|
|
598
598
|
`**Tailwind:** ${bundle.tailwindClasses.join(" ") || "\u2014"}`,
|
|
599
599
|
`**Runtime:** ${bundle.runtime.console.length} log \xB7 ${bundle.runtime.network.length} net \xB7 ${errs} err`,
|
|
600
|
-
`**Screenshot:** ${bundle.screenshot ?
|
|
600
|
+
`**Screenshot:** ${bundle.screenshot ? `attached` + (bundle.screenshot.source ? ` (${bundle.screenshot.source})` : "") + (bundle.screenshot.qualityNote ? ` \u2014 ${bundle.screenshot.qualityNote}` : "") : bundle.screenshotUnavailable ? `unavailable \u2014 ${bundle.screenshotUnavailable}` : "\u2014"}`,
|
|
601
601
|
bundle.userNote ? `
|
|
602
602
|
> ${bundle.userNote}` : "",
|
|
603
603
|
`
|
|
@@ -802,6 +802,53 @@ function beginPick(mode = "element") {
|
|
|
802
802
|
});
|
|
803
803
|
}
|
|
804
804
|
|
|
805
|
+
// src/capture-settings.ts
|
|
806
|
+
var DEFAULT_SETTINGS = {
|
|
807
|
+
alwaysPixelPerfect: false
|
|
808
|
+
};
|
|
809
|
+
function storageKey() {
|
|
810
|
+
if (typeof location === "undefined") return "insitu:capture-settings";
|
|
811
|
+
return `insitu:capture-settings:${location.host}`;
|
|
812
|
+
}
|
|
813
|
+
var cached = null;
|
|
814
|
+
function getCaptureSettings() {
|
|
815
|
+
if (cached) return cached;
|
|
816
|
+
if (typeof localStorage === "undefined") {
|
|
817
|
+
cached = { ...DEFAULT_SETTINGS };
|
|
818
|
+
return cached;
|
|
819
|
+
}
|
|
820
|
+
try {
|
|
821
|
+
const raw = localStorage.getItem(storageKey());
|
|
822
|
+
if (!raw) {
|
|
823
|
+
cached = { ...DEFAULT_SETTINGS };
|
|
824
|
+
return cached;
|
|
825
|
+
}
|
|
826
|
+
const parsed = JSON.parse(raw);
|
|
827
|
+
cached = { ...DEFAULT_SETTINGS, ...parsed };
|
|
828
|
+
return cached;
|
|
829
|
+
} catch {
|
|
830
|
+
cached = { ...DEFAULT_SETTINGS };
|
|
831
|
+
return cached;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
function setCaptureSettings(patch) {
|
|
835
|
+
const next = { ...getCaptureSettings(), ...patch };
|
|
836
|
+
cached = next;
|
|
837
|
+
if (typeof localStorage !== "undefined") {
|
|
838
|
+
try {
|
|
839
|
+
localStorage.setItem(storageKey(), JSON.stringify(next));
|
|
840
|
+
} catch {
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
for (const l3 of listeners) l3(next);
|
|
844
|
+
}
|
|
845
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
846
|
+
function onCaptureSettingsChange(l3) {
|
|
847
|
+
listeners.add(l3);
|
|
848
|
+
l3(getCaptureSettings());
|
|
849
|
+
return () => listeners.delete(l3);
|
|
850
|
+
}
|
|
851
|
+
|
|
805
852
|
// ../../node_modules/.pnpm/html-to-image@1.11.13/node_modules/html-to-image/es/util.js
|
|
806
853
|
function resolveUrl(url, baseUrl) {
|
|
807
854
|
if (url.match(/^[a-z]+:\/\//i)) {
|
|
@@ -1592,10 +1639,6 @@ async function toCanvas(node, options = {}) {
|
|
|
1592
1639
|
context.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
1593
1640
|
return canvas;
|
|
1594
1641
|
}
|
|
1595
|
-
async function toPng(node, options = {}) {
|
|
1596
|
-
const canvas = await toCanvas(node, options);
|
|
1597
|
-
return canvas.toDataURL();
|
|
1598
|
-
}
|
|
1599
1642
|
|
|
1600
1643
|
// src/capture.ts
|
|
1601
1644
|
function crossOrigin(url) {
|
|
@@ -1608,29 +1651,290 @@ function crossOrigin(url) {
|
|
|
1608
1651
|
return false;
|
|
1609
1652
|
}
|
|
1610
1653
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1654
|
+
var IMAGE_PLACEHOLDER = "data:image/svg+xml;utf8," + encodeURIComponent(
|
|
1655
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><rect width="32" height="32" fill="#e8e8e8"/><path d="M0 0 L32 32 M32 0 L0 32" stroke="#b0b0b0" stroke-width="1.5"/></svg>`
|
|
1656
|
+
);
|
|
1657
|
+
function findContextAncestor(el) {
|
|
1658
|
+
const minW = 420;
|
|
1659
|
+
const minH = 140;
|
|
1660
|
+
const maxW = window.innerWidth * 1.2;
|
|
1661
|
+
const maxH = window.innerHeight * 1.2;
|
|
1662
|
+
let cur = el;
|
|
1663
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
1664
|
+
const r3 = cur.getBoundingClientRect();
|
|
1665
|
+
if (r3.width >= minW && r3.height >= minH) return cur;
|
|
1666
|
+
const parent = cur.parentElement;
|
|
1667
|
+
if (!parent) return cur;
|
|
1668
|
+
const pr = parent.getBoundingClientRect();
|
|
1669
|
+
if (pr.width > maxW || pr.height > maxH) return cur;
|
|
1670
|
+
cur = parent;
|
|
1671
|
+
}
|
|
1672
|
+
return cur;
|
|
1673
|
+
}
|
|
1674
|
+
async function renderViewportCrop(cropRect, pixelRatio) {
|
|
1675
|
+
const bodyBg = getComputedStyle(document.body).backgroundColor;
|
|
1676
|
+
const htmlBg = getComputedStyle(document.documentElement).backgroundColor;
|
|
1677
|
+
const backgroundColor = bodyBg && bodyBg !== "rgba(0, 0, 0, 0)" && bodyBg !== "transparent" ? bodyBg : htmlBg && htmlBg !== "rgba(0, 0, 0, 0)" && htmlBg !== "transparent" ? htmlBg : "#ffffff";
|
|
1678
|
+
const fullCanvas = await toCanvas(document.documentElement, {
|
|
1679
|
+
pixelRatio,
|
|
1680
|
+
cacheBust: true,
|
|
1681
|
+
backgroundColor,
|
|
1682
|
+
imagePlaceholder: IMAGE_PLACEHOLDER,
|
|
1683
|
+
// Only filter out our own overlay layers — leave cross-origin
|
|
1684
|
+
// <img>s in place so embedImages can fetch+inline them.
|
|
1685
|
+
filter: (n2) => !(n2 instanceof Element && n2.closest?.("#insitu-root, [data-insitu-layer]"))
|
|
1686
|
+
});
|
|
1687
|
+
const sx = window.scrollX;
|
|
1688
|
+
const sy = window.scrollY;
|
|
1689
|
+
const out = document.createElement("canvas");
|
|
1690
|
+
out.width = Math.max(1, Math.round(cropRect.width * pixelRatio));
|
|
1691
|
+
out.height = Math.max(1, Math.round(cropRect.height * pixelRatio));
|
|
1692
|
+
const ctx = out.getContext("2d");
|
|
1693
|
+
if (!ctx) return null;
|
|
1694
|
+
ctx.drawImage(
|
|
1695
|
+
fullCanvas,
|
|
1696
|
+
Math.round((cropRect.x + sx) * pixelRatio),
|
|
1697
|
+
Math.round((cropRect.y + sy) * pixelRatio),
|
|
1698
|
+
Math.round(cropRect.width * pixelRatio),
|
|
1699
|
+
Math.round(cropRect.height * pixelRatio),
|
|
1700
|
+
0,
|
|
1701
|
+
0,
|
|
1702
|
+
out.width,
|
|
1703
|
+
out.height
|
|
1704
|
+
);
|
|
1705
|
+
if (looksBlankUniform(ctx, out.width, out.height)) {
|
|
1706
|
+
return null;
|
|
1707
|
+
}
|
|
1708
|
+
return out.toDataURL("image/png");
|
|
1709
|
+
}
|
|
1710
|
+
function looksBlankUniform(ctx, w3, h3) {
|
|
1711
|
+
if (w3 < 4 || h3 < 4) return false;
|
|
1712
|
+
const samples = [];
|
|
1713
|
+
for (let i3 = 0; i3 < 4; i3++) {
|
|
1714
|
+
for (let j3 = 0; j3 < 4; j3++) {
|
|
1715
|
+
const x2 = Math.floor(w3 * (i3 + 0.5) / 4);
|
|
1716
|
+
const y3 = Math.floor(h3 * (j3 + 0.5) / 4);
|
|
1717
|
+
try {
|
|
1718
|
+
const px2 = ctx.getImageData(x2, y3, 1, 1).data;
|
|
1719
|
+
samples.push(`${px2[0]},${px2[1]},${px2[2]},${px2[3]}`);
|
|
1720
|
+
} catch {
|
|
1721
|
+
return true;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return new Set(samples).size === 1;
|
|
1726
|
+
}
|
|
1727
|
+
function assessCaptureQuality(cropRect) {
|
|
1728
|
+
const out = {
|
|
1729
|
+
unembeddableImages: 0,
|
|
1730
|
+
hasVideo: false,
|
|
1731
|
+
hasCanvas: false
|
|
1732
|
+
};
|
|
1733
|
+
const all = document.querySelectorAll("img, video, canvas");
|
|
1734
|
+
for (const el of all) {
|
|
1735
|
+
if (el instanceof Element && el.closest?.("#insitu-root, [data-insitu-layer]")) {
|
|
1736
|
+
continue;
|
|
1737
|
+
}
|
|
1738
|
+
const r3 = el.getBoundingClientRect();
|
|
1739
|
+
const overlaps = r3.right >= cropRect.x && r3.left <= cropRect.x + cropRect.width && r3.bottom >= cropRect.y && r3.top <= cropRect.y + cropRect.height;
|
|
1740
|
+
if (!overlaps) continue;
|
|
1614
1741
|
if (el instanceof HTMLImageElement) {
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
}
|
|
1622
|
-
})();
|
|
1623
|
-
return `cross-origin <img> (${host})`;
|
|
1742
|
+
const src = el.currentSrc || el.src;
|
|
1743
|
+
if (!src) continue;
|
|
1744
|
+
const browserLoaded = el.complete && el.naturalWidth > 0;
|
|
1745
|
+
const corsSafe = !crossOrigin(src) || el.crossOrigin != null;
|
|
1746
|
+
if (!browserLoaded || !corsSafe) {
|
|
1747
|
+
out.unembeddableImages++;
|
|
1624
1748
|
}
|
|
1749
|
+
} else if (el instanceof HTMLVideoElement) {
|
|
1750
|
+
if (r3.width > 0 && r3.height > 0) out.hasVideo = true;
|
|
1751
|
+
} else if (el instanceof HTMLCanvasElement) {
|
|
1752
|
+
if (r3.width > 0 && r3.height > 0) out.hasCanvas = true;
|
|
1625
1753
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1754
|
+
}
|
|
1755
|
+
return out;
|
|
1756
|
+
}
|
|
1757
|
+
function describeImperfection(q2) {
|
|
1758
|
+
const parts = [];
|
|
1759
|
+
if (q2.unembeddableImages > 0) {
|
|
1760
|
+
parts.push(
|
|
1761
|
+
`${q2.unembeddableImages} non-CORS image${q2.unembeddableImages > 1 ? "s" : ""}`
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1764
|
+
if (q2.hasVideo) parts.push("video frame");
|
|
1765
|
+
if (q2.hasCanvas) parts.push("canvas content");
|
|
1766
|
+
return parts.join(" + ");
|
|
1767
|
+
}
|
|
1768
|
+
var displayMediaState = {
|
|
1769
|
+
stream: null,
|
|
1770
|
+
trackEndedHandler: null,
|
|
1771
|
+
idleTimer: null,
|
|
1772
|
+
deniedAt: null
|
|
1773
|
+
};
|
|
1774
|
+
var IDLE_MS = 9e4;
|
|
1775
|
+
var displayMediaListeners = /* @__PURE__ */ new Set();
|
|
1776
|
+
function onDisplayMediaChange(l3) {
|
|
1777
|
+
displayMediaListeners.add(l3);
|
|
1778
|
+
l3(displayMediaState.stream != null);
|
|
1779
|
+
return () => displayMediaListeners.delete(l3);
|
|
1780
|
+
}
|
|
1781
|
+
function notifyDisplayMedia(reason) {
|
|
1782
|
+
const active = displayMediaState.stream != null;
|
|
1783
|
+
for (const l3 of displayMediaListeners) l3(active, reason);
|
|
1784
|
+
}
|
|
1785
|
+
function stopDisplayMedia(reason = "stopped") {
|
|
1786
|
+
if (displayMediaState.stream) {
|
|
1787
|
+
for (const t3 of displayMediaState.stream.getTracks()) t3.stop();
|
|
1788
|
+
}
|
|
1789
|
+
if (displayMediaState.idleTimer) clearTimeout(displayMediaState.idleTimer);
|
|
1790
|
+
if (displayMediaState.trackEndedHandler && displayMediaState.stream) {
|
|
1791
|
+
for (const t3 of displayMediaState.stream.getTracks()) {
|
|
1792
|
+
t3.removeEventListener("ended", displayMediaState.trackEndedHandler);
|
|
1631
1793
|
}
|
|
1632
1794
|
}
|
|
1633
|
-
|
|
1795
|
+
displayMediaState.stream = null;
|
|
1796
|
+
displayMediaState.trackEndedHandler = null;
|
|
1797
|
+
displayMediaState.idleTimer = null;
|
|
1798
|
+
notifyDisplayMedia(reason);
|
|
1799
|
+
}
|
|
1800
|
+
function bumpIdleTimer() {
|
|
1801
|
+
if (displayMediaState.idleTimer) clearTimeout(displayMediaState.idleTimer);
|
|
1802
|
+
displayMediaState.idleTimer = setTimeout(
|
|
1803
|
+
() => stopDisplayMedia("idle"),
|
|
1804
|
+
IDLE_MS
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
function supportsDisplayMedia() {
|
|
1808
|
+
return typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getDisplayMedia === "function";
|
|
1809
|
+
}
|
|
1810
|
+
async function ensureDisplayMediaStream() {
|
|
1811
|
+
if (!supportsDisplayMedia()) return null;
|
|
1812
|
+
if (displayMediaState.stream) {
|
|
1813
|
+
bumpIdleTimer();
|
|
1814
|
+
return displayMediaState.stream;
|
|
1815
|
+
}
|
|
1816
|
+
if (displayMediaState.deniedAt && Date.now() - displayMediaState.deniedAt < 6e4) {
|
|
1817
|
+
return null;
|
|
1818
|
+
}
|
|
1819
|
+
try {
|
|
1820
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
1821
|
+
// `displaySurface: 'browser'` + `preferCurrentTab: true` makes
|
|
1822
|
+
// Chrome/Edge default-select the current tab in the prompt.
|
|
1823
|
+
// Other browsers ignore the hints; user still picks manually.
|
|
1824
|
+
video: {
|
|
1825
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1826
|
+
displaySurface: "browser"
|
|
1827
|
+
},
|
|
1828
|
+
audio: false,
|
|
1829
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1830
|
+
preferCurrentTab: true
|
|
1831
|
+
});
|
|
1832
|
+
displayMediaState.stream = stream;
|
|
1833
|
+
displayMediaState.deniedAt = null;
|
|
1834
|
+
const handler = () => stopDisplayMedia("track-ended");
|
|
1835
|
+
for (const t3 of stream.getTracks()) t3.addEventListener("ended", handler);
|
|
1836
|
+
displayMediaState.trackEndedHandler = handler;
|
|
1837
|
+
bumpIdleTimer();
|
|
1838
|
+
notifyDisplayMedia("granted");
|
|
1839
|
+
return stream;
|
|
1840
|
+
} catch {
|
|
1841
|
+
displayMediaState.deniedAt = Date.now();
|
|
1842
|
+
notifyDisplayMedia("denied");
|
|
1843
|
+
return null;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
async function retryDisplayMedia() {
|
|
1847
|
+
displayMediaState.deniedAt = null;
|
|
1848
|
+
const s3 = await ensureDisplayMediaStream();
|
|
1849
|
+
return s3 != null;
|
|
1850
|
+
}
|
|
1851
|
+
function hideOverlayLayersBriefly() {
|
|
1852
|
+
const id = "insitu-capture-hide";
|
|
1853
|
+
const style = document.createElement("style");
|
|
1854
|
+
style.id = id;
|
|
1855
|
+
style.textContent = `
|
|
1856
|
+
#insitu-root, [data-insitu-layer] { visibility: hidden !important; }
|
|
1857
|
+
`;
|
|
1858
|
+
document.head.appendChild(style);
|
|
1859
|
+
return () => {
|
|
1860
|
+
style.remove();
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
async function tryGrabViaDisplayMedia(cropRect, pixelRatio) {
|
|
1864
|
+
const wasActive = displayMediaState.stream != null;
|
|
1865
|
+
const stream = await ensureDisplayMediaStream();
|
|
1866
|
+
if (!stream) return null;
|
|
1867
|
+
const fresh = !wasActive;
|
|
1868
|
+
bumpIdleTimer();
|
|
1869
|
+
const restoreOverlay = hideOverlayLayersBriefly();
|
|
1870
|
+
await new Promise(
|
|
1871
|
+
(r3) => requestAnimationFrame(() => requestAnimationFrame(() => r3()))
|
|
1872
|
+
);
|
|
1873
|
+
try {
|
|
1874
|
+
const track = stream.getVideoTracks()[0];
|
|
1875
|
+
if (!track) return null;
|
|
1876
|
+
let bitmap = null;
|
|
1877
|
+
const Ctor = window.ImageCapture;
|
|
1878
|
+
if (Ctor) {
|
|
1879
|
+
bitmap = await new Ctor(track).grabFrame();
|
|
1880
|
+
} else {
|
|
1881
|
+
bitmap = await grabFrameViaVideo(stream);
|
|
1882
|
+
}
|
|
1883
|
+
if (!bitmap) return null;
|
|
1884
|
+
const frameW = bitmap.width;
|
|
1885
|
+
const frameH = bitmap.height;
|
|
1886
|
+
const scaleX = frameW / window.innerWidth;
|
|
1887
|
+
const scaleY = frameH / window.innerHeight;
|
|
1888
|
+
const sx = Math.max(0, Math.round(cropRect.x * scaleX));
|
|
1889
|
+
const sy = Math.max(0, Math.round(cropRect.y * scaleY));
|
|
1890
|
+
const sw = Math.min(frameW - sx, Math.round(cropRect.width * scaleX));
|
|
1891
|
+
const sh = Math.min(frameH - sy, Math.round(cropRect.height * scaleY));
|
|
1892
|
+
const out = document.createElement("canvas");
|
|
1893
|
+
out.width = Math.max(1, Math.round(cropRect.width * pixelRatio));
|
|
1894
|
+
out.height = Math.max(1, Math.round(cropRect.height * pixelRatio));
|
|
1895
|
+
const ctx = out.getContext("2d");
|
|
1896
|
+
if (!ctx) return null;
|
|
1897
|
+
ctx.drawImage(bitmap, sx, sy, sw, sh, 0, 0, out.width, out.height);
|
|
1898
|
+
bitmap.close?.();
|
|
1899
|
+
return { dataUrl: out.toDataURL("image/png"), fresh };
|
|
1900
|
+
} finally {
|
|
1901
|
+
restoreOverlay();
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
async function grabFrameViaVideo(stream) {
|
|
1905
|
+
const video = document.createElement("video");
|
|
1906
|
+
video.srcObject = stream;
|
|
1907
|
+
video.muted = true;
|
|
1908
|
+
video.playsInline = true;
|
|
1909
|
+
video.style.position = "fixed";
|
|
1910
|
+
video.style.pointerEvents = "none";
|
|
1911
|
+
video.style.opacity = "0";
|
|
1912
|
+
video.style.width = "1px";
|
|
1913
|
+
video.style.height = "1px";
|
|
1914
|
+
document.body.appendChild(video);
|
|
1915
|
+
try {
|
|
1916
|
+
await video.play().catch(() => void 0);
|
|
1917
|
+
await new Promise((resolve, reject) => {
|
|
1918
|
+
const onReady = () => {
|
|
1919
|
+
video.removeEventListener("loadeddata", onReady);
|
|
1920
|
+
resolve();
|
|
1921
|
+
};
|
|
1922
|
+
video.addEventListener("loadeddata", onReady, { once: true });
|
|
1923
|
+
setTimeout(() => reject(new Error("video timeout")), 2e3);
|
|
1924
|
+
});
|
|
1925
|
+
const tmp = document.createElement("canvas");
|
|
1926
|
+
tmp.width = video.videoWidth;
|
|
1927
|
+
tmp.height = video.videoHeight;
|
|
1928
|
+
const ctx = tmp.getContext("2d");
|
|
1929
|
+
if (!ctx) throw new Error("no 2d ctx");
|
|
1930
|
+
ctx.drawImage(video, 0, 0);
|
|
1931
|
+
return await createImageBitmap(tmp);
|
|
1932
|
+
} finally {
|
|
1933
|
+
video.remove();
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
if (typeof window !== "undefined") {
|
|
1937
|
+
window.addEventListener("pagehide", () => stopDisplayMedia("pagehide"));
|
|
1634
1938
|
}
|
|
1635
1939
|
function elementFor(sel) {
|
|
1636
1940
|
if (sel.mode === "element") return sel.pointerPath?.[0] ?? null;
|
|
@@ -1645,33 +1949,90 @@ async function buildBundle(sel) {
|
|
|
1645
1949
|
const el = elementFor(sel);
|
|
1646
1950
|
const rt = runtimeSnapshot();
|
|
1647
1951
|
const dpr = window.devicePixelRatio || 1;
|
|
1952
|
+
const settings = getCaptureSettings();
|
|
1648
1953
|
let screenshot;
|
|
1649
1954
|
let screenshotUnavailable;
|
|
1650
1955
|
if (el instanceof HTMLElement) {
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1956
|
+
const context = findContextAncestor(el);
|
|
1957
|
+
const cr = context.getBoundingClientRect();
|
|
1958
|
+
const cropRect = new DOMRect(
|
|
1959
|
+
Math.max(0, cr.x),
|
|
1960
|
+
Math.max(0, cr.y),
|
|
1961
|
+
Math.min(window.innerWidth, cr.right) - Math.max(0, cr.x),
|
|
1962
|
+
Math.min(window.innerHeight, cr.bottom) - Math.max(0, cr.y)
|
|
1963
|
+
);
|
|
1964
|
+
const orig = {
|
|
1965
|
+
outline: el.style.outline,
|
|
1966
|
+
outlineOffset: el.style.outlineOffset
|
|
1967
|
+
};
|
|
1968
|
+
el.style.outline = "3px solid #ff6b00";
|
|
1969
|
+
el.style.outlineOffset = "2px";
|
|
1970
|
+
try {
|
|
1971
|
+
const skipLayer1 = settings.alwaysPixelPerfect;
|
|
1972
|
+
let layer1Result = null;
|
|
1973
|
+
let quality = null;
|
|
1974
|
+
if (!skipLayer1) {
|
|
1975
|
+
layer1Result = await renderViewportCrop(
|
|
1976
|
+
cropRect,
|
|
1977
|
+
Math.min(dpr, 1.5)
|
|
1978
|
+
);
|
|
1979
|
+
quality = assessCaptureQuality(cropRect);
|
|
1980
|
+
}
|
|
1981
|
+
const imperfect = !layer1Result || quality != null && (quality.unembeddableImages > 0 || quality.hasVideo || quality.hasCanvas);
|
|
1982
|
+
if (imperfect || skipLayer1) {
|
|
1983
|
+
const grab = await tryGrabViaDisplayMedia(
|
|
1984
|
+
cropRect,
|
|
1985
|
+
Math.min(dpr, 2)
|
|
1986
|
+
);
|
|
1987
|
+
if (grab) {
|
|
1988
|
+
screenshot = {
|
|
1989
|
+
mime: "image/png",
|
|
1990
|
+
dataUrl: grab.dataUrl,
|
|
1991
|
+
bounds: {
|
|
1992
|
+
x: cropRect.x,
|
|
1993
|
+
y: cropRect.y,
|
|
1994
|
+
width: cropRect.width,
|
|
1995
|
+
height: cropRect.height
|
|
1996
|
+
},
|
|
1997
|
+
source: "display-media"
|
|
1998
|
+
};
|
|
1999
|
+
} else if (layer1Result) {
|
|
2000
|
+
const reason = quality ? describeImperfection(quality) : "non-CORS content";
|
|
1666
2001
|
screenshot = {
|
|
1667
2002
|
mime: "image/png",
|
|
1668
|
-
dataUrl,
|
|
1669
|
-
bounds: {
|
|
2003
|
+
dataUrl: layer1Result,
|
|
2004
|
+
bounds: {
|
|
2005
|
+
x: cropRect.x,
|
|
2006
|
+
y: cropRect.y,
|
|
2007
|
+
width: cropRect.width,
|
|
2008
|
+
height: cropRect.height
|
|
2009
|
+
},
|
|
2010
|
+
source: "rasterise",
|
|
2011
|
+
qualityNote: `${reason} couldn't be embedded \u2014 grant tab capture for pixel-perfect screenshots`
|
|
1670
2012
|
};
|
|
2013
|
+
} else {
|
|
2014
|
+
screenshotUnavailable = supportsDisplayMedia() ? "rasterise failed \u2014 grant tab capture for pixel-perfect screenshots" : "rasterise failed and tab capture unsupported in this browser";
|
|
1671
2015
|
}
|
|
1672
|
-
}
|
|
1673
|
-
|
|
2016
|
+
} else if (layer1Result) {
|
|
2017
|
+
screenshot = {
|
|
2018
|
+
mime: "image/png",
|
|
2019
|
+
dataUrl: layer1Result,
|
|
2020
|
+
bounds: {
|
|
2021
|
+
x: cropRect.x,
|
|
2022
|
+
y: cropRect.y,
|
|
2023
|
+
width: cropRect.width,
|
|
2024
|
+
height: cropRect.height
|
|
2025
|
+
},
|
|
2026
|
+
source: "rasterise"
|
|
2027
|
+
};
|
|
2028
|
+
} else {
|
|
2029
|
+
screenshotUnavailable = "rasterise produced an empty image";
|
|
1674
2030
|
}
|
|
2031
|
+
} catch (err) {
|
|
2032
|
+
screenshotUnavailable = err instanceof Error ? `rasterise failed: ${err.message}` : "rasterise failed";
|
|
2033
|
+
} finally {
|
|
2034
|
+
el.style.outline = orig.outline;
|
|
2035
|
+
el.style.outlineOffset = orig.outlineOffset;
|
|
1675
2036
|
}
|
|
1676
2037
|
}
|
|
1677
2038
|
return {
|
|
@@ -1711,5 +2072,11 @@ export {
|
|
|
1711
2072
|
installRuntimeCollectors,
|
|
1712
2073
|
runtimeErrorCount,
|
|
1713
2074
|
beginPick,
|
|
2075
|
+
getCaptureSettings,
|
|
2076
|
+
setCaptureSettings,
|
|
2077
|
+
onCaptureSettingsChange,
|
|
2078
|
+
onDisplayMediaChange,
|
|
2079
|
+
stopDisplayMedia,
|
|
2080
|
+
retryDisplayMedia,
|
|
1714
2081
|
buildBundle
|
|
1715
2082
|
};
|
|
@@ -5,11 +5,17 @@ import {
|
|
|
5
5
|
beginPick,
|
|
6
6
|
buildBundle,
|
|
7
7
|
d,
|
|
8
|
+
getCaptureSettings,
|
|
8
9
|
installRuntimeCollectors,
|
|
9
10
|
k,
|
|
11
|
+
onCaptureSettingsChange,
|
|
12
|
+
onDisplayMediaChange,
|
|
13
|
+
retryDisplayMedia,
|
|
10
14
|
runtimeErrorCount,
|
|
15
|
+
setCaptureSettings,
|
|
16
|
+
stopDisplayMedia,
|
|
11
17
|
y
|
|
12
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-RYS5Z2BU.js";
|
|
13
19
|
|
|
14
20
|
// src/client.ts
|
|
15
21
|
var CompanionClient = class {
|
|
@@ -173,6 +179,42 @@ function diffLines(diff) {
|
|
|
173
179
|
return k("div", { style: `color:${color};white-space:pre` }, ln || " ");
|
|
174
180
|
});
|
|
175
181
|
}
|
|
182
|
+
function renderMessageBody(text) {
|
|
183
|
+
const parts = [];
|
|
184
|
+
const lines = text.split("\n");
|
|
185
|
+
let buf = [];
|
|
186
|
+
let inCode = false;
|
|
187
|
+
let lang = "";
|
|
188
|
+
const flush = () => {
|
|
189
|
+
if (buf.length === 0) return;
|
|
190
|
+
parts.push({ code: inCode, lang, text: buf.join("\n") });
|
|
191
|
+
buf = [];
|
|
192
|
+
};
|
|
193
|
+
for (const line of lines) {
|
|
194
|
+
const fence = /^```(\w*)\s*$/.exec(line);
|
|
195
|
+
if (fence) {
|
|
196
|
+
flush();
|
|
197
|
+
inCode = !inCode;
|
|
198
|
+
lang = inCode ? fence[1] ?? "" : "";
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
buf.push(line);
|
|
202
|
+
}
|
|
203
|
+
flush();
|
|
204
|
+
return parts.map(
|
|
205
|
+
(p) => p.code ? k(
|
|
206
|
+
"div",
|
|
207
|
+
{
|
|
208
|
+
style: `${card};padding:8px;margin:4px 0;font:${mono};color:#ececef;overflow-x:auto;white-space:pre`
|
|
209
|
+
},
|
|
210
|
+
p.text
|
|
211
|
+
) : k(
|
|
212
|
+
"div",
|
|
213
|
+
{ style: "white-space:pre-wrap;word-break:break-word" },
|
|
214
|
+
p.text
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
}
|
|
176
218
|
function diffBlock(changes) {
|
|
177
219
|
return changes.map(
|
|
178
220
|
(c) => k("div", { style: "margin:6px 0" }, [
|
|
@@ -202,6 +244,11 @@ function App(props) {
|
|
|
202
244
|
const [showCtx, setShowCtx] = d(false);
|
|
203
245
|
const [showSettings, setShowSettings] = d(false);
|
|
204
246
|
const [autoApply, setAutoApply] = d(false);
|
|
247
|
+
const [captureSettings, setCaptureSettingsState] = d(
|
|
248
|
+
getCaptureSettings()
|
|
249
|
+
);
|
|
250
|
+
const [displayMediaActive, setDisplayMediaActive] = d(false);
|
|
251
|
+
const [displayMediaDenied, setDisplayMediaDenied] = d(false);
|
|
205
252
|
const [agentReady, setAgentReady] = d(null);
|
|
206
253
|
const [agentNote, setAgentNote] = d("");
|
|
207
254
|
const [chatInput, setChatInput] = d("");
|
|
@@ -225,6 +272,7 @@ function App(props) {
|
|
|
225
272
|
const changesRef = A([]);
|
|
226
273
|
const activeTurnRef = A(null);
|
|
227
274
|
const threadRef = A(null);
|
|
275
|
+
const panelRef = A(null);
|
|
228
276
|
autoApplyRef.current = autoApply;
|
|
229
277
|
changesRef.current = changes;
|
|
230
278
|
activeTurnRef.current = activeTurn;
|
|
@@ -235,6 +283,32 @@ function App(props) {
|
|
|
235
283
|
}
|
|
236
284
|
return [...ms, { role: "agent", text: delta }];
|
|
237
285
|
});
|
|
286
|
+
const storageKey = `insitue:session:${typeof window !== "undefined" ? window.location.origin : "default"}`;
|
|
287
|
+
const hydratedRef = A(false);
|
|
288
|
+
y(() => {
|
|
289
|
+
if (hydratedRef.current) return;
|
|
290
|
+
hydratedRef.current = true;
|
|
291
|
+
try {
|
|
292
|
+
const raw = window.localStorage.getItem(storageKey);
|
|
293
|
+
if (!raw) return;
|
|
294
|
+
const saved = JSON.parse(raw);
|
|
295
|
+
if (Array.isArray(saved.messages)) setMessages(saved.messages);
|
|
296
|
+
if (Array.isArray(saved.history)) setHistory(saved.history);
|
|
297
|
+
if (typeof saved.autoApply === "boolean") setAutoApply(saved.autoApply);
|
|
298
|
+
if (typeof saved.open === "boolean") setOpen(saved.open);
|
|
299
|
+
} catch {
|
|
300
|
+
}
|
|
301
|
+
}, [storageKey]);
|
|
302
|
+
y(() => {
|
|
303
|
+
if (!hydratedRef.current) return;
|
|
304
|
+
try {
|
|
305
|
+
window.localStorage.setItem(
|
|
306
|
+
storageKey,
|
|
307
|
+
JSON.stringify({ messages, history, autoApply, open })
|
|
308
|
+
);
|
|
309
|
+
} catch {
|
|
310
|
+
}
|
|
311
|
+
}, [messages, history, autoApply, open, storageKey]);
|
|
238
312
|
y(() => {
|
|
239
313
|
installRuntimeCollectors();
|
|
240
314
|
const c = new CompanionClient(props.port, {
|
|
@@ -370,6 +444,37 @@ function App(props) {
|
|
|
370
444
|
const el = threadRef.current;
|
|
371
445
|
if (el) el.scrollTop = el.scrollHeight;
|
|
372
446
|
}, [messages, changes, turnBusy, activity]);
|
|
447
|
+
y(() => {
|
|
448
|
+
const off1 = onDisplayMediaChange((active, reason) => {
|
|
449
|
+
setDisplayMediaActive(active);
|
|
450
|
+
if (reason === "denied") setDisplayMediaDenied(true);
|
|
451
|
+
if (reason === "granted") setDisplayMediaDenied(false);
|
|
452
|
+
});
|
|
453
|
+
const off2 = onCaptureSettingsChange((s) => setCaptureSettingsState(s));
|
|
454
|
+
return () => {
|
|
455
|
+
off1();
|
|
456
|
+
off2();
|
|
457
|
+
};
|
|
458
|
+
}, []);
|
|
459
|
+
y(() => {
|
|
460
|
+
const onKey = (ev) => {
|
|
461
|
+
const meta = ev.metaKey || ev.ctrlKey;
|
|
462
|
+
if (meta && ev.key === "k") {
|
|
463
|
+
ev.preventDefault();
|
|
464
|
+
setOpen(true);
|
|
465
|
+
setTimeout(() => {
|
|
466
|
+
const ta = panelRef.current?.querySelector(
|
|
467
|
+
"textarea"
|
|
468
|
+
);
|
|
469
|
+
ta?.focus();
|
|
470
|
+
}, 0);
|
|
471
|
+
} else if (ev.key === "Escape" && open && !chatInput.trim()) {
|
|
472
|
+
setOpen(false);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
window.addEventListener("keydown", onKey);
|
|
476
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
477
|
+
}, [open, chatInput]);
|
|
373
478
|
const captureSel = async (sel) => {
|
|
374
479
|
setLastSel(sel);
|
|
375
480
|
const b = await buildBundle(sel);
|
|
@@ -394,9 +499,30 @@ function App(props) {
|
|
|
394
499
|
}
|
|
395
500
|
};
|
|
396
501
|
const sendChat = () => {
|
|
397
|
-
if (!client || !
|
|
398
|
-
const turnId = bundle.id;
|
|
502
|
+
if (!client || !chatInput.trim() || turnBusy) return;
|
|
399
503
|
const text = chatInput.trim();
|
|
504
|
+
if (text.startsWith("/")) {
|
|
505
|
+
const [cmd, ...rest] = text.split(/\s+/);
|
|
506
|
+
if (cmd === "/clear") {
|
|
507
|
+
setMessages([]);
|
|
508
|
+
setChatInput("");
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (cmd === "/undo") {
|
|
512
|
+
const top = history.find((h) => h.status === "applied");
|
|
513
|
+
if (top) client.sendUndo(top.turnId);
|
|
514
|
+
setChatInput("");
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (cmd === "/commit") {
|
|
518
|
+
const msg = rest.join(" ") || "Apply InSitue session changes";
|
|
519
|
+
client.sendCommitSession(msg);
|
|
520
|
+
setChatInput("");
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (!bundle) return;
|
|
525
|
+
const turnId = bundle.id;
|
|
400
526
|
setActiveTurn({ turnId, prompt: text, sel: lastSel });
|
|
401
527
|
setMessages((ms) => [...ms, { role: "user", text }]);
|
|
402
528
|
setChatInput("");
|
|
@@ -550,12 +676,26 @@ ${resolved.snippet}`
|
|
|
550
676
|
},
|
|
551
677
|
[
|
|
552
678
|
...messages.map(
|
|
553
|
-
(m) => k(
|
|
679
|
+
(m, i) => k(
|
|
554
680
|
"div",
|
|
555
681
|
{
|
|
556
|
-
style: m.role === "user" ? `align-self:flex-end;max-width:88%;${card};border-color:#2e2e3c;padding:6px 8px;color:#ececef;white-space:pre-wrap;word-break:break-word` : `align-self:flex-start;max-width:96%;padding:6px 8px;color:#ececef
|
|
682
|
+
style: m.role === "user" ? `align-self:flex-end;max-width:88%;${card};border-color:#2e2e3c;padding:6px 8px;color:#ececef;white-space:pre-wrap;word-break:break-word;cursor:pointer` : `align-self:flex-start;max-width:96%;padding:6px 8px;color:#ececef`,
|
|
683
|
+
// Click any prior user message to re-populate the
|
|
684
|
+
// input — matches Claude.ai / Cursor edit-and-retry.
|
|
685
|
+
// The original turn stays in the thread; sending
|
|
686
|
+
// creates a new turn.
|
|
687
|
+
onClick: m.role === "user" ? () => {
|
|
688
|
+
setChatInput(m.text);
|
|
689
|
+
setTimeout(() => {
|
|
690
|
+
const ta = panelRef.current?.querySelector(
|
|
691
|
+
"textarea"
|
|
692
|
+
);
|
|
693
|
+
ta?.focus();
|
|
694
|
+
}, 0);
|
|
695
|
+
} : void 0,
|
|
696
|
+
title: m.role === "user" ? "click to edit + retry" : void 0
|
|
557
697
|
},
|
|
558
|
-
m.text
|
|
698
|
+
m.role === "agent" ? renderMessageBody(m.text) : m.text
|
|
559
699
|
)
|
|
560
700
|
),
|
|
561
701
|
thinking && turnBusy ? k(
|
|
@@ -687,7 +827,7 @@ ${resolved.snippet}`
|
|
|
687
827
|
proposed,
|
|
688
828
|
k("textarea", {
|
|
689
829
|
value: chatInput,
|
|
690
|
-
placeholder: messages.length ? "reply\u2026 (the agent remembers this thread)" : "what does this do? \xB7 make the padding bigger \xB7 fix this bug",
|
|
830
|
+
placeholder: messages.length ? "reply\u2026 (the agent remembers this thread) \xB7 /undo /clear /commit" : "what does this do? \xB7 make the padding bigger \xB7 fix this bug \xB7 \u2318K to focus",
|
|
691
831
|
rows: 2,
|
|
692
832
|
onInput: (ev) => setChatInput(ev.target.value),
|
|
693
833
|
onKeyDown: (ev) => {
|
|
@@ -847,11 +987,83 @@ ${resolved.snippet}`
|
|
|
847
987
|
"div",
|
|
848
988
|
{ style: `color:${muted};margin-top:4px` },
|
|
849
989
|
"Writes proposed changes immediately. Still checkpointed & undoable; no manual gate. Resets on reload."
|
|
990
|
+
),
|
|
991
|
+
k(
|
|
992
|
+
"label",
|
|
993
|
+
{
|
|
994
|
+
style: "display:flex;gap:8px;align-items:center;cursor:pointer;color:#ececef;margin-top:10px"
|
|
995
|
+
},
|
|
996
|
+
[
|
|
997
|
+
k("input", {
|
|
998
|
+
type: "checkbox",
|
|
999
|
+
checked: captureSettings.alwaysPixelPerfect,
|
|
1000
|
+
onChange: (ev) => setCaptureSettings({
|
|
1001
|
+
alwaysPixelPerfect: ev.target.checked
|
|
1002
|
+
})
|
|
1003
|
+
}),
|
|
1004
|
+
k("span", {}, "Always pixel-perfect screenshots")
|
|
1005
|
+
]
|
|
1006
|
+
),
|
|
1007
|
+
k(
|
|
1008
|
+
"div",
|
|
1009
|
+
{ style: `color:${muted};margin-top:4px` },
|
|
1010
|
+
"Skips the silent rasterise path and always uses tab capture. One permission per session; every screenshot is OS-pixel accurate."
|
|
850
1011
|
)
|
|
851
1012
|
]) : null;
|
|
1013
|
+
const captureActivePill = displayMediaActive ? k(
|
|
1014
|
+
"div",
|
|
1015
|
+
{
|
|
1016
|
+
style: "display:flex;align-items:center;gap:8px;padding:6px 10px;margin:6px 0;border-radius:4px;background:#1a2a1f;border:1px solid #2f5040;color:#9fe7b8;font-size:11px"
|
|
1017
|
+
},
|
|
1018
|
+
[
|
|
1019
|
+
k(
|
|
1020
|
+
"span",
|
|
1021
|
+
{ style: "display:inline-flex;align-items:center;gap:6px" },
|
|
1022
|
+
[
|
|
1023
|
+
k("span", {
|
|
1024
|
+
style: "width:8px;height:8px;border-radius:50%;background:#2fd16b;box-shadow:0 0 6px #2fd16b"
|
|
1025
|
+
}),
|
|
1026
|
+
k("span", {}, "Tab capture active")
|
|
1027
|
+
]
|
|
1028
|
+
),
|
|
1029
|
+
k(
|
|
1030
|
+
"button",
|
|
1031
|
+
{
|
|
1032
|
+
style: `${btn};margin-left:auto`,
|
|
1033
|
+
onClick: () => stopDisplayMedia("user"),
|
|
1034
|
+
title: "Stop sharing this tab"
|
|
1035
|
+
},
|
|
1036
|
+
"Stop"
|
|
1037
|
+
)
|
|
1038
|
+
]
|
|
1039
|
+
) : null;
|
|
1040
|
+
const captureDeniedNudge = displayMediaDenied && !displayMediaActive ? k(
|
|
1041
|
+
"div",
|
|
1042
|
+
{
|
|
1043
|
+
style: "display:flex;align-items:center;gap:8px;padding:6px 10px;margin:6px 0;border-radius:4px;background:#2a1f1a;border:1px solid #5a3a2a;color:#e8c69f;font-size:11px"
|
|
1044
|
+
},
|
|
1045
|
+
[
|
|
1046
|
+
k(
|
|
1047
|
+
"span",
|
|
1048
|
+
{ style: "flex:1" },
|
|
1049
|
+
"Screenshot missed some cross-origin content. Grant tab capture for pixel-perfect captures."
|
|
1050
|
+
),
|
|
1051
|
+
k(
|
|
1052
|
+
"button",
|
|
1053
|
+
{
|
|
1054
|
+
style: btn,
|
|
1055
|
+
onClick: () => {
|
|
1056
|
+
void retryDisplayMedia();
|
|
1057
|
+
}
|
|
1058
|
+
},
|
|
1059
|
+
"Enable"
|
|
1060
|
+
)
|
|
1061
|
+
]
|
|
1062
|
+
) : null;
|
|
852
1063
|
const panel = open ? k(
|
|
853
1064
|
"div",
|
|
854
1065
|
{
|
|
1066
|
+
ref: panelRef,
|
|
855
1067
|
style: {
|
|
856
1068
|
position: "fixed",
|
|
857
1069
|
bottom: "64px",
|
|
@@ -903,6 +1115,8 @@ ${resolved.snippet}`
|
|
|
903
1115
|
]
|
|
904
1116
|
),
|
|
905
1117
|
settings,
|
|
1118
|
+
captureActivePill,
|
|
1119
|
+
captureDeniedNudge,
|
|
906
1120
|
ctx,
|
|
907
1121
|
conversation,
|
|
908
1122
|
timeline,
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
mountCaptureOnly
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PRCHVT5A.js";
|
|
4
4
|
import {
|
|
5
5
|
mountInSitue
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-VWPAKOUW.js";
|
|
7
|
+
import "./chunk-RYS5Z2BU.js";
|
|
8
8
|
|
|
9
9
|
// src/InSitue.tsx
|
|
10
10
|
import { useEffect } from "react";
|
package/dist/overlay.js
CHANGED
package/package.json
CHANGED