23564 Commits

Author SHA1 Message Date
makesomethingshit
efa2518fe8
🐛 Fix deep-harden of host plugin context on load (#11521)
* 🐛 Fix deep-harden of host plugin context on load

ses.harden(context) in loadPlugin deep-freezes every host-owned object
and function reachable through the context. The host keeps needing to
modify those across page navigation (listener wrappers, proxies), so a
later property augmentation (e.g. assigning toString) throws
'TypeError: Cannot assign to read only property toString' and kills the
MCP session (penpot/penpot#11001).

Pass the host context through untouched; sandbox isolation stays at the
compartment boundary (hardened sandbox-owned globals + ses.safeReturn).

Regression test: load-plugin-context.spec.ts (no ses mock).

AI-assisted-by: muse-spark-1.3
Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>

* 🐛 Add real SES bootstrap to host-context regression test

The previous load-plugin-context.spec.ts had no SES bootstrap, so it
failed on the original code with 'ReferenceError: harden is not defined'
instead of the intended freeze assertion, and passed on the fixed code
merely by avoiding ses.harden.

Now the spec bootstraps real SES (repairIntrinsics + hardenIntrinsics),
adds a control test proving real ses.harden deep-freezes host-owned
functions (Object.isFrozen === true, later toString assignment throws
TypeError - the #11001 crash signature), and keeps the regression test
asserting loadPlugin leaves host functions unfrozen and patchable.

AI-assisted-by: muse-spark-1.3
Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>

* 🐛 Add production-order hardening contrast evidence

Proves the initialization-ordering hazard behind #11001 (cf. #8636):
in production, index.ts runs repairIntrinsics only at module load while
hardenIntrinsics runs later in createSandbox. The original loadPlugin
called ses.harden(context) between those steps, freezing the shared
Function.prototype with plain data properties so later override taming
is skipped and any subsequent fn.toString assignment throws TypeError.

Kept in a separate spec file so the full SES bootstrap in
load-plugin-context.spec.ts cannot mask the ordering effect.

AI-assisted-by: muse-spark-1.3
Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>

* 🐛 Apply approved lint fix and CHANGELOG entry

Restores the two approved deliverables missing from the previous push:
the prefer-rest-params fix in load-plugin-harden-order.spec.ts
(replacing the deprecated arguments usage) and the plugins-runtime
CHANGELOG entry for the host-context harden fix (#11001).

AI-assisted-by: muse-spark-1.3

Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>

* 🐛 Remove deep-hardening of host plugin context on load

Signed-off-by: makesomethingshit <junsoo1172@gmail.com>
Co-authored-by: multica-agent <github@multica.ai>
Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>

* 🐛 Align CHANGELOG and context comment with reviewed evidence

AI-assisted-by: multica-agent
Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>
Co-authored-by: multica-agent <github@multica.ai>

* 🔥 Remove SES semantic tests from plugin regression coverage

Drop the tests that only verify SES library semantics rather than Penpot
application behavior:

- Delete load-plugin-harden-order.spec.ts (pure SES initialization-order
  evidence, never calls loadPlugin).
- Remove the ses.harden control test and its SES bootstrap setup from
  load-plugin-context.spec.ts.
- Remove the #8636 hardening-order contrast test and the now-unused ses
  import from load-plugin-real-path.spec.ts.

Keep the application-level regression coverage: the real loadPlugin
initialization path, permission enforcement, host-context isolation and
safeReturn protection. No production code changes.

Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>
AI-assisted-by: Omen Alpha

---------

Signed-off-by: Junsoo Choi <junsoo1172@gmail.com>
Signed-off-by: makesomethingshit <junsoo1172@gmail.com>
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-09-08 12:32:45 +02:00
Eva Marco
5b97fb9408
♻️ Migrate editable-select to DS icon* and design tokens (#11500)
* ♻️ Migrate editable-select to DS icon* and design tokens

The dropdown toggle and selected-item tick used the deprecated
app.main.ui.icons component and the SCSS relied on refactor/
placeholders and aliased design tokens.

Replace both icon usages with the DS icon* component (arrow-down
instead of a rotated arrow, tick unchanged, both at DS size "s"),
and rework editable_select.scss to depend only on DS modules:
placeholders inlined, aliases resolved to their terminal --color-*
tokens, sizes/spacing/borders mapped to DS tokens, logical
properties used where they preserve behavior, and per-state colors
driven by component-local CSS custom properties (fed through
currentColor for the icons, since DS icon* strokes default to
currentColor).

AI-assisted-by: claude-sonnet-5

* ♻️ Migrate frame_grid to the DS and unify its options dropdown

Refactors frame_grid.scss off the deprecated refactor/ styling system
onto DS tokens (borders, sizes, spacing, typography), and replaces the
remaining deprecated-icon usages (gap-horizontal, grid-margin) with
the DS icon* component.

Unifies the square/column/row "reset to default" toggle+panel, which
had drifted into two inconsistent, duplicated implementations, into
two shared components (default-options-toggle*, default-options-
dropdown*) built on the existing dropdown component, with proper
menu/menuitem roles and Enter/Space keyboard activation.

Along the way, fixes several regressions the migration surfaced:
- .show-more-options lost its border width when converted to a
  CSS-variable-driven color.
- the reset-to-default toggle was enabled while its own options were
  disabled instead of the other way around.
- numeric inputs in .height/.gutter/.margin lost their explicit width,
  letting them fall back to the browser's intrinsic input width and
  overflow the sidebar.
- a leftover CSS override was forcing the docked dropdown-select's
  icon to render fully transparent.

Also fixes editable-select's is-selected check, which compared a
stringified option value against a non-stringified current-value and
so could never match, silently hiding the selected-item checkmark.

AI-assisted-by: claude-sonnet-5

* ♻️ Migrate frame_grid's numeric inputs to the DS component

Replaces the deprecated app.main.ui.components.numeric-input with the
DS numeric-input* across .grid-size, .height, .gutter and .margin,
using its built-in :icon/:text-icon slots (via input-field*) instead
of rendering a separate icon element beside the input.

The gutter/margin icons previously rotated 90deg via CSS to indicate
row vs column orientation; DS ships dedicated icon-ids for both axes
(gap-horizontal/gap-vertical, margin-top-bottom/margin-left-right),
so the icon itself now swaps per grid type instead of being rotated.
The height row's "H"/"W" text label is now the matching DS
character-h/character-w icon rather than literal text.

AI-assisted-by: claude-sonnet-5

* ♻️ Flatten unnecessary nesting in frame_grid.scss

CSS Modules already scope every class to this file, so nesting
purely to dodge cross-file name collisions is dead weight now.
Rewrite the nested rules as flat top-level selectors, dropping
ancestor-wrapper prefixes wherever the target class is already
unique in the file and keeping them only where they still do real
work (winning a same-node specificity fight against another
component's own class).

Also drop --show-options-icon-color: it was assigned across every
state but never actually read anywhere, so it did nothing.

AI-assisted-by: claude-sonnet-5
2026-09-07 14:37:40 +02:00
Alejandro Alonso
269aa36f82
Export solid and patterned strokes to WASM SVG (#11513)
*  Export solid inner and outer strokes to WASM SVG

SkSVGDevice drops save_layer+Clear used for path Outer strokes, so
solid Inner/Outer strokes on closed shapes are expanded to filled
outlines via stroke_to_path. Center and open paths stay on the shared
stroke path.

Closes #11381

*  Export dotted, dashed, and mixed strokes to WASM SVG

PathEffects do not survive SkSVGDevice, so dotted/dashed/mixed strokes
expand via stroke_to_path with the effect kept, producing filled
outline geometry for all alignments including Center.

Closes #11382

* 🐛 Fix WASM SVG stroke export edge cases

Keep expanded stroke outlines in local space under the leaf CTM so
rotated rects/paths are not double-transformed; overlay open-path caps
after dotted expansion; draw frame strokes outside the content clip;
and route per-side rect/frame strokes through the evenodd band path.
2026-09-07 13:50:58 +02:00
Alexei Bratuhin
1f12561427
📚 Fix typo in docker-compose.yaml comment (#11517)
Signed-off-by: Alexei Bratuhin <alexei.bratuhin@googlemail.com>
2026-09-07 13:15:41 +02:00
Andrey Antukh
c52778d6f6 Merge remote-tracking branch 'origin/staging' into develop 2026-09-07 11:28:06 +02:00
Andrey Antukh
9462543fb5
Add skip-onboarding option to create-demo-profile (#11526)
Add optional skip-onboarding param to create-demo-profile. When true, the demo profile is created with onboarding-viewed and release-notes-viewed set, so it skips the onboarding flow. Default keeps the current behavior. Cover both cases with RPC tests. AI-assisted-by: muse-spark-1.3-contributor
2026-09-07 11:15:17 +02:00
Andrey Antukh
5c10ea5bd6 📚 Document branch naming convention
The creating-prs memory described a branch format (issue/... with a
<type>/<short-description> fallback) that does not match actual repo
practice, where issue-driven branches are issue-NNNN. Replace it with
a Branch Naming section: issue-NNNN as primary, descriptive name
without slashes as fallback.

Add the matching public convention to CONTRIBUTING.md under Pull
Requests, which previously defined no branch naming at all.

AI-assisted-by: omen-alpha
2026-09-07 08:41:54 +00:00
Eva Marco
cd98a88c4d
🐛 Fix errors on shortcuts (#11081)
* 🐛 Fix shift + character recording

* 🐛 Fix importing conflicts

* 🐛 Fix paste as non customizable shortcut

* 🐛 Fix duplicate manage of custom shortcuts

* 🐛 Fix json file name

* 🎉 Add search by commands

* 🐛 Fix CI
2026-09-07 10:14:09 +02:00
Andrey Antukh
1dfa2cd9f2 Merge remote-tracking branch 'origin/staging' into develop 2026-09-07 09:53:58 +02:00
Elena Torró
f0680cf5f8
🐛 Handle empty variant options (#11499)
* 🐛 Handle empty variant options

* 🐛 Disable variant value select when there are no options
2026-09-07 09:53:45 +02:00
Andrey Antukh
7ebd7cc0d5 Merge remote-tracking branch 'origin/staging' into staging 2026-09-07 09:52:07 +02:00
Andrey Antukh
2100ed29ea Merge remote-tracking branch 'origin/main' into staging 2026-09-07 09:45:06 +02:00
Andrey Antukh
7e1139b906
🐛 Validate origin and route messages to sender in plugin postMessage channel (#10970)
* 🐛 Fix plugin postMessage channel allowing cross-plugin message injection

The global postMessage listener was broadcasting incoming messages to all
loaded plugins without validating the origin or routing to the correct
sender. This allowed any plugin (or any iframe from any origin) to inject
messages into other plugins.

- Added origin validation — messages from origins other than
  window.location.origin are rejected.
- Added sender-based routing — a message is only delivered to the plugin
  whose iframe contentWindow matches event.source.
- Exposed iframeWindow getters in PluginManager, PluginModalElement, and
  createPlugin so the runtime can compare event.source against the correct
  iframe reference.
- Updated documentation examples to include origin validation and
  recommend window.location.origin over '*' for postMessage targetOrigin.

AI-assisted-by: qwen3.7-plus

* 🐛 Fix plugin origin check breaking cross-origin plugin messaging

The origin check added in the previous commit compared event.origin
against window.location.origin (Penpot own origin). Since plugins
are cross-origin by design (hosted on the plugin author domain),
this check rejected every legitimate message from every real plugin.

The event.source-based sender routing (matching iframeWindow identity)
is the correct and sufficient security mechanism - it cannot be forged
cross-origin, so the redundant origin check was removed.

- Removed event.origin check from load-plugin.ts message listener
- Updated tests to use realistic plugin origins (localhost:4202/4203)
  and to verify rejection based on source identity, not origin
- Fixed documentation examples: use event.source for receiving
  validation and '*' for postMessage targetOrigin

AI-assisted-by: mimo-v2.5-pro
2026-09-07 09:31:35 +02:00
Andrey Antukh
960209f1fa 🐛 Fix update-profile-props not rejecting plugin props at validation
The schema:props schema still included :plugins as a valid key,
so the closed-map validation passed instead of rejecting it.
The system-managed-props denylist only silently stripped the key
after validation, never raised an error.

Derive schema:props-writeable from schema:props using sm/dissoc-key
to exclude system-managed keys, and use it in the update-profile-props
params schema. The original schema:props remains intact for get-profile
result validation.

AI-assisted-by: mimo-v2.5
2026-09-07 09:30:01 +02:00
Andrey Antukh
9289e676e1 Require re-confirmation when plugin manifest differs on open
When the fetched manifest differs from the stored manifest
(code, name, etc.), show the permissions dialog instead of
silently updating — preventing execution of tampered/injected
plugins.

On fetch error, show a warning notification instead of loading
with the old manifest.

Bundled plugins (no URL) skip validation as they are trusted
with no remote source.

Completes the 3-layer defense for T3-N1-02: (1) closed
permission schema, (2) dedicated RPC methods, (3) integrity
validation on open.

AI-assisted-by: qwen3.7-plus
2026-09-07 09:30:01 +02:00
Andrey Antukh
a8e0b3c1f8 🐛 Add dedicated RPC methods for plugin registry operations
Add `add-profile-plugin` and `remove-profile-plugin` RPC methods for
atomic plugin registry operations, preventing manipulation via the
broader `update-profile-props` endpoint.

- Close the `:plugins` field in `update-profile-props` schema to
  eliminate the mass assignment attack vector for plugin data.
- Define `valid-permissions` and a closed `schema:permissions` enum to
  restrict plugin permissions to known values.
- Migrate the frontend to use the new granular RPC methods with
  optimistic updates and rollback on failure.
- Add comprehensive backend tests covering valid/invalid permissions,
  updates, removal, and rejection via old endpoint.

AI-assisted-by: qwen3.7-plus
2026-09-07 09:30:01 +02:00
Andrey Antukh
c8691d0cde
🐛 Fix crash when a nil shape id reaches WASM modifiers (#10896)
* 🐛 Fix crash when a nil shape id reaches WASM modifiers

A modif-tree containing a nil shape id crashed the WASM heap write
with "Cannot read properties of null (reading '__u32_buffer')" in
propagate-modifiers. Production builds elide the upstream asserts,
so a nil id (e.g. update-dimensions called with a missing shape's
parent id in the variants flow) reached the uuid heap write.

Drop nil-keyed modif-tree entries in apply-wasm-modifiers and
set-wasm-modifiers before they reach propagate-modifiers and
set-structure-modifiers, filter nil ids at the parse level, and
guard the variant reposition/creation events against missing shapes.

AI-assisted-by: hy3

* 🐛 Fix let scoping bugs in modifiers.cljs

Two let forms in set-wasm-modifiers and apply-wasm-modifiers
had their bindings vectors closed prematurely, causing undeclared
var warnings for snap-pixel?, translation?, ids, update-shape,
options, bool-ids, undo-id, and other bindings.

In set-wasm-modifiers: removed premature ) after ] on the let
bindings so the let body wraps snap-pixel? and translation?.

In apply-wasm-modifiers: the cond form was not explicitly closed,
so all subsequent bindings (ignore-tree, options, modif-tree, ids,
update-shape, bool-ids, undo-id) were parsed as cond clauses
instead of let bindings. Added ) to close cond after :else, and
moved ] to close the bindings vector after undo-id.

AI-assisted-by: mimo-v2.5-pro
2026-09-07 08:34:06 +02:00
Elena Torró
00e0492bb5
🐛 Fix import libraries missing team-id (#11505) 2026-09-04 13:49:37 +02:00
Elena Torró
d82038a570
🐛 Fix paragraph span restyle on line change (#11503)
* 🐛 Fix paragraph span restyle on line change

* 🐛 Fix text shape boundaries on resize
2026-09-04 13:43:23 +02:00
Marina López
5452eb5c8a
🐛 Fix email sales (#11514) 2026-09-04 13:36:10 +02:00
Elena Torró
0533be100d
🐛 Fix text selection render overlay and double/triple click flash (#11512) 2026-09-04 12:38:31 +02:00
Eva Marco
df48c834e3
🐛 Fix create nil typography token (#11489)
* 🐛 Fix stroke width token dropdown closing mid-interaction

The stroke-row* key included a hash of applied-tokens, forcing a full
remount whenever the async token-resolution pipeline updated that
state (e.g. after a detach action settles). If the remount landed
while the width dropdown was open, it destroyed the dropdown and
reset its open state, permanently closing it before the user could
pick a token.

Drop the hash from the key so the row only remounts on actual
structural changes (add/remove/reorder), not on every token
resolution tick.

AI-assisted-by: claude-sonnet-5

* 🐛 Fix crash when a typography token has a nil value

A composite typography token saved with no fields filled in stores a
nil :value. That value reached StyleDictionary's tokens-studio
font-styles preprocessor, which assumes a typography value is never
null and threw, crashing token resolution for every other token in
the file.

Reject the nil value at the source in the typography token form
validation, and as defense in depth, filter nil-valued tokens out of
every resolve-tokens* entry point before they reach StyleDictionary,
tagging them with the existing empty-input error instead.

Also remove a stray debug prn left in find-refs.

AI-assisted-by: claude-sonnet-5

* 🔧 Gitignore local CLAUDE.md

Keep the commit-conventions instructions file local to each
contributor's checkout instead of tracking it in the repo.

AI-assisted-by: claude-sonnet-5

*  Use a single transducer to tag invalid-value tokens

merge-invalid-value-tokens ran three separate passes over the token
map (remove, map, into) and then merged the result back in. Combine
the remove/map steps into one ns-level transducer, defined once
instead of rebuilt on every call, and pass resolved as the seed to
into so the trailing merge isn't needed either.

AI-assisted-by: claude-sonnet-5

* ♻️ Drop redundant t/testing wrapper in nil-value token test

The outer t/testing just repeated the deftest's own name and added
nothing the two inner t/testing blocks (each covering one concrete
assertion group) don't already say.

AI-assisted-by: claude-sonnet-5

* 🐛 Fail the nil-value token test on a resolution error

rx/sub! only handles the success case, so if token resolution ever
errors instead, done is never called and the async test hangs
instead of failing. Switch to rx/subs! with an error handler that
reports the failure and calls done, matching the pattern already
used elsewhere in the tokens test suite.

AI-assisted-by: claude-sonnet-5
2026-09-04 12:31:26 +02:00
Alejandro Alonso
86aebd3429
Reduce WASM text selrect churn after batch page load (#11471)
* 🐛 Align WASM auto-width text size with HTML measurement

Include the HTML paragraph-set 1px right margin in Skia auto-width
so WASM and DOM measurements match.

*  Scope and defer post-font WASM text relayout

Track pending font faces per shape, force-relayout only affected
texts, defer stale-selrect sync until the first tiles paint, and
batch request-render after attr updates. Tolerate missing font-face
keys so callers that omit them do not blow up.
2026-09-04 12:24:37 +02:00
Alejandro Alonso
7f46c8c80e Merge remote-tracking branch 'origin/staging' into develop 2026-09-04 11:00:56 +02:00
Elena Torró
fdcc4666e3
🐛 Fix multiple shapes export on files rendered with webgl (#11506) 2026-09-04 11:00:35 +02:00
Eva Marco
064f200774
🐛 Fix crash pressing Ctrl+D with no shape selected (#11491)
AI-assisted-by: claude-sonnet-5
2026-09-04 10:21:31 +02:00
Eva Marco
214dbd0c8b
🐛 Stop opening Comments section from persisting display-comments (#11492)
AI-assisted-by: claude-sonnet-5
2026-09-04 10:15:38 +02:00
Marina López
f2f352d06c
Add props to invitations and organizations events (#11508) 2026-09-04 10:13:25 +02:00
David Barragán Merino
034707a9d7 Enable parallel integration tests on staging branch
Remove the temporary serial exception introduced alongside sharding.
Sharded runs on `develop` complete in ~13 min against ~52 min for the
serial runs on `staging`, and the gap keeps widening as the suite
grows.

Also restore the JSON report as a 30-day artifact. It was dropped when
the suite moved to sharded blob reporters, which left flakiness rates
only obtainable by scraping job logs.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-03 18:10:38 +02:00
Elena Torró
eb8d3a2fef
🐛 Fix paragraph selection (#11493) 2026-09-03 16:17:38 +02:00
Alejandro Alonso
fb22c1547c
🐛 Skip component sync for derived WASM text layout commits (#11490)
Post-font-load selrect fixes and position-data regeneration write
sync-attrs on texts inside mains. That made watch-component-changes
treat them as edits and run touch/sync per component, freezing large
files. Mark those commits with skip-component-sync? (same idea as
translation?) so only real user edits propagate.
2026-09-03 13:43:36 +02:00
Alejandro Alonso
7b26949c76 🐛 Fix typography.cljs error from staging merge 2026-09-03 12:34:54 +02:00
Alejandro Alonso
37f1d3f0fd Merge remote-tracking branch 'origin/staging' into develop 2026-09-03 12:24:44 +02:00
Alejandro Alonso
a4becb5d1f 🐛 Fix size-limiting-stream read arity on v3 binfile import (#11468)
The FilterInputStream proxy only implemented read() and
read(byte[], int, int). Buffered reads call read(byte[]) (and
read(byte[], int) via Clojure interop), causing ArityException
while hashing storage objects and breaking v3 imports.

Implement all read overloads and extract shared byte-count logic.
2026-09-03 12:16:26 +02:00
Luis de Dios
a19b3c8d62 🐛 Fix font preview in assets breaks the font row (#11428)
* 🐛 Fix font preview in assets breaks the font row

* 🐛 Fix font height problem also in the font dropdown

* 🐛 Fix a small bug within the changes

---------

Co-authored-by: Eva Marco <evamarcod@gmail.com>
2026-09-03 12:16:26 +02:00
Elena Torró
80dea409c6
🐛 Fix share-link viewers unable to load file fragments (#11484) 2026-09-03 12:15:35 +02:00
María Valderrama
7c762d8a98
🐛 Fix recommended plan (#11488) 2026-09-03 11:59:20 +02:00
Pablo Alba
15dff4a9e1
💄 Fix sales email (#11478) 2026-09-03 08:46:52 +02:00
Eva Marco
c6a32a2f5a
🐛 Fix list on registration toast notification (#11479) 2026-09-02 17:19:37 +02:00
David Barragán Merino
f633d82f51 ♻️ Switch penpot images to sha-<commit> tagging
Replaces the content-hash build key (bundle_version + docker/images tree hash) used to tag and dedupe the backend/frontend/exporter/storybook/mcp image set with sha-<commit>, matching the scheme already used by admin-console, licenses-manager and payments across the org. The check→build→promote pattern with the S3 marker is unchanged; only the key used for the marker, the immutable tag and the local bundle cache filename moves from the composite build key to the git commit sha (the bundle cache now keys on bundle_version alone, which is what it actually caches). devenv is intentionally left out of this pass, it has no versioned tagging today.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-02 17:14:31 +02:00
David Barragán Merino
f22abc9861 ♻️ Switch penpot images to sha-<commit> tagging
Replaces the content-hash build key (bundle_version + docker/images tree hash) used to tag and dedupe the backend/frontend/exporter/storybook/mcp image set with sha-<commit>, matching the scheme already used by admin-console, licenses-manager and payments across the org. The check→build→promote pattern with the S3 marker is unchanged; only the key used for the marker, the immutable tag and the local bundle cache filename moves from the composite build key to the git commit sha (the bundle cache now keys on bundle_version alone, which is what it actually caches). devenv is intentionally left out of this pass, it has no versioned tagging today.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-02 16:37:20 +02:00
Alejandro Alonso
c5897bc50a
Paint plain text directly onto Current (#11355)
* ♻️ Share text layout paragraphs across modifier clones

Store Skia paragraphs in Rc so TextContentLayout::clone keeps the
cached layout for rotate/pan modifiers. Add layout.clear() and treat
needs_update as paragraphs-empty only.

*  Reuse cached Skia paragraphs when painting text

Add try_paint_from_layout_cache to paint from TextContent.layout when
versions match, skipping ParagraphBuilder rebuild and layout on each
frame. Wire into the layered text path for plain fills without strokes
or effects.

*  Paint plain text directly onto Current

Extend can_render_directly for stroke-free text and skip the empty
save_layer in draw_text when no stroke-group opacity is set. Plain text
paints into Current without the Fills/Strokes blit.
2026-09-02 16:17:06 +02:00
Eva Marco
cb80ea98ba
🎉 Add a new favicon for localhost enviroment (#11459) 2026-09-02 16:02:23 +02:00
0xTHAC0
b2fb63988f
🐛 Fix font-size dropdown clipping multi-digit values in Firefox (#11162)
The .custom-select-dropdown used width: fit-content which, in Firefox,
is capped to the containing block width (60 px for the font-size input).
This caused two- and three-digit font-size values (e.g. 48, 120, 1000)
to be visually clipped in the dropdown list.

Switching to width: max-content lets the dropdown expand to fit its
content regardless of the containing block width, matching the behaviour
Chrome already exhibited with fit-content.

Fixes #11008
2026-09-02 15:54:33 +02:00
Elena Torró
70b443a716
Render multiple-object SVG exports with render-wasm (#11475) 2026-09-02 14:45:32 +02:00
andrés gonzález
88d715938f
📚 Add migration guide page (#11454)
Point First Steps at the enterprise migration PDF with a short
summary, without duplicating the Community post.
2026-09-02 14:06:06 +02:00
Eva Marco
7620386215
🐛 Fix font family preview sample (#11473)
* 🐛 Fix font family preview sample

* 🐛 Fix font line height inside font selector
2026-09-02 13:55:38 +02:00
andrés gonzález
b6b1a47a7b
📚 Update MCP Quick demo video (#11453)
Replace the outdated Quick demo embed on the MCP docs with the
new recording.
2026-09-02 13:54:01 +02:00
Alejandro Alonso
b92112da54
🐛 Export multiple solid fills to WASM SVG (#11467)
Shapes and text with stacked solid fills were exported without fill
attributes because merge_fills builds a Skia shader that SkSVGDevice
does not serialize. Draw each fill layer separately for SVG export,
bottom to top, matching the classic exporter.

Closes #11466
2026-09-02 13:40:05 +02:00
Elena Torró
03e6f119e5
♻️ Clean unnecessary methods (#11472)
* ♻️ Remove duplicated/unused set-children code

* ♻️ Remove unused methods
2026-09-02 12:58:52 +02:00