Skip to content
DEV

Developers

The in-page API (window.__pixelpont). For checking what your AI can touch, or calling it from your own scripts.

Last updated:
On this page
  1. Overview
  2. Methods
  3. Connection
    1. help()
    2. connect()
    3. disconnect()
  4. State
    1. getState()
    2. setVisible()
    3. setActiveLayer()
    4. updateLayer()
    5. alignTo()
  5. Measure
    1. measure()
    2. getReport()
    3. showDiff()
    4. inspect()
    5. samplePixel()
  6. Compatibility

Overview#

From inside the page, window.__pixelpont lets you read and change PixelPont’s state and run measurements. It’s the same API your AI agent uses.

  • Every method returns a Promise
  • Everything except help() needs connect(passphrase) first. The passphrase is in the prompt you copy from the panel
  • Always available on localhost pages. On other sites you’ve allowed, only while an AI connection is open
  • On failure, methods resolve to { ok: false, error, hint } instead of throwing. hint says what to do next
Example
const pp = window.__pixelpont;
await pp.connect('<passphrase>');
const report = await pp.measure({ scope: 'viewport' });
Security

The limits of the passphrase (it can’t fully protect against other scripts on the same page) and how color-reading methods behave are covered in AI › Security.

Methods#

Generated from the definition table

Everything below is generated at build time from the extension’s definition table — the source of help(). It’s the same text your AI reads.

MethodSummary
help(topic?)this index, or details for one method
connect(passphrase, options?)open the connection (needed once before every other method except help)
disconnect()close the connection when the work is finished
getState()overlay / layer / calibration state
setVisible(visible)show or hide the whole overlay
setActiveLayer(id)switch which comp is drawn
updateLayer(id, patch)change position, scale, opacity, blend mode, lock, scroll sync, centering
alignTo(selector, compY)move the comp so that its row compY sits on an element's top edge
measure(options?)compare comp vs render, returns mismatched elements
getReport(options?)progress or result of the latest measurement (also ones run from the panel)
showDiff(mode)draw the differences on the page (boxes for the last measure(), or a heatmap)
inspect(selector, options?)one element in detail: position, size, colors, computed styles
samplePixel(x, y, options?)comp color and rendered color at one point

Connection#

help()

Signature
await window.__pixelpont.help(topic?)

this index, or details for one method

Arguments, return shape, example
help() returns the index. help("<method>") returns arguments, return shape and an example.
help("<method>", page) returns one short page (under 900 characters) of a long topic, for
tools whose return value is cut off; each page says how to get the next one.
Example: await __pixelpont.help("updateLayer")

connect()

Signature
await window.__pixelpont.connect(passphrase, options?)

open the connection (needed once before every other method except help)

Arguments, return shape, example
The API is closed by default. The user opens it by pressing "Copy AI prompt" in the
PixelPont panel and pasting the copied text to the AI; that text contains the passphrase.
passphrase: string from that text. options: { client?: string } — a short name of the
calling agent or tool (optional, shown in the panel as a self-reported label).
A browser automation tool that cannot operate the tab the user has open can open the same
site (same host and port) in its own tab and call connect() there: PixelPont then moves
to that tab, with the same comps. No focus change or click is needed for that.
Returns { ok, minutesLeft }. After a page reload the passphrase is forgotten by the page
and connect() is needed again with the same passphrase.
The connection closes after 30 minutes without calls, when the panel closes, or when the
page moves to another domain. A new passphrase then has to be copied by the user.
Errors not-connected / invalid-token mean the user has to copy and paste the prompt again.
Example: await __pixelpont.connect("<passphrase>", { client: "my-agent" })

disconnect()

Signature
await window.__pixelpont.disconnect()

close the connection when the work is finished

Arguments, return shape, example
Closes the API for this page and removes the boxes / color diff drawn on it.
Layer settings are left as they are. Returns { ok }.
Calling it at the end of a task is expected; the panel then shows that the AI finished.

State#

getState()

Signature
await window.__pixelpont.getState()

