This repository was archived by the owner on Oct 4, 2026. It is now read-only.
Repository navigation
refactor(canvas): admit edits before they mutate, own raster memory, and add a color picker loupe - #392
Merged
Conversation
… surface Base, derived, group and decoded stores keep running byte totals; working-set eviction keeps pinned and unsaved pixels as accounted overage. Raster reads return pinned RasterReadLeases; a dirty layer without a cache now fails the paint barrier instead of saving nothing. Decodes refuse pre-aborted requests, cooldown/disposal release group surfaces, and closing a project crosses its paint barrier first.
A genuine tool switch cancels the active drag through the outgoing tool and drops its tail; reset cancels before restoring a held tool. Enter and Space act only while the canvas surface owns focus, ignoring controls, prevented events and modifier chords. Freehand lasso/shape spacing is measured in CSS pixels, and the cursor ring follows size changes only.
Value edits locate nodes through the index, copy only the sibling arrays on each edited path and record the replaced nodes; the mirror folds those records instead of diffing forests, and outermostNodes and move-overlay overrides resolve by id. The engine prunes group surfaces only on restructuring.
Stroke samples append immediately with incremental decimation; the accumulated stroke renders in a scheduler task that runs before the frame's flags are taken, and pointerup renders the final stroke.
The readback only feeds the coverage scan and outline trace; the mask takes its pixels by drawImage.
Layer caches expose a revision; the sampler skips repeated pixels of an unchanged document and cache revision, and compositeDocument accepts a prepared draw plan.
Merge Down of empty layers keeps an empty cache; pixel-edit rollbacks reinstate caches through the store; cooldown keeps pinned and held caches. Read leases capture guards before pinning and release the source pin once a copy exists; pinned layers persist untrimmed. Open-project flushes cross the paint barrier through the renamed LiveCanvasEngines registry, and close re-crosses it after the push.
…de group scopes Damage is full, none or bounded: offscreen damage draws nothing and bounded repaints cull layers, groups and previews; one cached frame description (matrices, bounds, scopes) feeds frame demand, compositing and sampling. Grouped filter previews and floats compose inside the group surface, which now refreshes only member-damaged regions in place; compositing requires the raster backend. Overlay chrome draws in CSS pixels under a DPR base transform, and the viewport owns sub-one and fractional ratios for both backing size and view.
…t on open edits Close, export and duplicate cross the barrier through the open-project handle; rename keeps its tolerant flush. Document-write barriers fail fast with a "finish or cancel the current edit" reason instead of polling held pixels.
…float drags Group damage pads one source pixel before the member matrix; a moved float damages its old and new landing. Previewed layers stay resident, leaf bounds follow document size, and staged outlines and overlay shade survive bounded repaints.
…fore tool switches Space, Alt, C and Escape reach the canvas when focus arrived by pointer; keyboard-reached controls, fields and open overlays keep them. The widget passes its keyboard root to attach; a genuine switch restores the held tool first, and cancelled drags keep the cursor tracking.
Batched layer configs apply as one value edit; folding stops past a quarter of the forest; tools must schedule stroke frames through the engine. Adds coverage for group lock/hidden flips, group prune on delete, incremental decimation, override order and shared preparation reuse.
Float landings expand one source pixel before the placed matrix; float drags on scaled members are covered.
A colour-sample request ends any temporary hold before recording the tool to return to; the pointer-focused control is held weakly.
…ring a hold The surface test starts on brush and owns Space only through its keyboard root; choosing the held tool again deactivates nothing.
Export adjusts the region a layer's output samples before resampling, matching the display; downscaled layers stay bounded by the output size.
…ry failure-atomically Edits run as admitted transactions: history and raster bytes are reserved first, steps are verified, and refusals (over-budget, busy, stale) reach users as notices. Undo/redo are async and failure-atomic, pixel replays rasterize untrustworthy caches first, previews run as owned sessions, and merge down, rasterize, mask clear/invert and transform bake are undoable steps. Removes no-op bitmap tracking and a pass-through engine hook, narrows interaction writes to settings, renames the pixel epochs, and lets export reuse the display's adjusted copies.
Edit-step builders, the refusal notice adapter and the damage constant module join the editor startup graph; the removed surface budget and engine-hook re-export leave it.
A failed rasterization keeps the step unpersisted and reported; an empty source replaces a stale cache before the patch. Clarifies that export's adjusted copy is built on a miss.
joshistoast
requested review from
JPPhoto,
Pfannkuchensack,
blessedcoolant and
lstein
as code owners
October 1, 2026 02:57
editSteps.ts owns byte accounting, replay reservations with one refusal message, layer add/replace/remove steps and new paint layers. Rejected publications map explicitly: layer operations report failed, guarded results report stale.
A float is admitted for its commit footprint before it cuts anything and grows that admission, from geometry, with each accepted move, scale or rotation; a transform that does not fit is refused once and the float stays put. A refused lift no longer falls back to moving the layer; cooldown banks a live float, and every terminal path returns the admission after the hole is refilled.
The filter session publishes typed error codes for refused applies, unreadable or stale sources and unexpected failures, keeping a failure's message as technical detail. FilterOptions translates them through an exhaustive key map, so a visible error follows a language change; English catalog entries cover every code.
The React Compiler cached render-time engine reads, so Filter stayed disabled after a fill until another layer was selected. useCanvasEngineRead takes those reads fresh on every render and pixel publish (Filter, layer menu merge/group/delete, merge visible, control-layer checks); merge-visible stops at its second contributor.
The engine publishes a documentEpoch once the mirror has reacted to a document change, and useCanvasEngineRead re-reads on it. Control-layer position warnings and the other routed reads now follow edits to other layers, such as reordering or toggling a parent group.
… into refactor/canvas-engine-overhaul
The English catalog grew with main's strings and the new filter refusal messages; startup requests dropped by nine on editor routes.
The first generator test waited one second for a lazy chunk that CI's dev server could still be transforming.
The picker shows a loupe of the composited pixels around the pointer with the sampled pixel boxed and its RGB values, sampled once per overlay frame through the pick's own sampler. Hover chrome leaves the canvas with the pointer, and input handlers stay stable so detach removes every listener attach added.
The color picker hides the system cursor over the canvas, so the boxed center pixel is no longer covered by a crosshair.
SegmentTabs hides the dividers beside a tab only while its panel is shown, so a collapsed strip keeps every divider.
joshistoast
enabled auto-merge
October 1, 2026 06:35
joshistoast
disabled auto-merge
October 1, 2026 06:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reworks the canvas engine for correctness, clear ownership and bounded memory, adds a color picker loupe, and fixes layer-panel controls that went stale. Before, edits could mutate first and fail to record undo, background work could evict pixels in use, undo could replay onto an evicted cache, frames recomposited everything, focused controls fought the canvas for keys, and layer actions such as Filter stayed disabled after a fill until another layer was selected. Now:
failed, guarded results asstale.main).useCanvasEngineReadreads them fresh on every render, pixel publish and document change (a new enginedocumentEpoch, published once the mirror has reacted).QA Instructions
From
invokeai/frontend/webv2with Node 22.14.0 and pnpm 10:pnpm run check:releasepassed end to end with the loupe (tsc, oxlint, oxfmt, the architecture policy, the node suite at 599 files / 9,290 tests and the Chromium suite at 268 files / 2,014 tests, the performance build and browser gates, and the accessibility journeys).src/features/workflow/ui/editor/InvocationFlowNode.browser.test.tsx("exports authored source images…") can fail when run in isolation, on unrelated branches too, and some browser tests time out only while other suites run concurrently. They fail onmaintoo; they passed in the final full run.Measurements (node unless noted):
updateNodesTracked+ derive)updateNodeValues)Export work stays bounded by the output size for downscaled layers.
Review
Resolved findings:
All of these have regression tests checked against the original defect.
Remaining limitations:
Compatibility / Rollout
Persisted data: no project or document format changes.
Canvas capability API:
history.undo/redo/stepByreturn promises with a replay status.beginStructuralPreviewsessions.interaction.setaccepts only setting keys.surface.attachtakes the keyboard root.All callers in webv2 are updated.
Performance baselines:
performance/browser-baseline.jsonpins the new startup modules explicitly. After mergingmain, route resources were re-recorded: startup "other" bytes grew 4.7 KB (the English catalog, frommainand the new filter messages) and editor routes make nine fewer startup requests.Canvas capability API (addition):
interactiongains a read-onlydocumentEpochsignal.Checklist
What's Newcopy (if doing a release after this PR)