Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f3a5e62
feat(ui-core): add browser asset-source, acquisition and active-targe…
JArmandoAnaya Sep 17, 2026
5c7d699
feat(ui-core): compute suggest blockers per active target, not per co…
JArmandoAnaya Sep 17, 2026
d957ca8
feat(ui-core): add refusal prose for browser negative-point requests
JArmandoAnaya Sep 17, 2026
ee5d0b9
feat(ui-core): feed the displayed asset to the browser inference runtime
JArmandoAnaya Sep 17, 2026
7fa1d27
feat(ui-core): route suggestions through the active target, server or…
JArmandoAnaya Sep 17, 2026
911f94f
fix(ui-core): fall back to Server silently for a stale stored browser…
JArmandoAnaya Sep 17, 2026
8839e06
feat(ui-core): add a This device section to the suggest chooser
JArmandoAnaya Sep 17, 2026
0fd7c6a
feat(app): fetch and verify EfficientSAM-Ti artifacts against pinned …
JArmandoAnaya Sep 17, 2026
0c4f688
feat(app): add the race-safe browser suggestion executor
JArmandoAnaya Sep 17, 2026
64ec4ba
fix(app): retry the encode after a failed prepareImage instead of cac…
JArmandoAnaya Sep 17, 2026
6f95ba0
fix(app): hold one embedding slot, and pin the prompt and tolerance w…
JArmandoAnaya Sep 17, 2026
74be52d
feat(app): add the concrete browser inference runtime and acquisition…
JArmandoAnaya Sep 17, 2026
e7b837d
feat(app): inject the browser inference runtime into the OSS session
JArmandoAnaya Sep 17, 2026
e07405c
test(scripts): ban @visionset/browser-inference and CDN literals from…
JArmandoAnaya Sep 17, 2026
18f0494
test(scripts): add missing CDN violation examples for cloudfront, ama…
JArmandoAnaya Sep 17, 2026
48e49d1
test(scripts): ban CDN/vendor literals from annotator and browser-inf…
JArmandoAnaya Sep 17, 2026
a9ccb4a
test(app): extract serveApi/openJob into a shared _wireApiStub
JArmandoAnaya Sep 17, 2026
a057346
test(app): add hermetic Playwright coverage for browser suggestion
JArmandoAnaya Sep 17, 2026
53f19eb
fix(ui-core): let a browser target's chooser survive its own not-read…
JArmandoAnaya Sep 17, 2026
7da9256
fix(ui-core): dedupe the runtime-wired check and give the blocked ser…
JArmandoAnaya Sep 17, 2026
663e92f
test(app): fix browser suggestion e2e review findings
JArmandoAnaya Sep 17, 2026
128c189
fix(ui-core): don't call executorFor on a browser target that isn't r…
JArmandoAnaya Sep 17, 2026
4fa0181
fix(ui-core): derive executor readiness from blocker, and make the re…
JArmandoAnaya Sep 17, 2026
c3b9328
ci: run the browser suggestion e2e suite in the browser-models job
JArmandoAnaya Sep 17, 2026
79bd155
test(app): assert the acquired target's modelRef carries the pinned r…
JArmandoAnaya Sep 17, 2026
d46ff0a
fix(app): match the real manifest's nested artifacts schema
JArmandoAnaya Sep 17, 2026
6f2e4be
test(app): match browserSuggestion's CDN fixture to the real manifest…
JArmandoAnaya Sep 17, 2026
be7bd7a
fix(app): dedupe the CDN URL prefix, validate manifest shape, and gua…
JArmandoAnaya Sep 17, 2026
27553b2
fix(browser-inference): make the ORT wasm asset path survive bundler …
JArmandoAnaya Sep 17, 2026
5e05107
fix(app): build browser-inference before serving the e2e suite
JArmandoAnaya Sep 17, 2026
de961d0
test(app): serve a real correctly-sized asset image in browserSuggestion
JArmandoAnaya Sep 17, 2026
1e10937
fix(browser-inference): move the asset-resolution doc-comment to the …
JArmandoAnaya Sep 17, 2026
efb2d7c
fix(app): serve browser-inference's ORT assets in production builds
JArmandoAnaya Sep 17, 2026
38cefdd
test(packaging): guard that the ORT runtime travels inside the wheel
JArmandoAnaya Sep 17, 2026
8aa1623
docs(packaging): correct MAX_WHEEL_BYTES' stated numbers
JArmandoAnaya Sep 17, 2026
3eccb12
fix(app): hold one executor, and wait for the ORT session before clai…
JArmandoAnaya Sep 17, 2026
1d63104
fix(app): stop reporting browser-local failures as "the server could …
JArmandoAnaya Sep 17, 2026
34ae80d
ci: wake the browser-models job for everything its e2e suite exercises
JArmandoAnaya Sep 17, 2026
89b196f
refactor(ui-core): unpublish computeSuggestBlocker, and stop inviting…
JArmandoAnaya Sep 17, 2026
890b497
docs: catch up with a browser inference port that now has a host
JArmandoAnaya Sep 17, 2026
3dbb98d
ci(app): build browser-inference before the clean-clone cycle suite
JArmandoAnaya Sep 17, 2026
c49ea85
fix(ui-core): hand the browser executor the descriptor's frame, not t…
JArmandoAnaya Sep 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/path-filters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ docs:
# `python` -- and this file says outright that a path may appear in several.
browser-models:
- "frontend/browser-inference/**"
- "frontend/app/e2e/browserSuggestion.spec.ts"
# Everything that suite actually exercises. Naming only the spec would have been
# the usual trap: `browserSuggestion.spec.ts` is the one place a real ONNX model
# runs in a real browser, and it self-skips in the `frontend` job (no artifacts, no
# `VISIONSET_REQUIRE_BROWSER_MODELS`), so a change to the composition root or to the
# panel it drives would have been "covered" by a job that ran nothing.
- "frontend/app/src/data/browserInference/**"
- "frontend/app/vite.config.ts"
- "frontend/app/playwright.config.ts"
- "frontend/ui-core/src/inference/**"
- "frontend/ui-core/src/annotator/SuggestPanel.tsx"
- "frontend/ui-core/src/annotator/AnnotationPage.tsx"
- "scripts/browser_models/**"
- "tests/browser_models/**"
- "pyproject.toml"
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,14 @@ jobs:
if: needs.changes.result != 'success' || needs.changes.outputs['browser-models'] == 'true'
run: pnpm --filter @visionset/browser-inference test:browser

