The browser#

Ohm opens on a web page and behaves like a browser: a tab strip, an address bar, back, forward and reload. What it adds is hidden tabs, an activity bar of panels, and a console.

Tabs#

Ctrl+T opens a tab, Ctrl+W closes one. Typing in the address bar follows the usual rules: text with a scheme is a URL, bare localhost or an IP gets http://, other host-shaped text gets https://, and anything else becomes a search.

When you close the active tab, focus moves to the first visible tab at or to the right of it, falling back to the nearest one on the left — the same rule Chrome uses, and it skips hidden tabs.

A tab can be discarded to free memory: the page is destroyed but the entry stays in the strip with its title and URL, and clicking it loads the page again.

Hidden tabs#

Ctrl+Shift+H opens the hidden-tab drawer. A hidden tab is out of the strip but still running — background throttling is turned off for it deliberately, because hidden tabs are where scripts do their work:

local tab = browser.open("https://example.com", { hidden = true })
print(tab.title())
tab.close()

The deep-research pipeline runs entirely this way: it opens searches and fetches pages in hidden tabs, so your view never jumps around while it works.

From the drawer you can show, discard or close a hidden tab. Showing one makes it a normal tab — a tab can never be focused and hidden at the same time.

Popups (window.open, target="_blank") are never handed to the page: the shell intercepts them and opens a normal tab instead.

The activity bar#

Icon Panel Shortcut
∮ Scripts Ctrl+Shift+E
▦ Projects Ctrl+Shift+P
◐ Hidden tabs Ctrl+Shift+H
✎ Editor
◎ Context: open tabs, page structure, exports
? Help — this documentation, compiled into the app
⚙ Settings
▤ Console Ctrl+`

The page is a real Chromium view layered over the UI, which is why opening Settings or maximising the editor hides it entirely rather than shrinking it.

The console#

Ctrl+` opens a console that is both a log and a REPL. It shows output from every script run — print, log.info/warn/error, and the value of any bare expression — and you can type Lua or math straight into it:

ohm> print($\frac{22}{7}$)
3.142857143
ohm> x = 41
ohm> x + 1
42

Variables persist between lines. Stopping a run resets the session and says so, because stopping terminates the worker thread.

Click any error line to jump to that line in the editor.

Driving pages from a script#

Scripts control pages through the Chrome DevTools Protocol, so everything you can do by hand is scriptable:

local tab = browser.open("https://news.ycombinator.com", { hidden = true })
tab.waitFor(".titleline", 5000)
local items = tab.queryAll(".titleline > a")
for i, item in ipairs(items) do print(i, item.text) end
tab.close()

click, type, press, scroll, eval, screenshot and waitFor are all available. waitFor polls every 200 ms and throws on timeout; screenshots are written to .ohm/shots/ and the path is returned.

See The Ohm language and the script API.