overlay / layer / calibration state

Arguments, return shape, example
Returns { ok, ready, domain, visible, activeLayerId, layers[], viewport }.
layers[]: { id, name, active, x, y, scale, opacity, blendMode, locked, syncScroll,
  autoCenterX, naturalWidth, naturalHeight, designWidth, renderedWidth }.
Only the active layer is drawn on the page. x / y / renderedWidth are CSS px.
designWidth is the comp image width in comp px; comp px is CSS px divided by scale.
When syncScroll is true, y is a document coordinate; otherwise a viewport coordinate.
When autoCenterX is true, the layer is centered horizontally and x is not used.
viewport: { innerWidth, innerHeight, scrollY, devicePixelRatio }, read at the moment of the
call. Some automation tools change the viewport temporarily (a debugging bar that appears
while the tool is attached, or a resize for screenshots), so the
values can differ from what is on screen; the width of an element (inspect().rect) is the
safer base for calculating a scale, and measure() returns the viewport it actually used.

setVisible()

Signature
await window.__pixelpont.setVisible(visible)

show or hide the whole overlay

Arguments, return shape, example
visible: boolean. Layer selection and settings are kept while hidden.
Example: await __pixelpont.setVisible(false) before taking a clean screenshot.

setActiveLayer()

Signature
await window.__pixelpont.setActiveLayer(id)

switch which comp is drawn

Arguments, return shape, example
id: a layer id from getState().layers. Returns { ok, activeLayerId }.

updateLayer()

Signature
await window.__pixelpont.updateLayer(id, patch)

change position, scale, opacity, blend mode, lock, scroll sync, centering

Arguments, return shape, example
patch fields (all optional): x, y (CSS px, finite numbers), scale (> 0),
opacity (0..1), blendMode (normal | difference | invert | multiply | overlay),
locked, syncScroll, autoCenterX (booleans).
Values are stored as given: toggling syncScroll does not convert y.
Changing blendMode without opacity may also change opacity: each blend mode remembers its own
(first use: difference / multiply / overlay 1, others 0.5), unless the user turned that off.
locked only makes the layer click-through and read-only in the panel; it does not block this method.
Returns { ok, id, applied, previous }: previous holds the values before the change for the
fields in patch, so updateLayer(id, previous) undoes it.
Example: await __pixelpont.updateLayer(id, { y: 120, blendMode: "difference" })

alignTo()

Signature
await window.__pixelpont.alignTo(selector, compY)

move the comp so that its row compY sits on an element's top edge

Arguments, return shape, example
selector: a CSS selector (first match). compY: a y coordinate in comp px.
Moves the active layer vertically only, and returns { ok, id, y, previousY }.
On long pages, small differences add up, so everything below drifts. Aligning the comp
to a section first shows which offsets are local to that section: measure() afterwards
reports them without the accumulated drift (commonOffset close to 0).
Example: await __pixelpont.alignTo(".pricing", 2480)

Measure#

measure()

Signature
await window.__pixelpont.measure(options?)

compare comp vs render, returns mismatched elements

Arguments, return shape, example
options: { scope?: "viewport" | "page" | "<css selector>", ignore?: string[],
  tolerance?: number, minConfidence?: number, layer?: { x?, y?, scale? },
  format?: "full" | "lines", settle?: number, limit?: number }.