- name: Install the app's Playwright browsers
if: needs.changes.result != 'success' || needs.changes.outputs['browser-models'] == 'true'
run: pnpm --filter @visionset/app exec playwright install chromium

- name: Run the browser suggestion e2e suite, with the model present
if: needs.changes.result != 'success' || needs.changes.outputs['browser-models'] == 'true'
run: pnpm --filter @visionset/app e2e browserSuggestion

# The step that makes this job's green check mean something. Everything above answers
# "did what ran succeed?"; a suite that skips itself, or a spec file that stopped being
# collected at all, answers that with a cheerful yes. This reads the JSON report the
Expand Down
47 changes: 31 additions & 16 deletions docs/content/architecture/frontend/ui-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,22 @@ concrete adapter, and it discovers none either.

Absence is the ordinary case, so the hook answers `null` and never throws - the rule stated
twice above, for a third capability. **A host that offers browser inference composes the
runtime at the host boundary; a host that does not offer it supplies nothing.** No host in this
repository supplies one, so every build behaves as it did before the port existed.

The port is two members wide on purpose, and it is an *execution* contract rather than a
catalog: finding and obtaining a model is a question about things that cannot answer yet, and
this is not where it is answered. Nor is the narrowness a promise that growth is free - the
interface is published, so adding a **required** member to it would break every host
implementing the older one, and
runtime at the host boundary; a host that does not offer it supplies nothing.** `frontend/app`
now supplies one: `data/browserInference/BrowserInferenceRuntime.ts` composes a real
`VisionSetBrowserInferenceRuntime` over `@visionset/browser-inference` and passes it through
`OssSession.tsx` unconditionally. A host that omitted it would still behave exactly as it did
before the port existed.

The port is four members wide, and it is an *execution* contract rather than a catalog: the two
required members - `listTargets` and `executorFor` - are only about asking a model that can
already answer. The two added since are optional, which is what let them arrive without
breaking a host implementing the older interface: `listAcquisitions?()`, which names models this
device could run once their bytes are fetched, and `setActiveAsset?()`, which tells the runtime
which asset is on screen so an executor's staleness checks have something to check against. That
is exactly how
[browser inference is host-injected](../decisions/browser-inference-is-host-injected.md) says
how it is grown instead.
the interface is grown - the published shape means a new **required** member would break every
host implementing the older one, so growth is additive and optional.

