opencode-browser-annotation-plugin 0.5.0 → 0.5.1
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/extension/manifest.json +1 -1
- package/extension/overlay.js +51 -20
- package/package.json +1 -1
package/extension/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "OpenCode Browser Annotation",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.1",
|
|
5
5
|
"description": "Alt+A to annotate an element, Alt+Shift+A for the annotation list. Sends element metadata to your OpenCode agent.",
|
|
6
6
|
"permissions": [
|
|
7
7
|
"activeTab",
|
package/extension/overlay.js
CHANGED
|
@@ -139,6 +139,7 @@
|
|
|
139
139
|
let sessions = []; // [{ id, title, updated }]
|
|
140
140
|
let targetSessionID = null; // user-chosen target; null = auto (last active)
|
|
141
141
|
let autoSessionID = null; // the plugin's last-active session
|
|
142
|
+
let lastStatusState = null; // "good" | "bad" | "checking" | null; avoids flicker
|
|
142
143
|
|
|
143
144
|
// A content script keeps running after its extension is reloaded/updated, but
|
|
144
145
|
// its chrome.* calls then throw "Extension context invalidated". Guard every
|
|
@@ -203,6 +204,25 @@
|
|
|
203
204
|
return undefined;
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
// Drop build-generated/hashed class names (CSS Modules, styled-components,
|
|
208
|
+
// Emotion, etc.). They change every build and don't map to source, so they are
|
|
209
|
+
// noise for locating code. Keep human-authored classes like "heading-element".
|
|
210
|
+
function isHashedClass(c) {
|
|
211
|
+
if (!c) return true;
|
|
212
|
+
if (/module__/i.test(c)) return true; // CSS Modules: Foo-module__Bar__hHXUL
|
|
213
|
+
if (/__[A-Za-z0-9]{4,}$/.test(c)) return true; // trailing __hash
|
|
214
|
+
if (/^css-[a-z0-9]{5,}$/i.test(c)) return true; // Emotion: css-1ab2c3
|
|
215
|
+
if (/^sc-[A-Za-z0-9]{5,}$/.test(c)) return true; // styled-components: sc-bdVaJa
|
|
216
|
+
if (/^[A-Za-z0-9]{7,}$/.test(c) && /[0-9]/.test(c) && /[A-Z]/.test(c)) return true; // mixed-case+digit opaque hash
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function cleanClasses(list) {
|
|
221
|
+
if (!list || !list.length) return undefined;
|
|
222
|
+
const kept = list.filter((c) => !isHashedClass(c));
|
|
223
|
+
return kept.length ? kept : undefined;
|
|
224
|
+
}
|
|
225
|
+
|
|
206
226
|
function cssPath(el) {
|
|
207
227
|
if (!(el instanceof Element)) return "";
|
|
208
228
|
if (el.id) return `#${CSS.escape(el.id)}`;
|
|
@@ -237,7 +257,8 @@
|
|
|
237
257
|
else if (el.id) s += `#${el.id}`;
|
|
238
258
|
const role = el.getAttribute && el.getAttribute("role");
|
|
239
259
|
if (role) s += `[role=${role}]`;
|
|
240
|
-
|
|
260
|
+
const cls = cleanClasses(el.classList ? Array.from(el.classList) : []);
|
|
261
|
+
if (cls) s += "." + cls.slice(0, 3).join(".");
|
|
241
262
|
return s;
|
|
242
263
|
}
|
|
243
264
|
|
|
@@ -319,7 +340,6 @@
|
|
|
319
340
|
|
|
320
341
|
function elementMeta(el, inShadow) {
|
|
321
342
|
const r = el.getBoundingClientRect();
|
|
322
|
-
const classes = el.classList ? Array.from(el.classList) : [];
|
|
323
343
|
const fw = frameworkComponents(el);
|
|
324
344
|
const m = {
|
|
325
345
|
selector: cssPath(el),
|
|
@@ -329,7 +349,7 @@
|
|
|
329
349
|
testId: bestTestId(el),
|
|
330
350
|
role: el.getAttribute("role") || undefined,
|
|
331
351
|
ariaLabel: el.getAttribute("aria-label") || undefined,
|
|
332
|
-
classes:
|
|
352
|
+
classes: cleanClasses(el.classList ? Array.from(el.classList) : []),
|
|
333
353
|
text: (el.textContent || "").trim().slice(0, 500) || undefined,
|
|
334
354
|
href: el.getAttribute("href") || undefined,
|
|
335
355
|
src: el.getAttribute("src") || undefined,
|
|
@@ -371,18 +391,22 @@
|
|
|
371
391
|
const pad = 6 * dpr;
|
|
372
392
|
let sx = Math.max(0, rect.left * dpr - pad);
|
|
373
393
|
let sy = Math.max(0, rect.top * dpr - pad);
|
|
374
|
-
|
|
375
|
-
|
|
394
|
+
const sw = Math.min(img.width - sx, rect.width * dpr + pad * 2);
|
|
395
|
+
const sh = Math.min(img.height - sy, rect.height * dpr + pad * 2);
|
|
376
396
|
if (sw <= 0 || sh <= 0) return resolve(null);
|
|
377
|
-
//
|
|
378
|
-
|
|
397
|
+
// Keep the crop at (up to) full captured resolution so it stays sharp
|
|
398
|
+
// on the sidebar's HiDPI display. Only downscale if it's very large,
|
|
399
|
+
// to keep the data URL reasonable.
|
|
400
|
+
const maxW = 900;
|
|
379
401
|
const scale = Math.min(1, maxW / sw);
|
|
380
|
-
const cw = Math.round(sw * scale);
|
|
381
|
-
const ch = Math.round(sh * scale);
|
|
402
|
+
const cw = Math.max(1, Math.round(sw * scale));
|
|
403
|
+
const ch = Math.max(1, Math.round(sh * scale));
|
|
382
404
|
const c = document.createElement("canvas");
|
|
383
405
|
c.width = cw;
|
|
384
406
|
c.height = ch;
|
|
385
407
|
const ctx = c.getContext("2d");
|
|
408
|
+
ctx.imageSmoothingEnabled = true;
|
|
409
|
+
ctx.imageSmoothingQuality = "high";
|
|
386
410
|
ctx.drawImage(img, sx, sy, sw, sh, 0, 0, cw, ch);
|
|
387
411
|
resolve(c.toDataURL("image/png"));
|
|
388
412
|
} catch {
|
|
@@ -620,7 +644,7 @@
|
|
|
620
644
|
</div>`;
|
|
621
645
|
root.appendChild(sb);
|
|
622
646
|
sb.querySelector("#oc-close").addEventListener("click", closeSidebar);
|
|
623
|
-
sb.querySelector("#oc-pick").addEventListener("click", () => startPicking());
|
|
647
|
+
sb.querySelector("#oc-pick").addEventListener("click", () => (picking ? stopPicking() : startPicking()));
|
|
624
648
|
sb.querySelector("#oc-dd-btn").addEventListener("click", (e) => {
|
|
625
649
|
e.stopPropagation();
|
|
626
650
|
sb.querySelector("#oc-dd").classList.toggle("open");
|
|
@@ -654,6 +678,7 @@
|
|
|
654
678
|
clearInterval(statusTimer);
|
|
655
679
|
statusTimer = null;
|
|
656
680
|
}
|
|
681
|
+
lastStatusState = null; // re-check cleanly on reopen
|
|
657
682
|
}
|
|
658
683
|
|
|
659
684
|
function toggleSidebar() {
|
|
@@ -693,27 +718,33 @@
|
|
|
693
718
|
|
|
694
719
|
// ---------- status + submit ----------
|
|
695
720
|
|
|
721
|
+
// Apply a status only when it actually changes, so routine polls don't make
|
|
722
|
+
// the text flicker. Only the very first check shows "Checking…".
|
|
723
|
+
function setStatus(state, text) {
|
|
724
|
+
const el = root && root.getElementById("oc-status");
|
|
725
|
+
if (!el) return;
|
|
726
|
+
if (lastStatusState === state) return; // stable during polls
|
|
727
|
+
lastStatusState = state;
|
|
728
|
+
el.className = `oc-status ${state}`;
|
|
729
|
+
el.textContent = text;
|
|
730
|
+
}
|
|
731
|
+
|
|
696
732
|
function refreshStatus() {
|
|
697
733
|
if (!root) return;
|
|
698
|
-
|
|
699
|
-
if (!el) return;
|
|
700
|
-
el.className = "oc-status checking";
|
|
701
|
-
el.textContent = "Checking connection…";
|
|
734
|
+
if (lastStatusState === null) setStatus("checking", "Checking connection…");
|
|
702
735
|
sendMsg({ type: "oc-status" }, (res) => {
|
|
703
736
|
if (!res) {
|
|
704
|
-
|
|
705
|
-
el.textContent = "Extension error";
|
|
737
|
+
setStatus("bad", "Extension error");
|
|
706
738
|
return;
|
|
707
739
|
}
|
|
708
740
|
if (res.ok && res.data?.ok) {
|
|
741
|
+
// Session list updates every poll; the status text stays stable.
|
|
709
742
|
sessions = Array.isArray(res.data.sessions) ? res.data.sessions : [];
|
|
710
743
|
autoSessionID = res.data.sessionID || null;
|
|
711
744
|
renderSessions();
|
|
712
|
-
|
|
713
|
-
el.textContent = "Connected";
|
|
745
|
+
setStatus("good", "Connected");
|
|
714
746
|
} else {
|
|
715
|
-
|
|
716
|
-
el.textContent = "Not connected — check the SSH tunnel";
|
|
747
|
+
setStatus("bad", "Not connected — check the SSH tunnel");
|
|
717
748
|
}
|
|
718
749
|
});
|
|
719
750
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-browser-annotation-plugin",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Select an element in your browser, type an instruction, and send it to your OpenCode agent over a loopback + SSH tunnel. Text and element metadata only (no screenshots).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|