title: "The Context tab" description: "What is open, what is on each page, the set algebra over tabs, and exporting them." nodes: 17 progress: 94% generated: ohm spec editor#

The Context tab#

What is open, what is on each page, the set algebra over tabs, and exporting them.

Contents#

☑ Overview#

☑ Scope#

note

packages/core/src/page-inspect.ts, packages/core/src/context-export.ts, the context.* protocol methods, apps/ui/src/components/ContextPane.tsx, and the tab.inspect / tab.markdown / context.* host API.

☑ One extraction, two consumers#

decision

The panel's per-tab breakdown and the tab.inspect() scripts call are the same code: a single expression evaluated in the page, returning the outline, links, inputs, buttons, forms and counts.

Two implementations of "what is on this page" would disagree within a week, and the panel's job is partly to teach the script API — it can only do that if what it shows is what a script gets.

☑ Reading a page#

☑ A password value is never read#

requirement #security #regression

Inputs of type password are listed — type, name, label, selector — with a null value. The field is useful to know about; its contents are not Ohm's to copy out of the page, and a feature that quietly harvested them would be indefensible whatever it enabled.

☑ Everything is capped in the page, not after#

requirement #regression

300 links, 200 inputs, 200 buttons, 50 forms, 100 headings, and every string clipped to 200 characters — all applied inside the evaluated expression, before anything crosses the debugger. A shopping page with ten thousand links must not become a ten megabyte CDP message.

What was cut is named in truncated, which the panel and the markdown both show. Silently returning the first 300 of 9000 links would be worse than refusing.

  • node: one-extraction

☑ Every control carries a selector that still finds it#

requirement

#id when there is one, then tag[name=…], then tag:nth-of-type(n) scoped to an identified parent. The point is that reading and acting are the same surface: what inspect() lists, tab.click() and tab.type() can use.

☑ One unreadable tab does not lose the others#

requirement #safety

context.inspectAll and context.export report a per-tab error — discarded, crashed, navigated mid-read — and return everything else. The panel says how many failed and why.

☑ Tabs as a set#

☑ Checked tabs, a stored set, and the operators between them#

component

The panel holds two collections: the checked tabs and a stored set. Set ← checked fills one from the other; ∪ ∩ − ∖ combine them; shuffle, rotate and reverse reorder, matching S.Shuffle, S.Rotate and S.Reverse exactly.

Results keep tab order rather than click order, so the same two clicks always give the same set.

  • spec: 02-ohm-language

☑ Every action shows the Ohm that would do it#

decision

The panel renders the current set as \Tab \times urls, with Copy and Run it. Clicking is meant to teach the notation, not to substitute for it: the shortest path from "I did this by hand" to "this runs every morning" is seeing the expression that matches what you just clicked.

☑ context.selection() in a script#

api

The UI pushes its selection to core (context.setSelection), which a script reads as context.selection() — a matrix of tabs, so \times and every S.* operator apply — plus context.inspect() and context.expression().

Core owns the selection; the panel and the script host read the same value, so the two can never disagree.

  • spec: 03-scripts

☑ Export#

☑ Four formats, two destinations#

api

html (the page's own outerHTML), text (innerText), markdown (the inspection: outline, forms, buttons, links) and json (the inspection as data). To a chosen folder, one file per tab named from the page title with an index prefix so two identical titles cannot overwrite each other — or returned for the clipboard.

☑ Review findings#

☐ Export into a project needs the active project threading through#

task

context.export accepts a project and will write into its files/, but the shell does not yet pass the active project down to the panel, so the button always asks for a folder. Thread it from ProjectsPanel through App.

☑ The Context view had to opt out of the .view layout#

note #regression

.view is a column flex box that sizes to its content, which left the panel 499px wide inside an 887px slot — the tab list squeezed to 151px while the console showed through beside it. .context-view sets width: 100% explicitly.

Measured, not guessed: the layout looked plausible in the screenshot until the element widths were read out of the running app.

graph LR
  everything_is_capped_in_the_page_not_after["Everything is capped in the page, not after"] -.-> one_extraction["One extraction, two consumers"]