← Commit history

Publish 0.15.0

John Lauer ·4c3f34e3a4 ·2mo ago ·parent 4df48c9
4 files changed +232−15
bin/nb-dashboard+1−1
@@ -20,7 +20,7 @@ const { execFile, spawn } = require('child_process'); const fs = require('fs'); const crypto = require('crypto'); -const VERSION = '0.14.0';           // keep in lockstep with package.json (pkg version)+const VERSION = '0.15.0';           // keep in lockstep with package.json (pkg version) // SEND TO AI IS UN-PARKED (John: "yeah, we are re-enabling the send to ai. it seems to be good now"). // The ah build thread certified the crash family dead on the gate-fixed build and the delivery leg is // proven end to end. The park const is DELETED rather than flipped, so no dead branch can resurrect it.
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "nb-bridge",-  "version": "0.14.0",+  "version": "0.15.0",   "type": "app",   "title": "NB - Native Browser Bridge",   "description": "This package installs the SKILLS into your container so your AI knows how to drive your real signed-in Chrome and Edge; Bridge loads the bridge runtime itself from the release zip.",
page.json+76−6
@@ -16,10 +16,10 @@     "chrome",     "edge",     "nbrowser",-    "ai-agent",-    "developer-tools",-    "vibe-coding",-    "native-messaging"+    "pup",+    "native-messaging",+    "credentials",+    "chip-fetcher"   ],   "status": "live",   "discovery_triggers": [@@ -70,5 +70,75 @@   },   "visibility": "public",   "brief": "Your real browser, driven by AI.",-  "discovery_pitch": "nbe (the Adom Native Browser Extension, layer 2 of nb) drives the user's REAL signed-in Chrome/Edge via nbrowser_* verbs: use it for LOGGING IN, filling forms, and downloading behind auth with their OWN saved passwords - pup cannot (it is a fresh Chrome-for-Testing with no logins). Next moves: run 'adom-bridge bridge_list' to check it is installed; if not, OFFER to install it (see the installing-the-extension skill) - never make the user do it. If this container lacks the skills, 'adom-wiki pkg install adom/nb-bridge'. Check readiness with 'adom-bridge nbrowser_readiness' (state:needs-extension means offer the install)."-}+  "discovery_pitch": "nbe (the Adom Native Browser Extension, layer 2 of nb) drives the user's REAL signed-in Chrome/Edge via nbrowser_* verbs: use it for LOGGING IN, filling forms, and downloading behind auth with their OWN saved passwords - pup cannot (it is a fresh Chrome-for-Testing with no logins). Next moves: run 'adom-bridge bridge_list' to check it is installed; if not, OFFER to install it (see the installing-the-extension skill) - never make the user do it. If this container lacks the skills, 'adom-wiki pkg install adom/nb-bridge'. Check readiness with 'adom-bridge nbrowser_readiness' (state:needs-extension means offer the install).",+  "version": "0.15.0",+  "type": "app",+  "description": "This package installs the SKILLS into your container so your AI knows how to drive your real signed-in Chrome and Edge; Bridge loads the bridge runtime itself from the release zip.",+  "install": {+    "binary_name": "nb-bridge"+  },+  "files": [+    "screenshots/hero.png",+    "skills/adom-native-browser/SKILL.md",+    "skills/driving-sites/SKILL.md",+    "skills/driving-the-desktop/SKILL.md",+    "skills/driving-the-extension/SKILL.md",+    "skills/driving-the-extension-demo/SKILL.md",+    "skills/installing-the-extension/SKILL.md",+    "skills/native-browser-recording/SKILL.md",+    "skills/native-browser-safety/SKILL.md",+    "skills/reading-google-messages/SKILL.md",+    "skills/uninstalling-the-extension/SKILL.md",+    "bin/nb-dashboard",+    "docs/img/dock-nb.svg",+    "docs/img/settings/overlay-on.png",+    "docs/img/settings/overlay-off.png",+    "docs/img/settings/activity-progress.png",+    "docs/img/settings/activity-flash.png",+    "docs/img/settings/activity-off.png",+    "demo/audio/intro.mp3",+    "demo/audio/open.mp3",+    "demo/audio/shot.mp3",+    "demo/audio/click.mp3",+    "demo/audio/wrap.mp3",+    "vendor/newbie-panel.js",+    "dockbar.json",+    "install.sh",+    "uninstall.sh",+    "package.json"+  ],+  "scripts": {+    "install": "./install.sh",+    "uninstall": "./uninstall.sh"+  },+  "dependencies": {+    "adom/adom-bridge": "^2.0.3"+  },+  "sample_prompts": [+    {+      "label": "Open in my browser",+      "prompt": "open the wiki in my real chrome work profile"+    },+    {+      "label": "Log in for me",+      "prompt": "log into digikey for me using my saved password"+    },+    {+      "label": "Form behind a login",+      "prompt": "fill out this form on the site I'm already signed into"+    },+    {+      "label": "Download behind auth",+      "prompt": "download that datasheet, it's behind a login"+    },+    {+      "label": "Install the extension",+      "prompt": "install the adom native browser extension in my chrome"+    }+  ],+  "org": "adom",+  "author": {+    "name": "John Lauer",+    "email": "[email protected]"+  }+}\ No newline at end of file
vendor/newbie-panel.js+154−7
@@ -139,6 +139,35 @@     return safe.replace(re, '<b>$1</b>');   } +  /* ---- copy, with a fallback that does not need the async clipboard ------+   * navigator.clipboard.writeText needs a secure context AND, in several embedded webviews,+   * transient user activation plus a clipboard-write permission that is simply absent. In+   * ah's webview it rejects with NotAllowedError, so the ONLY path the panel had would fail+   * and the user got "Could not reach the clipboard" on a button that looks like it works.+   * The execCommand path is deprecated but synchronous, needs no permission, and works in+   * exactly the contexts the async API refuses. Try the modern one, fall back, and only then+   * report failure. */+  function copyText(text) {+    return new Promise(function (resolve, reject) {+      function legacy() {+        try {+          var ta = document.createElement('textarea');+          ta.value = text;+          ta.setAttribute('readonly', '');+          ta.style.cssText = 'position:fixed;top:0;left:-9999px;opacity:0';+          document.body.appendChild(ta);+          ta.select(); ta.setSelectionRange(0, ta.value.length);+          var ok = document.execCommand && document.execCommand('copy');+          ta.remove();+          ok ? resolve('legacy') : reject(new Error('execCommand refused'));+        } catch (e) { reject(e); }+      }+      if (navigator.clipboard && navigator.clipboard.writeText) {+        navigator.clipboard.writeText(text).then(function () { resolve('async'); }, legacy);+      } else legacy();+    });+  }+   /* ---- toasts ---------------------------------------------------------- */   var toastHost = null;   function toast(msg, kind, ms) {@@ -275,7 +304,7 @@       tipFor(cp, 'Copy this phrase');       cp.addEventListener('click', function (e) {         e.stopPropagation();-        (navigator.clipboard ? navigator.clipboard.writeText(phrase) : Promise.reject())+        copyText(phrase)           .then(function () { toast('Copied. Paste it to your AI.', 'ok'); })           .catch(function () { toast('Could not reach the clipboard. Select the phrase and copy it.', 'err'); });       });@@ -383,6 +412,18 @@   function selfCheck(panel) {     panel = panel || document.querySelector('.anp');     if (!panel) return { ok: false, error: 'no .anp panel on the page' };+    /* REFUSE TO REPORT FROM A COLLAPSED LAYOUT. An unforegrounded tab returns zero-height+     * rects while computed styles still resolve normally, so selfCheck produced EIGHT GREEN+     * ROWS beside two red ones: "panel gutter: 0px left, 744px right" and "columns: want 1,+     * got 3". That mixed shape is far more dangerous than an all-zero one, because it reads+     * as a specific layout regression rather than an invalid run, and project-manager nearly+     * filed it as a bug. A refusal cannot be misread. (project-manager, 2026-08-25.) */+    var w0 = Math.round(panel.getBoundingClientRect().width);+    if (w0 === 0) return { ok: false, invalid: true,+      error: 'panel measures 0px wide: this tab is not foregrounded, so every layout row would ' ++             'be computed from a collapsed pane while the style rows pass normally. Activate ' ++             'the tab by name and re-run. No rows reported, because a partial result from an ' ++             'unrendered pane looks like a real bug.' };     var cs = getComputedStyle(panel), rows = [], ok = true;     /* Chrome reports the USED border-width, snapped to whole device pixels, so a declared      * 1px reads as 0.666667px at dpr 1.5. Compare against the snapped value, not the@@ -427,12 +468,88 @@      * column while the other three rendered TWO, at an identical pane width. The panel was      * conformant in all four; the PAGE around it was not. The fleet gutter is 16px a side,      * so the panel's border box is the pane minus 32. */-    var vw = document.documentElement.clientWidth;-    var want32 = vw - 32, gotW = Math.round(panel.getBoundingClientRect().width);-    var gutterOk = Math.abs(gotW - want32) <= 2;+    /* Measure against the panel's OWN CONTAINING BLOCK, never documentElement.+     * The first version compared to documentElement.clientWidth, which silently assumes the+     * PAGE scrolls. An app-shell dashboard (position:fixed shell, 44px header, scroller on an+     * inner .app-body) has no document scrollbar, so clientWidth is the full pane and the+     * inner scrollbar is charged to content instead: a correctly built app-shell page fails+     * by exactly one scrollbar width, and only pages that happen to scroll at document level+     * pass. Since app-shell is the prescribed structure, the check was failing the apps that+     * followed the skill. The containing block is scrollbar-agnostic and still catches the+     * real defect, which is a page adding its own padding around the mount point.+     * (parts-search, 2026-08-25, from reading the implementation rather than the prose.) */+    /* Measure BOTH INSETS AGAINST THE PANE, and never against a named element.+     * Two earlier versions asked the wrong element how wide the world is. documentElement+     * assumes the page scrolls, and fails every app-shell dashboard by one scrollbar width.+     * The mount div assumes the 16px lives on the mount div, and reds a visually perfect page+     * that puts the gutter on a shared outer container instead. Both encode a STRUCTURE the+     * fleet was never promised. What the contract actually says is where the panel's EDGES+     * land, so measure exactly that: find the nearest scrolling ancestor (or the document),+     * and check the panel sits 16px inside its client box on both sides. Structure-agnostic:+     * it does not care which element carries the padding, or how many wrappers sit between.+     * (pup found the mount-div version would red an outer-container page, before six apps+     * adopted the pattern I had recommended; parts-search found the documentElement one.) */+    var sc = panel.parentElement;+    while (sc && sc !== document.body) {+      /* A DECLARED scroller is the pane whether or not it is scrolling RIGHT NOW. Requiring+       * it to overflow was a bug with a nastier shape than the two before it: with+       * scrollbar-gutter:stable (which this pack mandates, so results arriving cause no+       * reflow) the track is reserved even when nothing overflows, so the loop walked PAST+       * the real pane, fell back to documentElement, and reported the right inset over by+       * one scrollbar width. The SAME PAGE passed when populated and failed when empty, and+       * empty is a dashboard's first-load state. Predicted from a code read by parts-search+       * and confirmed here: 16 left / 31 right, exact. */+      var so = getComputedStyle(sc).overflowY;+      if (so === 'auto' || so === 'scroll') break;+      sc = sc.parentElement;+    }+    var paneLeft, paneW;+    if (sc && sc !== document.body) { paneLeft = sc.getBoundingClientRect().left; paneW = sc.clientWidth; }+    else { paneLeft = 0; paneW = document.documentElement.clientWidth; }+    var pr = panel.getBoundingClientRect();+    var insetL = Math.round(pr.left - paneLeft);+    var insetR = Math.round(paneLeft + paneW - pr.right);+    var gutterOk = Math.abs(insetL - 16) <= 2 && Math.abs(insetR - 16) <= 2;     if (!gutterOk) ok = false;-    rows.push({ what: 'panel gutter', want: want32 + 'px wide (16px a side)',-                got: gotW + 'px (' + Math.round((vw - gotW) / 2) + 'px a side)', pass: gutterOk });+    rows.push({ what: 'panel gutter', want: '16px inset both sides',+                got: insetL + 'px left, ' + insetR + 'px right', pass: gutterOk });+    /* THE LEFT-EDGE ROW closes the hole the gutter row cannot see. The gutter row measures+     * against whatever element the app mounts INTO, so an app that mounts into a wrapper+     * which itself carries padding passes while sitting inset from its neighbours, and the+     * fleet mismatch comes back in a form the check is blind to. This asserts the panel's+     * left edge against the PANE, which covers the whole ancestor chain in one number.+     * (parts-search, 2026-08-25, reasoning about what the new yardstick still could not see.) */+    /* PHRASE HEADROOM AS A ROW, not a console warning nobody reads: "does not truncate TODAY"+     * and "has room" are different claims.+     * MEASURE need AGAINST AN OFFSCREEN nowrap PROBE. Do NOT use clientWidth - scrollWidth:+     * for a block span with overflow:hidden, scrollWidth is max(clientWidth, content), so that+     * expression SATURATES AT 0 for anything that fits, whether it fits by 1px or by 180px. It+     * still goes negative correctly when text overflows, so it looks like a working metric.+     * Two apps independently reported "0px headroom, one character from truncating" off it;+     * remeasured with this probe, neither had less than 141px of slack. A metric that can only+     * return one value on success is not evidence of success. */+    var spans = [].slice.call(panel.querySelectorAll('.anp-phrase span'));+    if (spans.length) {+      var probe = document.createElement('span');+      probe.style.cssText = 'position:absolute;visibility:hidden;white-space:nowrap;font:' ++                            getComputedStyle(spans[0]).font;+      document.body.appendChild(probe);+      var worst = 0, worstTxt = '', tight = 0;+      spans.forEach(function (sp) {+        probe.textContent = sp.textContent;+        var need = Math.ceil(probe.getBoundingClientRect().width);+        var have = Math.round(sp.getBoundingClientRect().width);+        var slack = have - need;+        if (worstTxt === '' || slack < worst) { worst = slack; worstTxt = sp.textContent.trim().slice(0, 40); }+        if (slack < 8) tight++;+      });+      probe.remove();+      var headOk = worst >= 8;+      if (!headOk) ok = false;+      rows.push({ what: 'phrase headroom', want: 'at least 8px of slack on every phrase',+                  got: worst + 'px on ' + JSON.stringify(worstTxt) + (tight > 1 ? ' (' + tight + ' tight)' : ''),+                  pass: headOk });+    }     var fam = getComputedStyle(panel.querySelector('.anp-phrase span') || panel).fontFamily;     var famOk = /Satoshi/.test(fam);     if (!famOk) ok = false;@@ -440,6 +557,36 @@     return { ok: ok, rows: rows };   } -  root.AdomNewbiePanel = { mount: mount, toast: toast, selfCheck: selfCheck, version: '2.3.0',+  /* checkSend(apiBase) - prove the send path EXISTS, without sending anything.+   * gate() fails OPEN on purpose, so a broken probe cannot block a user from sending. The+   * consequence, which kicad found in itself: A MISSING GATE READS AS AN ALLOWED GATE. Its+   * /updater/status route had been lost in a rebuild, the gate got HTML, caught, and returned+   * open, so the version gate had been silently no-opping. Nothing anywhere said so.+   * This makes that visible. It never posts a prompt: the run-prompt route is probed with+   * OPTIONS, so it can report "wired" without creating a thread in anyone's list. */+  function checkSend(base) {+    base = base == null ? '' : base;+    var rows = [], ok = true;+    function add(what, want, got, pass) { rows.push({what: what, want: want, got: got, pass: pass}); if (!pass) ok = false; }+    return fetch(base + '/updater/status', {cache: 'no-store'})+      .then(function (r) { return r.text().then(function (t) { return {s: r.status, t: t}; }); })+      .then(function (res) {+        var v = null, parsed = false;+        try { var j = JSON.parse(res.t); parsed = true; v = j && j.updater && j.updater.current_version; } catch (e) {}+        add('gate route wired', '200 with JSON', res.s + (parsed ? ' JSON' : ' NOT JSON'), res.s === 200 && parsed);+        add('gate reports a version', 'updater.current_version', String(v), !!v);+        if (v) add('version meets the minimum', MIN_API.join('.'), String(v), verGte(verNum(v), MIN_API));+      })+      .catch(function (e) { add('gate route wired', '200 with JSON', 'FETCH FAILED: ' + e.message, false); })+      .then(function () {+        return fetch(base + '/ai-threads/run-prompt', {method: 'OPTIONS'})+          .then(function (r) { add('send route wired', 'not 404', String(r.status), r.status !== 404); })+          .catch(function (e) { add('send route wired', 'not 404', 'FETCH FAILED: ' + e.message, false); });+      })+      .then(function () { return {ok: ok, rows: rows}; });+  }++  root.AdomNewbiePanel = { mount: mount, toast: toast, selfCheck: selfCheck, copyText: copyText,+                           checkSend: checkSend, version: '2.19.1',                            TWO_COL_MIN: TWO_COL_MIN, PHRASE_BUDGET: PHRASE_BUDGET }; })(typeof window !== 'undefined' ? window : this);