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>
* ♻️ Use DS buttons and icons in the dashboard fonts page
The fonts page moves off the deprecated icons and raw buttons:
- "Add custom font", "Upload all", "Upload" and "Save" become DS primary
buttons and "Dismiss all" a secondary one.
- The remove, cancel and menu controls of the font rows become ghost DS
icon buttons named "Remove", "Cancel" and "Options"; the menu one
says whether its menu is open. Two of them were spans.
- The delete control of a font style was a span; it is now a button
named "Delete font style" with the DS close icon, still shown only
on hover.
- The warning and empty list icons use the DS icons. The loading
placeholder keeps the legacy loader: there is no DS one.
The font menu offset grows by the 6px the taller menu button added, so
the menu opens where it did. New key: dashboard.fonts.delete-variant
(en, es). A visual test covers a font ready to upload, the style hover,
the font menu and the edition row.
AI-assisted-by: claude-opus-5-5
* ♻️ Move dashboard fonts styles to logical properties
Physical sizes, offsets, margins and paddings become logical properties
and the DS typography module is used without a namespace. The element
selectors (section title, inputs, hero text) stay, with a comment: they
have no class.
The shared dashboard header styles (common-dashboard) still come from
refactor/: ten dashboard modules use them, so they move in their own
change. No visual change.
AI-assisted-by: claude-opus-5-5
* ♻️ Use the DS loader and heading in the dashboard fonts page
The fonts loading placeholder used the legacy loader icon, drawn as an
outline in a 32px box. It now uses the DS loader*, the same animated
pencil as the other dashboard loading screens. Its box is 9px tall,
so the label moves 6px up.
The section title h3 is now a heading* with the title-small typography
and its own class, so the SCSS no longer styles the bare element.
The legacy loader icon has no other use, so its SVG and def are gone.
AI-assisted-by: claude-opus-5-5
* ♻️ Give the dashboard fonts inputs and hero text classes
The font name and search inputs and the hero text get their own
classes instead of bare input and p selectors, and the hero title uses
the DS heading with title-large instead of the global h2 rule. The
title keeps its 24px size and weight; the DS line height (1.4 instead
of 1.15) moves the hero content about 6px down.
AI-assisted-by: claude-opus-5-5
* 🐛 Give the fonts placeholder icon its color back
Removing the legacy loader also removed the .icon svg rule, which gave
the empty placeholder text icon its color, so the icon had none. The
icon now has its own class with the secondary foreground color, and the
wrapper div without styles goes.
AI-assisted-by: claude-opus-5-5
* 🔥 Remove a commented-out selector from the debug shape panel
.shape-info-title was left commented out with a "check by hand" TODO in
#12484. Nothing uses it, so delete it instead of keeping it as a comment.
AI-assisted-by: claude-opus-5-5
* ♻️ Use DS typographies in the debug shape panel
The panel text and the shape title only set a font size, with a "no exact
DS typography match" TODO. body-small and body-medium differ from them
only in the line-height, so use them directly and drop the TODOs.
Visible change: the panel lines get the DS line-height (1.3 for the text,
1.4 for the shape title) instead of the browser default, so rows grow a
little taller. The panel is debug-only.
AI-assisted-by: claude-opus-5-5