An option that is not in this list is rejected (unknown-option), not ignored.
limit (1 to 30): how many items each list (mismatches, unmatched, colorMismatches,
borderline, groups) returns at most. The totals stay the same. Small values keep
repeated results short.
settle (ms, 500 to 10000): before measuring, wait until the page has shown no change
(DOM, stylesheets, size) for 300 ms, but no longer than this many ms. For CSS that was
just saved and may not be applied yet. The result then carries settle { waitedMs,
timedOut }; timedOut true means the page was still changing when the time ran out, so
the measurement may show a state in between. A change that has not started within
300 ms of the call is not waited for.
format "lines" returns a short text summary instead of the full result; see
help("getReport"). It suits tools that cut long return values.
layer overrides the layer position or scale for this measurement only; the settings the
user sees in the panel stay untouched (unlike updateLayer).
x and y are layer positions for the scale in use: when scale is overridden, the stored y
usually no longer fits (a header may change height with the page width), so y has to be
overridden too. For y: the top of the element (document coordinate when syncScroll is on)
minus the comp row that should sit there times scale. The comp row is 0 for the section
at the very top of the comp; for others it is the y of that section inside the comp image.
For scale: the width on the page of the area the comp shows (often the main column, not
the whole window) divided by designWidth. With autoCenterX the layer stays centered for
the overridden scale, so x does not need to be overridden.
scope defaults to "viewport". A selector limits the check to that element subtree;
only the part currently scrolled into view is measured.
scope "page" scrolls through the whole page (about 1 second per screen). It returns
{ ok, started: true } at once; getReport() gives progress, then the result.
During a page run, lazy images are loaded, and fixed / sticky elements are hidden while
they are stuck. The layer needs syncScroll: true and the window itself has to scroll (a
page that scrolls inside an element returns page-scrolls-inside-element). Elements taller
than the viewport are not measured (counted in tooLarge).
ignore: selectors to skip (photos, sliders, fixed headers). tolerance: CSS px, default 1.
minConfidence (0..1, default 0.1): offsets below it are counted in uncertain instead of
being listed. { tolerance: 0, minConfidence: 0 } lists every non-zero offset found.
Returns { ok, pass, breakdown, scope, tolerance, ignore, layer?, layerFix?, designWidth,
  scale, viewport, checked, skipped, total,
  mismatches[], borderlineTotal, borderline[], borderlineBand, uncertain, commonOffset,
  commonApplied, commonCount, unmatchedTotal, unmatched[], colorTotal, colorMismatches[],
  groups[], misaligned, outOfView, tooLarge, pinned, truncated, pageCut, notes[] }.
  Lists are largest first (unmatched: in document order), at most
  30 each (borderline 10).
pass is true only when at least one element was compared and there is no mismatch, no
unmatched element, no color mismatch, no element left out under a stuck fixed or sticky one,
nothing left out of view,
nothing cut off by a limit (truncated 0, pageCut false), and neither misaligned nor
commonApplied. borderline, uncertain and tooLarge do not fail it, so a strict check also
looks at breakdown.
truncated counts elements left unmeasured because one screen held more than 200
candidates; pageCut is true when a page taller than 60 screens left a range uncaptured.
breakdown counts every candidate element: matched, mismatched, unmatched, borderline, uncertain,
notAlignable (nothing to align on, or outside the comp), outOfView, overLimit, tooLarge, tooSmall,
coveredByFixedOrFrame (the last two are null for scope "page"), pinned.
The same counts under their older names: total is breakdown.mismatched (position mismatches,
not all elements; that is checked), skipped is notAlignable, truncated is overLimit.
unmatched[]: { selector, rect, styles } lists elements with no matching place
in the comp: what is drawn differs (other text or image, a missing or extra element) or
the offset exceeds the search range.
Their position and color are not compared. You know the code: decide whether the content is
meant to differ (then pass it in ignore) or the element is wrong.
pinned { total, selectors[], by[{ selector, position }] }: elements skipped because a fixed
or sticky element (by) is stuck over them after scrolling; the comp shows it where it sits
at the top of the page. At scrollY 0 fixed headers and sticky elements are measured as
usual. Scope "page" measures them where they rest (a header on the first screen, a sticky
heading before it sticks) and hides them while stuck; pinned then holds only what never
rested (a fixed banner, for example).
mismatches[] / borderline[]: { selector, rect, delta, deltaDesignPx, moveBy, confidence,
  styles, atLimit?, own? }.
