Skip to content

feat(browser-bridge): support --annotated with --frame - #271

Merged
ZacxDev merged 3 commits into
mainfrom
feat/annotated-frame-support
Aug 2, 2026
Merged

feat(browser-bridge): support --annotated with --frame#271
ZacxDev merged 3 commits into
mainfrom
feat/annotated-frame-support

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

What

Support --annotated with --frame for the text op. Previously this returned annotated_with_frame_unsupported, but annotatedTextFn is self-contained and works identically when injected into a frame's document.

Changes

Discriminator (why 0.7.1)

There is no new wire op, so ping's ops list is byte-identical between builds and cannot tell you which one Brave loaded. The version bump restores the extension_stale tell, and the capability itself is the exact discriminator:

browser --instance <label> frames                       # pick a real frameId
browser --instance <label> --frame <frameId> text --annotated
  # 0.7.0 → annotated_with_frame_unsupported: --annotated is not supported with --frame
  # 0.7.1 → the per-element elements[] payload (frame-relative CSS paths)

Run it against a page that HAS an iframe — on a frameless page both builds fail identically with frame_not_found.

Verification

Live (laptop host 192.168.50.155, profiles personal+work, deployed extension 0.7.0):

  • Nested-OOPIF rig served on 127.0.0.1:8901, top.html opened + waked (never activate), tab closed after. frames0 (top) → 4410 (127.0.0.1.sslip.io/mid.html) → 4411 (127.0.0.1.nip.io/leaf.html), cross-origin, nested.

  • Control --frame 4411 text"LEAF (site: 127.0.0.1.nip.io) — a GRANDCHILD OOPIF (nested inside mid)\n\nleaf-marker", url: http://127.0.0.1.nip.io:8901/leaf.html. The frame plumbing is sound.

  • Discriminator, "before" reading --frame 4411 text --annotatedannotated_with_frame_unsupported. This is the running 0.7.0 build, i.e. the error this PR removes is live and observable today.

  • Staleness detection restored — fed the live /health versions into the server's own annotate_staleness() (read-only, nothing deployed):

    LIVE loaded versions : personal=0.7.0, work=0.7.0
    vs DEPLOYED manifest (0.7.0)   → extension_stale: false, false   ← the defeated tell
    vs THIS BRANCH  (0.7.1)        → extension_stale: true,  true    ← restored
    

🔴 NOT verified live: the feature working. The deployed extension is 0.7.0 and loading 0.7.1 needs a home-manager switch + an extension reload, neither of which was done here. Operator steps to complete it: home-manager switchbrave://extensions ↻ → browser --instance <label> ping must report extensionVersion 0.7.1 (if it still says 0.7.0, the ↻ no-op'd — fully quit and reopen Brave) → re-run the discriminator above and expect elements[].

Tests (measured on the merged tree, this branch @ 325e73a):

