From d5987322a61319a85b3780789725bc3bb26c5144 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Mon, 10 Aug 2026 13:03:25 -0700 Subject: [PATCH] Isolate No Paint transform sources --- nopaint/macos-native/main.swift | 2 +- nopaint/tests/macos-native.test.mjs | 1 + .../aesthetic.computer/disks/nopaint.mjs | 26 +++++++++++--- .../tests/nopaint-transform-source.test.mjs | 34 +++++++++++++++++++ 4 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 system/tests/nopaint-transform-source.test.mjs diff --git a/nopaint/macos-native/main.swift b/nopaint/macos-native/main.swift index 7a39da99e..e5b7abcb2 100644 --- a/nopaint/macos-native/main.swift +++ b/nopaint/macos-native/main.swift @@ -48,7 +48,7 @@ private final class NoPaintApp: NSObject, NSApplicationDelegate, WKNavigationDel window.makeKeyAndOrderFront(nil) installMenu() - loadFresh(false) + loadFresh(true) } func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { true } diff --git a/nopaint/tests/macos-native.test.mjs b/nopaint/tests/macos-native.test.mjs index 0ce43be06..dc3755e3c 100644 --- a/nopaint/tests/macos-native.test.mjs +++ b/nopaint/tests/macos-native.test.mjs @@ -17,6 +17,7 @@ test("the app is a focused native No Paint host", () => { test("New Painting requests a fresh full-stage No Paint canvas", () => { assert.match(source, /withTitle: "New Painting"/); + assert.match(source, /loadFresh\(true\)/); assert.match(source, /URLQueryItem\(name: "fresh", value: "1"\)/); }); diff --git a/system/public/aesthetic.computer/disks/nopaint.mjs b/system/public/aesthetic.computer/disks/nopaint.mjs index ddb1e4df8..51cf7cc73 100644 --- a/system/public/aesthetic.computer/disks/nopaint.mjs +++ b/system/public/aesthetic.computer/disks/nopaint.mjs @@ -461,6 +461,22 @@ function seedNoiseSubstrate(painting, seed) { } } +// The accepted artwork lives in disk.mjs's isolated system painting. Never +// derive transform input from the screen, proposal buffer, or retained layer +// metadata: each of those may contain presentation state or stale pixels. +export function cloneAcceptedPaintingPixels(system, width, height) { + const painting = system?.painting; + const expectedLength = width * height * 4; + if (!painting?.pixels || painting.width !== width || painting.height !== height || + painting.pixels.length !== expectedLength) { + throw new Error( + `No Paint accepted substrate mismatch: expected ${width}x${height}, got ` + + `${painting?.width || 0}x${painting?.height || 0}`, + ); + } + return new Uint8ClampedArray(painting.pixels); +} + function chooseProposal(api) { stopBrushCue(); decisionHeld = false; @@ -483,13 +499,15 @@ function chooseProposal(api) { base: baseProposal, }) : baseProposal; - // Pixel transforms operate on the accepted No Paint composite, never the + // Pixel transforms operate on disk.mjs's accepted sub-painting, never the // composited screen. The screen also contains the proposal UI and controls. - const acceptedPixels = api.system.nopaint.piece?.composite?.pixels || - api.system.painting.pixels; proposalPixels = compatibleBrush?.applyPixels ? compatibleBrush.applyPixels( - new Uint8ClampedArray(acceptedPixels), + cloneAcceptedPaintingPixels( + api.system, + resolution.width, + resolution.height, + ), resolution.width, resolution.height, proposal.brush.parameters, diff --git a/system/tests/nopaint-transform-source.test.mjs b/system/tests/nopaint-transform-source.test.mjs new file mode 100644 index 000000000..7348a0722 --- /dev/null +++ b/system/tests/nopaint-transform-source.test.mjs @@ -0,0 +1,34 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { cloneAcceptedPaintingPixels } from + "../public/aesthetic.computer/disks/nopaint.mjs"; + +test("pixel transforms clone only disk.mjs's isolated accepted painting", () => { + const accepted = new Uint8ClampedArray([ + 1, 2, 3, 255, + 4, 5, 6, 255, + ]); + const interfacePixels = new Uint8ClampedArray(accepted.length).fill(220); + const system = { + painting: { width: 2, height: 1, pixels: accepted }, + nopaint: { + buffer: { width: 2, height: 1, pixels: interfacePixels }, + piece: { composite: { width: 2, height: 1, pixels: interfacePixels } }, + }, + }; + + const source = cloneAcceptedPaintingPixels(system, 2, 1); + assert.deepEqual(source, accepted); + assert.notDeepEqual(source, interfacePixels); + source[0] = 99; + assert.equal(accepted[0], 1, "transform input cannot mutate the accepted painting"); +}); + +test("pixel transforms reject a mismatched painting instead of sampling another surface", () => { + assert.throws( + () => cloneAcceptedPaintingPixels({ + painting: { width: 1, height: 1, pixels: new Uint8ClampedArray(4) }, + }, 2, 1), + /accepted substrate mismatch/, + ); +}); -- 2.51.2