commonOffset { x, y } (CSS px): the offset shared by at least half of the measured elements
(and 3 or more), or 0 when there is none. Not 0 means the layer position itself (or
everything above) is off by that much; commonApplied is then true and commonCount tells
how many elements share it. Elements off by only that amount are not listed, and the
listed ones carry own { x, y }: their offset with commonOffset removed (delta stays the
full difference from the comp). The value does not depend on tolerance or minConfidence.
layerFix { x?, y } comes with commonApplied: the layer position that removes commonOffset
(the position measured with, minus commonOffset; x is left out while autoCenterX is on).
measure({ layer: layerFix }) tries it for one measurement; updateLayer(id, layerFix) keeps it.
ignore repeats the selectors that were left out; layer { x, y, scale } is present when the
measurement used options.layer, so a result measured under conditions is not read as a
plain one.
borderline[] / borderlineTotal: elements above the tolerance by no more than borderlineBand
(CSS px, one captured pixel: 0.5 at device pixel ratio 2, at most 1). They flip between
runs, so they are kept out of mismatches. With tolerance 1 and band 0.5, 1.5 px is still
borderline; a stricter line can be drawn from the borderline list itself.
groups[]: { parent, moveBy, count, selectors[] } lists sets of two or more listed
mismatches that share a parent element and are off by exactly the same amount (own when
commonApplied). Such a set is usually fixed on the parent (its position, padding or gap)
rather than element by element.
uncertain counts offsets left out because the match was ambiguous.
rect is in viewport coordinates, or document coordinates for scope "page".
delta: how the rendered element differs from the comp, top-left anchored:
  y: 3 means the element sits 3px too high (moving it down 3px matches the comp);
  delta is CSS px, deltaDesignPx is comp px. x / y come from sliding the comp over the
  render to the best match (works over photos). width / height are only reported on
  plain backgrounds (width: 4 means the comp is 4px wider); otherwise they are 0.