suite command result
node node --test --test-reporter=tap scripts/browser-bridge/tests/*.test.mjs 462 tests / 462 pass / 0 fail / 0 skipped
pytest python3 -m pytest scripts/browser-bridge/tests -q (pytest 9.0.2) 421 passed / 0 failed

test_surface_parity.py (#277) is not present on this branch, so its deliberate AGENT SURFACE PARTITION failure does not appear here.

Red-before-change (base origin/main = a0a5d73):

  • service_worker.test.mjs annotated-in-frame tests → 2 fail, both with annotated_with_frame_unsupported: --annotated is not supported with --frame; the other 15 tests in the file pass, so the harness is sound and the failures are attributable.
  • annotated.test.mjs → the whole file fails to load at base (byteCapElements is not exported).
  • test_manifest_version_reads_current / ..._falls_back_to_repo_when_not_deployed → mutation-checked by reverting manifest.json to 0.7.0: both fail with assert '0.7.0' == '0.7.1'.

Notes

Remove the annotated_with_frame_unsupported error. The annotatedTextFn
is self-contained and works identically when injected into a frame's
document. Extract byteCapElements() helper to avoid duplicating the
byte-cap loop across three paths (wake, standalone, frame).

- service_worker.js: add annotated branch inside --frame path
- protocol.js: extract byteCapElements() helper
- Remove incompatibility notes from 6 doc files
- Add 6 tests for byteCapElements

561 tests pass.
@ZacxDev

ZacxDev commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed while integration-testing this against five other open browser-bridge PRs. It merges clean with all of them and the merged tree is green (pytest 406/0; node 466 with only an unrelated intended failure from #277). Two things worth fixing before merge — the first I'd call blocking.

🔴 manifest.json is not bumped, and isn't in the diff

extension/protocol.js (+22) and extension/service_worker.js (+21/-37) both change, but manifest.json stays byte-identical at 0.7.0.

Per the repo's own rule, an extension change that must be provably loaded has to bump manifest.json and add a discriminator. Without the bump:

  • browser --instance <label> ping reports extensionVersion: 0.7.0 whether or not the new code loaded;
  • whoami/health compute extension_stale: falsenot stale — even if Brave is still running the old service worker.

So the deterministic tell that exists specifically to catch a no-op ↻ reload is defeated for exactly this change. Given how often a ↻ silently doesn't take (the long-poll keeps the old worker alive), there's currently no way to confirm this shipped rather than sitting inert.

Note the extension id is path-derived, not version-derived, so it won't disambiguate this either.

🟡 The "561 tests pass" figure doesn't reconcile

Measured on this branch: 460 node tests, 460 pass, 0 fail (node --test --test-reporter=tap, counted from # pass). pytest on main is 369 — now 370 since #275. No combination reproduces 561. The "0 failures" half matches.

Not claiming it's wrong — I don't know the command used — but worth stating it, since an unreproducible count is the kind of claim that's bitten this repo before.

Verification

I couldn't find live-verification evidence in the body. This changes extension code, so it needs a full Brave restart (not ↻), and per the above there's no version tell to confirm it took until the manifest is bumped.

The good part

The removal of annotated_with_frame_unsupported is thorough and consistent — gone from all six places it appeared (service_worker.js, SKILL.md, both READMEs, reference/errors.md, reference/read-envelopes.md), no dangling references to a now-unreachable error. ALLOWED_OPS is unchanged, so no new wire op and no impact on the surface-parity tests in #277.

—Reviewed while landing #266/#272/#274/#275; not merging this one since it isn't mine.

@ZacxDev

ZacxDev commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Heads-up: #278 just merged (a0a5d73), and it touches the adjacent row in the SKILL.md ops table — so this PR now conflicts there. Posting the resolution I already verified so you don't have to re-derive it.

The conflict

Both PRs rewrite neighbouring rows in the ops table:

The resolution — take BOTH, one row each

Keep your text row verbatim and #278's nav row verbatim. I merged and gated this exact combination before #278 landed:

gate result
SKILL.md 11,999 B vs the enforced budget 12,03839 B slack
pytest 436 passed / 0 failed
node 466 tests / 465 pass / 1 fail — the single failure is #277's deliberate AGENT SURFACE PARTITION, unrelated

🔴 The byte gate is the trap here. SKILL.md is capped at MAX_BYTES 12_288 with MIN_HEADROOM_BYTES 250 (tests/test_skill_size.py), so the enforced budget is 12,038 and there are 39 bytes of slack. A resolution that keeps both phrasings of the --frame clause — the natural thing to do when a merge tool shows you two plausible lines — blows the gate. Take one row from each side; do not merge the sentences.

Not a contradiction, in case it looks like one

After resolution the file says --annotated works with --frame (your row) and --wake is refused with --frame (the wake row, wake_with_frame_unsupported). Those are different claims about different flags and both are correct — #278 added a parse-time CLI guard for the --wake+--frame case. Don't "fix" the second one while resolving.

Still outstanding from my earlier review

The 🔴 manifest.json bump is unaddressed — extension/protocol.js and extension/service_worker.js both change while manifest.json stays byte-identical at 0.7.0, so ping will report 0.7.0 and extension_stale will compute false whether or not the new code actually loaded. That's the tell that exists to catch a no-op ↻ reload, and it's defeated for exactly this change.

ZacxDev and others added 2 commits August 2, 2026 13:44
# Conflicts:
#	scripts/browser-bridge/SKILL.md
…tated-in-frame

The extension's service_worker.js and protocol.js changed but manifest.json
stayed at 0.7.0, so `ping` reported 0.7.0 and `whoami`/`health` computed
`extension_stale: false` whether or not the new build loaded — the exact tell
that exists to catch a no-op brave://extensions reload was defeated for this
change. Bump to 0.7.1 and update the two literal assertions that pin it
(test_server.py; mutation-checked: reverting the manifest to 0.7.0 fails both
with `assert '0.7.0' == '0.7.1'`).

There is NO new wire op here, so `ping`'s `ops` list is byte-identical between
builds and cannot discriminate. The discriminator is the capability itself,
documented in extension/README.md: `--frame <id> text --annotated` errors
`annotated_with_frame_unsupported` on 0.7.0 and returns `elements[]` on 0.7.1.
Confirmed live against the deployed 0.7.0 build on the laptop host using the
nested-OOPIF rig (top -> mid -> leaf, cross-origin): the plain frame read
returns the leaf's text, the annotated frame read returns the old error.

Also add two service_worker.test.mjs regression tests for the annotated+frame
branch (injection targets the resolved frameId, reports the FRAME url, byte-cap
applies). Both are RED at origin/main a0a5d73 with
`annotated_with_frame_unsupported`, green at HEAD.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ZacxDev

ZacxDev commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Correction: the "561 tests pass" figure was wrong

Flagging this explicitly because the body has been edited and a reviewer may have read the original claim.

The original body said "561 tests pass, 0 failures". That number is not reproducible and has been removed. Measured counts:

tree node (node --test --test-reporter=tap scripts/browser-bridge/tests/*.test.mjs) pytest (python3 -m pytest scripts/browser-bridge/tests -q)
original PR head f5fbe82 (pre-merge) 460 tests / 460 pass / 0 fail
this branch 325e73a (post-merge of a0a5d73 + the 0.7.1 work) 462 / 462 / 0 421 passed / 0 failed

The 460 figure at f5fbe82 matches @ZacxDev's independent measurement exactly. No combination of node + pytest on any tree here reaches 561, so the number appears to have been asserted rather than counted. The "0 failures" half was correct.

Two counting gotchas worth recording, since they produce false numbers in this repo:

  • node --test tests/ (directory form) reports # tests 1 / # fail 1 — a false red. The glob form is the correct invocation.
  • a bare python3 -m pytest here exits 0 printing No module named pytest. Run it under nix-shell -p python312Packages.pytest (9.0.2 used above) and read the N passed line, not the exit code.

The 🔴 manifest finding is now addressed

manifest.json is bumped 0.7.0 → 0.7.1 in 325e73a, along with the two literal assertions in test_server.py that pin it (mutation-checked — reverting the manifest fails both with assert '0.7.0' == '0.7.1').

Since there is no new wire op, ping's ops list cannot discriminate this build, so the capability itself is documented as the discriminator in extension/README.md. Both halves are live-confirmed against the currently-deployed 0.7.0 build on the laptop host (nested-OOPIF rig, wake only, tab closed after):

--frame 4411 text              → "…GRANDCHILD OOPIF…\n\nleaf-marker"  (control: frame plumbing works)
--frame 4411 text --annotated  → annotated_with_frame_unsupported     (the "old build" reading)

and feeding the live /health versions through the server's own annotate_staleness() (read-only):

LIVE loaded: personal=0.7.0, work=0.7.0
expected 0.7.0 (deployed)    → extension_stale: false, false   ← the defeated tell
expected 0.7.1 (this branch) → extension_stale: true,  true    ← restored

🔴 What is still unverified: the feature actually working in Brave. That needs a home-manager switch + an extension reload, which I could not do (a full Brave restart would lose the operator's tabs — restore_on_startup is unset). Operator steps are in the body.

Also resolved here: the SKILL.md conflict with #278, using exactly the resolution posted above — this branch's text row + #278's nav/wake rows, one from each side. SKILL.md lands at 11,999 B against the 12,038 budget (39 B slack), and test_skill_size.py passes. Merged (not rebased) so the push is a fast-forward and the branch checked out in the shared base clone is not disturbed.

@ZacxDev
ZacxDev merged commit 4416770 into main Aug 2, 2026
@ZacxDev
ZacxDev deleted the feat/annotated-frame-support branch August 2, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant