mountScenario now assigns the mounted renderer to
globalThis.playgroundAdapter and removes it on dispose, so the
wasm module is reachable from the browser console on every
playground page (for example playgroundAdapter.module._render_stats()).
The benchmark verify-pages diagnostic already used this name by
patching the served lib.js. The source is now the canonical
writer, so the wrapper's assignment is redundant.
AI-assisted-by: deepseek-flash
The playground called _init and _set_render_options with the DPR,
which sets the ratio but leaves the Skia target, backbuffer and
UI surfaces at CSS resolution. The canvas drawing buffer is
physical (CSS x DPR), so at DPR > 1 the smaller surface anchored
to the bottom-left of the framebuffer and left the top and right
edges unpainted.
Call _resize_viewbox after setting the DPR, the same sequence the
application uses in resize-canvas!, so the renderer surfaces
match the physical drawing buffer.
Cover DPR 1, 1.25 and 2 in the adapter test.
AI-assisted-by: deepseek-flash
Apply at most one _set_view + _render_from_cache per animation
frame in the wasm playground, accumulating the wheel zoom factor
and the last cursor point instead of rendering synchronously on
every wheel event.
This mirrors schedule-zoom! in
app.main.ui.workspace.viewport.actions so playground
interactions match the application. A comment records that the
playground should call the real function once it migrates to
ClojureScript.
Extend the adapter tests with the coalesced flush, factor reset
across frames, scale clamps, and per-move pointer pan.
AI-assisted-by: deepseek-flash
Compare every on/off combination of requested Cargo features over the
Boolean-lattice edges, in Gray-code build order, with a matrix index of
configs, edge comparisons and provenance.
Single runs record explicit feature sets as schemaVersion 2 with resolved
defaults and build environments. Edge comparisons stay diagnostic; edges
clean apart from the intended toggle are marked controlled.
Validate with 68 focused tests, CLI fault checks, dry runs, and container
smoke matrices for branch-b pan cases.
AI-assisted-by: muse-spark-1.3-contributor-free
Add a regression test that keeps 1000 shadowed shapes with two
drop shadows and one inner shadow, seven FFI arguments, and
visible hidden flags. Record isolated load success against pan
and zoom context loss on SwiftShader without changing workloads.
Keep 3 warm-ups, 10 attempts, and 30s timeout provisional with
measured per-attempt costs and workflow guidance.
AI-assisted-by: muse-spark-1.3
Record manifest-default features without adding A/B selection. Keep
scored builds free of the optional function-name profiling switch.
Exercise CLI persistence, interruption, browser failure and legacy page
interactions through separate diagnostics. Share readable reports and
warn when offline comparisons include failed runs.
Validate with 50 focused tests, browser and CLI fault checks, two full
corpus workflow runs, and headed rectangle cases. Shadow graphics and
sampling calibration remain open.
AI-assisted-by: GPT-6
Share seven deterministic fixtures with the playground pages and measure
renderer lifecycle, pan, and zoom through a prepared Playwright runner.
Retain raw attempts and failures with offline statistical comparisons.
Add focused tests and separate browser fault diagnostics. Full-suite
calibration and shadow GPU validation remain tracked follow-up work.
AI-assisted-by: GPT-6
* 🎉 Add OAuth support for remote MCP connections
Add MCP-specific discovery, client registration, and approval endpoints.
Exchange single-use authorization codes with PKCE for the fixed MCP user
token, enabling MCP and creating the token on approval when needed.
Use self-contained client IDs without new database tables or stored client
records. Keep temporary authorization state in Redis/Valkey.
Accept bearer credentials while preserving query-parameter connections.
Enable OAuth through PENPOT_MCP_AUTH_URI in multi-user mode. Document the
environment variable in the README and implementation details in Serena.
Explain protocol and concurrency decisions in the Clojure code. Cover token
provisioning, OAuth state, client registration, HTTP flows, and the approval
page with tests.
AI-assisted-by: gpt-6
* 🐛 Wait for devenv initialization before starting builds
Docker reports a container as Running as soon as its main process starts,
while the entrypoint may still be preparing the development environment.
manage.sh used that status to launch tmux jobs, allowing frontend builds
to race with ownership changes to /opt/rustup and Cargo setup.
On a fresh container, rustup could fail with Permission denied before the
frontend watcher started. Nginx would later serve the existing JavaScript,
leaving source changes absent from the running app despite restarting
devenv.
Wait for nginx's local health endpoint before launching the jobs. Nginx
starts after entrypoint setup, so its response confirms that setup has
finished. Allow up to 120 seconds and report a timeout without starting
the jobs if initialization does not complete.
Validation: shell syntax and whitespace checks pass.
AI-assisted-by: gpt-6
* ♻️ Share the login modal with MCP authorization
Extract the viewer login modal and its styles into a shared auth
component, retaining the viewer wrapper and its close behavior.
Use the modal on the MCP approval page and preserve the pending request
through login. Closing it clears the saved return URL and shows a
cancellation message without further actions.
Cover return URL handling and cancellation with frontend tests.
AI-assisted-by: gpt-6
* ✨ Show client icons in MCP authorization
Preserve HTTPS logo URLs from client registration and published metadata
and show the icon beside the application name on the approval page.
Ignore unusable URLs and hide failed images without blocking approval.
Update the access description to refer to MCP connections established
from Penpot. Cover logo metadata and approval responses in backend tests.
AI-assisted-by: gpt-6
* 🐛 Follow the system theme when no preference is configured
Default to the system theme in the shared resolver used by the app and
plugins. Preserve explicit light and dark choices, including the legacy
default value that means dark.
Reuse the reactive theme hook in progress widgets and show the system
preference in settings for accounts without a configured theme.
Verify preference resolution with regression tests and browser checks.
All 1,474 frontend tests pass.
AI-assisted-by: gpt-6
* 🐛 Replace expired MCP tokens during OAuth approval
Settings can give an MCP token an expiry date. OAuth approval reused
the newest MCP token without checking it, so an expired token reached
the client and never matched a plugin connection.
Approval now treats an expired token as missing and creates a new one.
Token exchange rejects a code whose token expired after approval with
invalid_grant.
AI-assisted-by: claude-opus-5-5
* 🐛 Log unexpected errors on MCP OAuth endpoints
The OAuth routes sit outside the shared HTTP middleware, so the shared
error handler never saw them, and their own handler turned unexpected
failures into a 503 without logging anything. Failures in the session
middleware of the approval endpoints had no handler at all.
Unexpected failures now also go through the shared error handler for
its logging, while clients still get an OAuth error body. The OAuth
error handler also wraps the session middleware of the approval
endpoints.
AI-assisted-by: claude-opus-5-5
* 🔧 Add devenv script to run the MCP server with OAuth
start-mcp-devenv-oauth starts the MCP server in multi-user mode with
PENPOT_MCP_AUTH_URI derived from PENPOT_PUBLIC_URI, to test OAuth in
the devenv. Agentic tooling still uses the single-user
start-mcp-devenv.
AI-assisted-by: claude-opus-5-5
* 🐛 Fix failing test case
---------
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>