opencode-browser-annotation-plugin 0.3.1 → 0.3.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "OpenCode Browser Annotation",
4
- "version": "0.3.1",
4
+ "version": "0.3.2",
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",
@@ -5,9 +5,9 @@
5
5
  // Alt+A -> start element picking (background sends "oc-pick")
6
6
  // Alt+Shift+A -> toggle the list sidebar ("oc-toggle-sidebar")
7
7
  // Picking an element opens a floating popup near it (onUI-style): type an
8
- // instruction, choose Act/Queue, then Add (to the sidebar list) or Send (submit
9
- // immediately). The sidebar is a floating rounded card holding pending
10
- // annotations for batch submit.
8
+ // instruction, then Add (to the sidebar list) or Send (submit immediately). The
9
+ // sidebar is a floating rounded card holding pending annotations for batch
10
+ // submit, with a session-target picker.
11
11
 
12
12
  (() => {
13
13
  if (window.__ocAnnotationInjected) return;
@@ -95,11 +95,11 @@
95
95
  .oc-desc { font: 11px ui-monospace, monospace; color: #aeb4c6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: rgba(255,255,255,.06); padding: 2px 7px; border-radius: 6px; flex: 1; }
96
96
  .oc-card .oc-text { font-size: 12.5px; color: #d4d7e0; white-space: pre-wrap; word-break: break-word; }
97
97
  .oc-foot-bar { padding: 11px 12px; border-top: 1px solid rgba(255,255,255,.07); position: relative; }
98
- .oc-target-row { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; }
99
- .oc-target-label { font-size: 11px; font-weight: 600; color: #8b90a0; flex: none; }
100
- .oc-select { flex: 1; min-width: 0; font: inherit; font-size: 12px; color: #e6e8ee; background: #14151b; border: 1px solid rgba(255,255,255,.12); border-radius: 8px; padding: 6px 8px; }
98
+ .oc-status-row { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; }
99
+ .oc-select { flex: none; max-width: 150px; font: inherit; font-size: 11.5px; color: #cfd3df; background: #14151b; border: 1px solid rgba(255,255,255,.12); border-radius: 7px; padding: 4px 6px; }
101
100
  .oc-select:focus { outline: none; border-color: #4c8dff; }
102
- .oc-status { font-size: 12px; margin-bottom: 9px; display: flex; align-items: center; gap: 8px; color: #9298aa; }
101
+ .oc-status { flex: 1; min-width: 0; font-size: 12px; display: flex; align-items: center; gap: 8px; color: #9298aa; }
102
+ .oc-status span, .oc-status { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
103
103
  .oc-status::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: #5b6070; flex: none; }
104
104
  .oc-status.good::before { background: #34d399; box-shadow: 0 0 8px rgba(52,211,153,.6); }
105
105
  .oc-status.warn::before { background: #fbbf24; } .oc-status.bad::before { background: #f87171; } .oc-status.checking::before { background: #60a5fa; }
@@ -122,6 +122,44 @@
122
122
  let targetSessionID = null; // user-chosen target; null = auto (last active)
123
123
  let autoSessionID = null; // the plugin's last-active session
124
124
 
125
+ // A content script keeps running after its extension is reloaded/updated, but
126
+ // its chrome.* calls then throw "Extension context invalidated". Guard every
127
+ // message so a stale overlay fails quietly instead of spamming errors, and
128
+ // tear itself down once the context is gone.
129
+ function contextAlive() {
130
+ try {
131
+ return Boolean(chrome.runtime && chrome.runtime.id);
132
+ } catch {
133
+ return false;
134
+ }
135
+ }
136
+
137
+ function sendMsg(payload, cb) {
138
+ if (!contextAlive()) {
139
+ teardown();
140
+ return;
141
+ }
142
+ try {
143
+ chrome.runtime.sendMessage(payload, (res) => {
144
+ if (chrome.runtime.lastError) {
145
+ cb(null);
146
+ return;
147
+ }
148
+ cb(res);
149
+ });
150
+ } catch {
151
+ teardown();
152
+ }
153
+ }
154
+
155
+ function teardown() {
156
+ if (statusTimer) {
157
+ clearInterval(statusTimer);
158
+ statusTimer = null;
159
+ }
160
+ if (host) host.style.display = "none";
161
+ }
162
+
125
163
  function effectiveTarget() {
126
164
  return targetSessionID || autoSessionID;
127
165
  }
@@ -277,6 +315,19 @@
277
315
  document.addEventListener("mousemove", onMove, true);
278
316
  document.addEventListener("click", onClick, true);
279
317
  document.addEventListener("keydown", onKey, true);
318
+
319
+ // Keep keystrokes typed inside our UI from reaching the page's global
320
+ // hotkeys (e.g. GitHub's "s"/"f"). Capture key events at the document before
321
+ // page listeners run; if the event originates inside our shadow UI, stop it
322
+ // (Esc is handled by onKey above, which runs first in capture order here).
323
+ const contain = (e) => {
324
+ if (e.composedPath && e.composedPath().includes(host)) {
325
+ e.stopImmediatePropagation();
326
+ }
327
+ };
328
+ for (const type of ["keydown", "keyup", "keypress"]) {
329
+ document.addEventListener(type, contain, true);
330
+ }
280
331
  }
281
332
 
282
333
  function startPicking() {
@@ -386,20 +437,21 @@
386
437
  <span class="logo">${ICON.logo}</span>
387
438
  <span class="title">Annotations</span>
388
439
  <span class="badge" id="oc-badge">0</span>
440
+ <button class="iconbtn" id="oc-pick" title="Select element (Alt+A)">${ICON.target}</button>
389
441
  <button class="iconbtn" id="oc-close" title="Close (Alt+Shift+A)">${ICON.close}</button>
390
442
  </div>
391
443
  <div class="oc-list" id="oc-list"></div>
392
444
  <div class="oc-foot-bar">
393
- <label class="oc-target-row">
394
- <span class="oc-target-label">Session</span>
395
- <select class="oc-select" id="oc-session"></select>
396
- </label>
397
- <div class="oc-status" id="oc-status">…</div>
445
+ <div class="oc-status-row">
446
+ <div class="oc-status" id="oc-status">…</div>
447
+ <select class="oc-select" id="oc-session" title="Target session"></select>
448
+ </div>
398
449
  <button class="btn primary oc-submit" id="oc-submit" disabled>${ICON.send}<span>Submit to agent</span></button>
399
450
  <div class="oc-toast" id="oc-toast"></div>
400
451
  </div>`;
401
452
  root.appendChild(sb);
402
453
  sb.querySelector("#oc-close").addEventListener("click", closeSidebar);
454
+ sb.querySelector("#oc-pick").addEventListener("click", () => startPicking());
403
455
  sb.querySelector("#oc-session").addEventListener("change", (e) => {
404
456
  targetSessionID = e.target.value || null;
405
457
  });
@@ -473,8 +525,8 @@
473
525
  if (!el) return;
474
526
  el.className = "oc-status checking";
475
527
  el.textContent = "Checking connection…";
476
- chrome.runtime.sendMessage({ type: "oc-status" }, (res) => {
477
- if (chrome.runtime.lastError || !res) {
528
+ sendMsg({ type: "oc-status" }, (res) => {
529
+ if (!res) {
478
530
  el.className = "oc-status bad";
479
531
  el.textContent = "Extension error";
480
532
  return;
@@ -541,16 +593,13 @@
541
593
  if (!annotations.length) return;
542
594
  toast("Submitting…");
543
595
  const sessionID = effectiveTarget() || undefined;
544
- chrome.runtime.sendMessage({ type: "oc-submit", annotations, sessionID }, (res) => {
545
- if (chrome.runtime.lastError || !res) {
596
+ sendMsg({ type: "oc-submit", annotations, sessionID }, (res) => {
597
+ if (!res) {
546
598
  toast("Extension error", true);
547
599
  return;
548
600
  }
549
601
  if (res.ok) {
550
- const parts = [];
551
- if (res.injected) parts.push(`${res.injected} sent`);
552
- if (res.queued) parts.push(`${res.queued} queued`);
553
- toast(parts.length ? parts.join(", ") : "Submitted");
602
+ toast(res.injected ? `${res.injected} sent to agent` : "Submitted");
554
603
  if (!quick) {
555
604
  pending = [];
556
605
  renderCards();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-browser-annotation-plugin",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
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",