Elements smaller than 16x8px or under fixed / sticky elements are not measured (counted
in breakdown). Elements larger than 40% of the viewport (backgrounds, containers) are not
measured one by one either, but reported in tooLarge { total, selectors[] }; inspect()
compares one of them on its own.
moveBy { x, y } tells how far to move the rendered element so that it matches the comp. It is
delta.x / delta.y, or own when commonApplied is true (the comp layer offset is not something
to fix on the element).
confidence (0..1) is low over photos, repeated patterns or crowded areas. As a guide:
0.6 or more is dependable, 0.1 to 0.6 is doubtful (worth a look with inspect()), below
minConfidence the offset is not listed (counted in uncertain).
Offsets larger than about 12 CSS px cannot be found. atLimit: true marks an offset that
reached that range: the value is not reliable (larger offset, or no match).
notes[] explains situations that make the result easy to misread: the whole comp being
far off (misaligned: true, colors then not compared), or elements outside the visible
area that were not measured (outOfView { total, selectors[] }, scope viewport / selector).
colorMismatches[]: { selector, rect, styles, ratio, render, comp } lists elements whose
fill differs from the comp after aligning positions (wrong color, missing gradient).
Colors are compared as 4px block averages, so text anti-aliasing does not count.
ratio is the share of blocks that differ; render / comp are the mean colors (#rrggbb).
inspect(selector) gives the dominant colors of one element.
The overlay is hidden and animations are paused during the capture, then restored.
The target tab must be the active tab of its window.
Example: await __pixelpont.measure({ ignore: [".hero__photo"] })

getReport()

Signature
await window.__pixelpont.getReport(options?)

progress or result of the latest measurement (also ones run from the panel)

Arguments, return shape, example
options: { format?: "full" | "lines", limit?: number }. Each one left out falls back to
what the measure() that produced the result was given (default "full", no limit).
limit (1 to 30): how many items each list returns at most.
format "lines" returns { ok, status, pass, format, lines[] }: one short line per element,
with no styles and no long selectors, for tools that cut return values at about 1000
characters or block some characters (the lines hold no equals sign and no ampersand).
The first line is the totals, the second the conditions (scope, tolerance, viewport width,
ignored selectors, layer override); then come notes on the comp offset and on what was not
covered; then "1 h2.title in section.hero: move x 0 y 3, confidence 0.8" for each
mismatch (move has the meaning of moveBy), "c1 ..." for each color mismatch and
"g1 parent ..." for each group. The element is written as its last selector part "in"
its parent part, which reads well but may not be unique: line n is mismatches[n - 1] of
the full format, and the full selector is there.
While running: { ok, status: "running", scope, progress: { done, total } } (screens).
When finished: { ok, status: "done", by, measuredAt, ...same fields as measure() }.
by is "ai" or "human" (a person pressed Measure in the panel).
Example: poll getReport() every second after measure({ scope: "page" }).

showDiff()

Signature
await window.__pixelpont.showDiff(mode)

draw the differences on the page (boxes for the last measure(), or a heatmap)

Arguments, return shape, example
mode: "boxes" | "heatmap" | "off". "boxes" outlines each mismatched element of the last
measure() with a label such as "1 ↓3px" (same meaning as moveBy), "u1 ≠" or "c1 color". The
number is the place in the result: n is mismatches[n - 1], u is unmatched, c is
colorMismatches (the same numbers as in format "lines"). It returns { ok, mode, boxes }.
"heatmap" captures the current viewport and paints every 4px cell whose color differs
from the comp (darker means a larger difference). It returns { ok, mode, cells, cellSize } and does not need
a measure() first. A screenshot taken afterwards works as a diff image.
The drawing is removed by showDiff("off") or by the next capture (measure, inspect, ...).

inspect()

Signature
await window.__pixelpont.inspect(selector, options?)

one element in detail: position, size, colors, computed styles

Arguments, return shape, example
selector: a CSS selector; the first match is used. Only the part on screen is compared.
options: { layer?: { x?, y?, scale? } } — the same temporary layer override as in measure().
Without it the layer settings of the panel are used, so after measure({ layer }) the same
layer has to be passed here too, or the two results will not agree.
Returns { ok, selector, rect, positionMeasured, unmatched, delta, deltaDesignPx, confidence,
  color: { diffRatio, maxDifference, render[], comp[] }, styles, parent }.
delta has the same meaning as in measure(). positionMeasured is false for plain areas
with nothing to align on (delta is then 0, colors are still compared).
unmatched is true when no matching place was found (the content differs, or the offset
exceeds the search range); delta is then 0 and not a measurement.
color.render / color.comp: the three most frequent colors as { hex, share }, usually the
background first and the text color next. diffRatio is the share of 4px blocks whose
average color differs (0 means the same fill), maxDifference the largest channel difference.
styles holds computed values (color, backgroundColor, backgroundImage, font, box, and
what often decides the position: position, top, left, translate, transform, gap).
parent: { selector, styles } gives the parent element and the values that place its
children (display, position, gap, justifyContent, alignItems, paddingTop, paddingLeft).
These are plain computed values; which one causes an offset is left to the reader.
color is null when the element overlaps an image from another origin: such colors are
never returned (error protected-area in samplePixel, and no cells in the heatmap), because
scripts on the page cannot normally read them. An element that holds or touches an embedded
frame is not compared at all (error protected-area), as in measure().
Example: await __pixelpont.inspect(".cta__button")

samplePixel()

Signature
await window.__pixelpont.samplePixel(x, y, options?)

comp color and rendered color at one point

Arguments, return shape, example
x, y: viewport coordinates in CSS px. Returns { ok, x, y, render, comp, difference }.
options: { layer?: { x?, y?, scale? } } — the same temporary layer override as in measure().
render / comp are #rrggbb averaged over one CSS px. difference is the largest channel
difference (0..255); about 24 or less is the same color to the eye.
Each call captures the page (about 0.6 s apart). Example: sampling a few points along a
gradient shows whether it is missing in the render.

Compatibility#

This page describes v0.1.0 (main as of 2026-10-09). While a release is in store review, this page may describe a version that isn’t out yet.

  • No compatibility guarantee. The measure() result in particular has many fields and changes often
  • Check the installed version’s spec with help()
  • Internal values such as the 12px search range may change with the implementation