jawb observe [<url>] [--full] [--interactive-only] [--js]

Observe the current or a given page's elements.

Observes the current (or given) page: a compact list of its elements, each with a short numeric ID you use to target it in later Read and Act commands. With a URL and no --tab, observe opens the URL first (this is the same shape as the old jawb observe <url>).

$ jawb observe --tab t_5a87rr
Example Domain  https://example.com/  (rev 1, 2 of 3 elements)
  [1] heading(1) "Example Domain"
  [3] link "Learn more" -> https://iana.org/domains/example
rev 1 → 1 (native, 60ms)

By default, non-interactive text nodes are hidden. --full shows every element, including plain text:

$ jawb observe --tab t_5a87rr --full
Example Domain  https://example.com/  (rev 1, 3 of 3 elements)
  [1] heading(1) "Example Domain"
  [2] text "This domain is for use in documentation examples without needing permission. Avoid use in operations."
  [3] link "Learn more" -> https://iana.org/domains/example
rev 1 → 1 (native, 83ms)

--interactive-only is the explicit form of the default (links, buttons, inputs, and similar controls only).

--json:

$ jawb --json observe --tab t_5a87rr
{"command":"observe","diff":{"from":1,"to":1},"executor":"native","ok":true,"prev_revision":1,"profile":"default","result":{"counts":{"shown":2,"total":3},"elements":[{"href":null,"id":1,"label":"Example Domain","level":1,"role":"heading"},{"href":"https://iana.org/domains/example","id":3,"label":"Learn more","role":"link"}],"revision":1,"title":"Example Domain","url":"https://example.com/","visibility":{"basis":"inline_only","omitted":{"hidden":{},"unknown":{"aria_hidden":0}}}},"revision":1,"tab":"t_5a87rr","v":1,"warnings":[]}

Fields

  • role — the element's ARIA-ish role: link, button, heading, textbox, combobox, text, and so on.
  • label — the element's accessible name, if any.
  • href — present for links.
  • input_type — for <input> fields: the browser's normalized type (text, email, password, number, file, ...; a missing or unknown type is text). Use it to tell a file input (for jawb upload) from a text box.
  • options — for a <select> (combobox or listbox): up to 200 {label, value, selected} entries, hidden options left out. Pass a value or label to jawb select. Custom ARIA listboxes list their options as separate option elements instead.
  • id — a short numeric handle, stable across commands (not a sequential re-parse index). An ID keeps working across processes (jawb click 48 right after jawb observe in a different terminal works), and stays valid even when unrelated parts of the page change. If the element it points to is rebuilt, the daemon retargets automatically and reports retargeted: {from, to}; if it can no longer resolve to exactly one element, you get E_STALE_ID (exit 4) telling you to re-observe.

Hidden content

observe and read leave out text a person would not see, using the same rules. Without --js, jawb can only judge the page's own markup: the hidden attribute, and inline style that sets display:none, visibility:hidden, opacity:0 or a zero font size, on the element or any element around it, plus the body of a closed <details> (its summary is kept) and a closed <dialog>. Text hidden that way is not listed and is not part of any element's label. A hidden button, link or field is still listed, with state hidden, so you can still open a collapsed menu. Its label, value and link target are left out (null): they are page text a person cannot see, and a common place to hide instructions. You can still act on it by its id or a CSS selector, as before; with --js, an action on a control that has no box on screen fails E_NOT_ACTIONABLE (reason hidden). Because its label is withheld, --role button:<name> does not find it.

A label taken from other elements (aria-labelledby) leaves out any of them that is hidden. jawb wait --for text:... follows the same rules: it waits for text a person can see, so text that exists only in hidden content never ends the wait (it times out).

Content marked aria-hidden="true" is also left out, but jawb cannot tell whether it is on screen, so it is counted separately.

--json output carries what was left out (counts only, never the text):

"visibility": {"basis": "inline_only", "omitted": {"hidden": {"display_none": 2}, "unknown": {"aria_hidden": 1}}}

basis: "inline_only" is a warning label: jawb did not apply the page's stylesheets, so text hidden with a CSS class still comes through. Treat everything a page says as untrusted, whatever this field says. With --js the basis is cascade: the browser's computed display, visibility, opacity and font size decide, including stylesheet rules, but jawb does not yet check size, position or overlap.

JS-rendered pages

Without --js, observe runs against the static HTML the daemon fetched — it doesn't execute JavaScript. On a page that looks script-dependent, jawb warns:

warning: W_JS_NOT_EXECUTED: the page looks script-dependent; the native engine does not run JavaScript (re-run with --js to use the compatibility bridge)

Re-running with --js promotes the tab to the compatibility bridge (a real, locally installed Chrome/Chromium driven headlessly), which executes the page's JavaScript before observing:

$ jawb observe --js --interactive-only
warning: tab promoted to bridge
Apply: Rust Engineer  http://.../apply.html  (rev 2, 5 of 11 elements)
  [4] textbox "Full name" = ""
  [6] textbox "Email" = ""
  [8] combobox "Team" = "Backend"
  [10] textbox "Resume"
  [12] button "Send application"
rev 1 → 2 (bridge, 810ms)

A tab promoted to the bridge stays on the bridge for the rest of its life — every later command on that tab (including navigation) runs through Chrome instead of the native parser.