> An `InferenceConnection` is a model the VisionSet server has. A browser inference runtime is
> something this browser can do. They are not two spellings of one idea, and neither is
Expand All @@ -108,9 +114,10 @@ reference for one asset from reading pixels of the next asset either through a r
through the detached one a host's real asset-switch (unmount the old canvas, mount a fresh one)
leaves behind.

This is resource plumbing only. It neither selects a browser model nor exposes an inference
target — no capability in this phase lets a host ask for local inference at all; composing pixels
with a browser runtime remains a later host decision.
This is resource plumbing only: it neither selects a browser model nor exposes an inference
target. Composing the two is the host's decision, and `frontend/app` now makes it — the lease
reaches the browser runtime through the port's `setActiveAsset`, and the executor behind
`executorFor` is what reads `readRgb`.

**What is proved, and what is not claimed.** `e2e/assetPixels.spec.ts` drives a real Chromium
against a tiny runtime-generated image and confirms, in that browser, on that image: one content
Expand All @@ -126,10 +133,18 @@ rounding loss on partially-transparent pixels, EXIF-orientation auto-rotation (a
image's EXIF orientation when decoding to canvas; the server's direct-bytes decode path does not),
and AdobeRGB ICC-profile color management (a browser color-manages a tagged profile toward sRGB on
decode; the server's path does not). None of these are exact figures worth repeating here — they
are a known limitation, not a benchmark — and no consumer of this lease has yet needed to reconcile
them, since nothing in this phase reads pixels for inference. A future phase that feeds this lease's
`readRgb` output to a model must account for these divergences before treating browser-decoded
pixels as equivalent to the server's own decode of the same asset.
are a known limitation, not a benchmark.

**This is now a live limitation, not a future one.** The browser suggestion executor feeds this
lease's `readRgb` output straight to a model, so the pixels "This device" segments are the
browser's decode and the pixels the server segments are Pillow's. **Server and "This device" can
therefore return different masks for the same asset and the same click**, and for an asset
carrying EXIF orientation or an AdobeRGB profile the difference can be large rather than
marginal — an auto-rotated decode is not a variation on the same picture. Nothing reconciles
the two, and nothing in the editor tells a person which decode answered. Treat a browser
suggestion as this browser's answer about this browser's decode; it is not a claim about what
the server would have said. Closing the gap means changing a decode, not adding a tolerance,
and neither side has been changed here.

## Asking for a suggestion is not sending one

Expand Down
26 changes: 23 additions & 3 deletions docs/content/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,9 +599,10 @@ object list is how a lane is selected, which is a real affordance rather than a

The sparkles button - hotkey `S` - arms the **suggest tool**: click the thing you
want and a segmentation model proposes its shape, which you can then adjust
before accepting. It runs through a model
connection (`docs/content/inference.md`), and the server side of it is
`POST /inference/suggest`.
before accepting. It answers from one of two places - a model connection on the
server, or a model running in this browser - and the panel carries that choice.
Through a connection it is `POST /inference/suggest`
(`docs/content/inference.md`).

**It runs through a connection that can answer a click**, which is a narrower set
than "the ones that are ready": only those declaring `point_suggest`. A workspace
Expand All @@ -619,6 +620,25 @@ there is no control at all, only a line naming what is answering. The picker
appears on the idle card alone: changing which model answers while a proposal is
on screen would leave a shape nothing on the card explains.

**Server, or this device.** The panel's two tabs are where a click goes. **Server**
is the connection described above. **This device** runs a smaller segmentation model
in this browser, which answers without the picture or the click leaving the machine
and without a connection being configured at all - useful where the workspace has no
model that can answer a click, or where the server is slow to reach.

It has to be downloaded first, and the panel says so: about 41 MB, on an explicit
press, never on its own. **The download does not survive a page reload.** It is held
for the session only, so reopening the editor tomorrow - or reloading today - means
downloading it again before "This device" can answer. Until then the tab shows the
Download button rather than inviting a click that would do nothing, and the choice
falls back to Server.

**Alt-click needs Server.** The model running here takes only points that are *on*
the object; a point marking something that is not part of it is refused rather than
answered with something that is not an exclusion. Switch to Server to refine that
way. Everything else - the first click, refining with more points, the tolerance,
`↵` and `Esc` - works the same on both.

The gesture:

| Press | What it does |
Expand Down
Loading
Loading