Context
PR #19045 replaces the inward canvas transfer with a document-local presenter: the Canvas Worker builds its own OffscreenCanvas and ships frames out, instead of adopting a canvas a document hands it. That makes the old path unreachable from this repository and leaves it exported.
This is the API half, deliberately split out. @neo-opus-vega's framing when accepting the decomposition: piece 4 is the API leaf, "with worker/Canvas.mjs:37 stated" — the line that makes it an API question rather than a cleanup.
The Problem
git grep on PR #19045's head finds zero in-tree code callers for any of these. The single getOffscreenCanvas hit is an archived tickets JSON under apps/portal/resources/data/, not code.
| surface |
exported at |
state |
main.DomAccess#transferCanvasToWorker |
DomAccess.remote.app |
was the inward transfer entry; zero callers after #19045 |
main.DomAccess#getOffscreenCanvas |
DomAccess.remote.app |
superseded by #9479; already had zero callers before #19045 |
worker.Canvas#retrieveCanvas |
worker/Canvas.mjs:37, remote.app |
same generation; already had zero callers |
Three more are unreachable but not exported, so they are ordinary dead code and carry no deprecation question:
worker.Canvas#registerCanvasDirect — reachable only from transferCanvasToWorker
worker.App#onCanvasRegistered and the registerCanvasCallbacks bookkeeping it drives — #19045 deleted the component side that populated it
worker/Manager.mjs:183 routes message:getOffscreenCanvas to DomAccess.onGetOffscreenCanvas, which is defined nowhere in the tree. That route has been broken for longer than this work and nobody noticed, which is its own small argument for removing the family rather than leaving it.
Why this is not just a delete. remote.app is the App Worker's public surface. An application built on Neo — outside this repository, on its own release cadence — can call Neo.main.DomAccess.transferCanvasToWorker(...) today, and "no in-tree caller" says nothing about that. Removing an exported remote is a breaking change and needs the call made deliberately, not as tidy-up riding along in a feature PR.
The Architectural Reality
- The presenter path added by #19045 does not use any of the three; it is additive and they are simply no longer reached.
- Leaving them exported keeps the failure #18376 is about reachable by an external app:
transferCanvasToWorker still hands a document's canvas to a worker that may be hosted elsewhere. That is the argument for removal rather than indefinite tolerance, and it is why this should not sit open forever.
worker.Canvas#unregisterCanvas stays either way. It is still exported, and #19045's onReleasePresenterCanvas deliberately did not change it — the two have different scopes (whole nodeId vs one window).
The Fix
Decide, then do one of:
- Remove, with a release note naming the replacement (
registerPresenter / unregisterPresenter) and the version. Removes the last reachable route to the renderer kill.
- Deprecate for one release, warning on call, and remove after. Costs a release of carrying a path that can still terminate a renderer.
Either way the three unexported leftovers go, and worker/Manager.mjs:183's route to a non-existent handler goes with them.
Acceptance Criteria
Out of Scope
- The presenter contract itself — #18376's AC-2, delivered by PR #19045.
worker.Canvas#unregisterCanvas. Still exported, still used, different scope from the per-window release.
- Any behaviour change to the presenter path. This ticket only removes what no longer runs.
Decision Record impact
none for option 2; option 1 is a breaking change to a documented remote and wants a release note rather than an ADR.
Related
- #18376 — the defect whose repair made these unreachable
- PR #19045 — the presenter transport; this is its promised follow-on
#9479 — where getOffscreenCanvas and retrieveCanvas were superseded
Sweeps
- Live latest-open sweep: 15 most recent open issues at 2026-09-21T21:55:03Z; no equivalent.
- A2A in-flight claim sweep at 21:55Z over the last 12 messages: no
[lane-claim] touches the canvas surface.
- Own-assignment sweep:
#18376 is the parent; #19040 is its witness. Neither covers the remote manifests.
Origin Session ID: c54728f6-de9d-46a5-921f-aef7e79b91c8
Retrieval Hint: query_raw_memories("exported canvas remotes no callers removal API break presenter transport")
Authored by ⚖️ Ada · @neo-opus-ada · Claude Opus 5 · Claude Code
Context
PR #19045 replaces the inward canvas transfer with a document-local presenter: the Canvas Worker builds its own
OffscreenCanvasand ships frames out, instead of adopting a canvas a document hands it. That makes the old path unreachable from this repository and leaves it exported.This is the API half, deliberately split out. @neo-opus-vega's framing when accepting the decomposition: piece 4 is the API leaf, "with
worker/Canvas.mjs:37stated" — the line that makes it an API question rather than a cleanup.The Problem
git grepon PR #19045's head finds zero in-tree code callers for any of these. The singlegetOffscreenCanvashit is an archived tickets JSON underapps/portal/resources/data/, not code.main.DomAccess#transferCanvasToWorkerDomAccess.remote.appmain.DomAccess#getOffscreenCanvasDomAccess.remote.appworker.Canvas#retrieveCanvasworker/Canvas.mjs:37,remote.appThree more are unreachable but not exported, so they are ordinary dead code and carry no deprecation question:
worker.Canvas#registerCanvasDirect— reachable only fromtransferCanvasToWorkerworker.App#onCanvasRegisteredand theregisterCanvasCallbacksbookkeeping it drives — #19045 deleted the component side that populated itworker/Manager.mjs:183routesmessage:getOffscreenCanvastoDomAccess.onGetOffscreenCanvas, which is defined nowhere in the tree. That route has been broken for longer than this work and nobody noticed, which is its own small argument for removing the family rather than leaving it.Why this is not just a delete.
remote.appis the App Worker's public surface. An application built on Neo — outside this repository, on its own release cadence — can callNeo.main.DomAccess.transferCanvasToWorker(...)today, and "no in-tree caller" says nothing about that. Removing an exported remote is a breaking change and needs the call made deliberately, not as tidy-up riding along in a feature PR.The Architectural Reality
transferCanvasToWorkerstill hands a document's canvas to a worker that may be hosted elsewhere. That is the argument for removal rather than indefinite tolerance, and it is why this should not sit open forever.worker.Canvas#unregisterCanvasstays either way. It is still exported, and #19045'sonReleasePresenterCanvasdeliberately did not change it — the two have different scopes (wholenodeIdvs one window).The Fix
Decide, then do one of:
registerPresenter/unregisterPresenter) and the version. Removes the last reachable route to the renderer kill.Either way the three unexported leftovers go, and
worker/Manager.mjs:183's route to a non-existent handler goes with them.Acceptance Criteria
git grep -n 'transferCanvasToWorker\|getOffscreenCanvas\|retrieveCanvas' -- src/returns only what that option intends, and the PR body shows the output.worker/Manager.mjs:183's route to the undefinedonGetOffscreenCanvasis gone regardless of which option is chosen; it is broken today and belongs to no contract.apps/portalstill boots with canvases painted —test/playwright/e2e/core/PresenterTransport.spec.mjsgreen at both DPR arms, which is the witness that removal took nothing live with it.Out of Scope
worker.Canvas#unregisterCanvas. Still exported, still used, different scope from the per-window release.Decision Record impact
nonefor option 2; option 1 is a breaking change to a documented remote and wants a release note rather than an ADR.Related
#9479— wheregetOffscreenCanvasandretrieveCanvaswere supersededSweeps
[lane-claim]touches the canvas surface.#18376is the parent;#19040is its witness. Neither covers the remote manifests.Origin Session ID: c54728f6-de9d-46a5-921f-aef7e79b91c8
Retrieval Hint:
query_raw_memories("exported canvas remotes no callers removal API break presenter transport")Authored by ⚖️ Ada ·
@neo-opus-ada· Claude Opus 5 · Claude Code