24540 Commits

Author SHA1 Message Date
alonso.torres
f8ca373f28 ⚡ Lay out a window of the paragraph to find squeeze sheds
Finding the line-end squeeze laid out the whole paragraph again for every
line, so the cost grew with the square of the length and typing in a
long Japanese text lagged.

Lines break greedily, so a line depends only on the text from its start.
Lay out a window from the line start that holds the line and the next
one, doubling it when needed, keep it while no squeeze changes the sheds,
and lay out the full paragraph once at the end. A test checks the sheds
match a full relayout.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:40:13 +01:00
alonso.torres
dffb8a9240 🐛 Include horizontal readings in the text export bounds
Readings paint above the line box, outside the glyph metrics. The text
bounds joined the emphasis marks only, so an auto-width text with ruby
or Zhuyin and no emphasis exported with the top of its reading cut off.

Measure the ink of the readings with the code that paints them and join
it to the content and auto-width bounds.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:39:41 +01:00
alonso.torres
b8a922b4f0 🐛 Keep kinsoku and ruby bases in narrow Japanese text boxes
The narrow-box overflow plan, which lets an indivisible unit overflow a
box narrower than one character, ran for Chinese and Korean only. In a
Japanese box of one character width Skia broke anywhere: a line started
with "、" and a ruby base split over two lines.

Run the plan for Japanese too, and refuse emergency breaks before a
character that may not start a line or after one that may not end it.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:39:14 +01:00
alonso.torres
8cbe3728f6 🐛 Fix the mixed and plain labels of the text layout selects
The language profile and Japanese layout selects labelled mixed values
with "labels.mixed-values", a key that no longer exists, so they showed
the raw key. Use "settings.multiple", as the other text options do.

The "No language profile" choice had a blank id, which the select shows
as "--". Give it the "plain" id and map it back to no language.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:37:37 +01:00
alonso.torres
1817871f51 🐛 Undo text edits from the Edit menu while editing
Edit > Undo and Redo emit the same events as the shortcuts, but the menu
takes the focus. The blurred WASM editor then ignored the restore, so the
menu did nothing while the text edition stayed open.

Focus the editor again before restoring its history.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:37:37 +01:00
alonso.torres
5462012b4e 🐛 Combine short Western runs under tate-chu-yoko all
An `all` composite gave up below half width, so "2027" in a font with
proportional digits (about 0.45) stayed sideways, while `digits` combined
the same run down to 1 / length.

A Western run no longer than a `digits` composite now squeezes as far as
one. Upright CJK and longer runs keep the half-width limit, since a
deeper squeeze makes full-width glyphs illegible.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:36:41 +01:00
alonso.torres
ea40d9fbc1 🐛 Stop pushing a long forbidden run out of every vertical column
When an item overflowed a column, column_break moved the characters that
may not start a column into the next one, and only checked that they fit
there with the overflowing item. The characters after it may not start a
column either, so a run longer than a column was pushed out again at
every column: "ださい。ー、ャュョッ。" in a five-character box left
"ださ", "い", "。" and "ー" alone in four columns.

Measure the whole unit that moves, up to the next allowed break, and keep
the natural break when that unit is longer than a column.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:36:41 +01:00
alonso.torres
5d1c0bbe5e 🐛 Relayout text whose font is fetched for another shape
Font fetches are shared per URL, so a text that asks for a face already
being fetched gets no fetch callback. process-pending only relaid out
text after its own callbacks, so such a text kept its fallback-font
layout. Auto-grow text recovered through the reflow resize, but fixed
text stayed wrong until another change.

Wait for the text's pending faces to be stored or to fail before the
relayout, whoever started their fetch.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:36:41 +01:00
alonso.torres
3a7cf4be9e 🐛 Attach horizontal annotations to the ideographic em box
Zhuyin, Pinyin and emphasis marks over horizontal text were placed
against an em box bottom-aligned to Skia's tight glyph rect, which spans
the hhea ascent and descent. In CJK faces the descent reaches past the
em box, so Zhuyin columns sat about 0.17 em below their base, and over
annotations relied on a hand-tuned clearance.

Place them against the ideographic em box (OS/2 typo metrics) of the
font that shaped each base, centred on the line baseline, the same
metrics vertical text uses for upright glyphs.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:36:41 +01:00
alonso.torres
bfeb629562 🐛 Paint CJK underlines with the color of their own span
Skia keys a line's style runs by UTF-8 byte offset, while line indices
are UTF-16. Decoration segments searched those runs with UTF-16 offsets,
so with CJK text every underline or strike on a line took the paint of
the first run, and a decorated span wrapped onto a new line lost its bar.

Look up each segment's run by the span's UTF-8 style anchor, falling
back to the line's first run for a span continued from an earlier line.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:36:41 +01:00
alonso.torres
259a91bf11 🐛 Keep Pinyin readings on one baseline over horizontal text
Each ruby reading attached its lowest ink to the base, which suits kana
but not Latin letters: the descender of "y" lifted "yǒu" above "liáo"
over 友聊, and under ruby moved with ascenders and tone marks.

Western letters in a reading now take the font's descent (over) or cap
height (under) instead of their ink, so Pinyin and romaji readings share
one baseline and descenders stay clear of the base. Upright glyphs keep
the ink edge.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:36:41 +01:00
alonso.torres
e454134ee0 🐛 Move distributed alignment to the language layout sections
The fifth alignment button made the first row of the text options 36px
wider than the sidebar grid: the options button fell outside it and the
panel scrolled sideways.

Distributed alignment is an East Asian convention (均等割り付け, 分散对齐,
배분 정렬), so it moves out of the alignment group into a switch of the
Japanese, Chinese and Korean layout sections, shown when the text's
language matches. Turning it off aligns the text to the start. The label
shortens to fit beside the switch.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:36:40 +01:00
alonso.torres
4162ec3cbf 🐛 Keep distributed and justified lines inside the text box
SkParagraph moves each line right by half the letter-spacing of its first
character. That centres span tracking, but the spacing added to single
characters (distribution and justification gaps, squeezing, compression
and ruby room) must only open the gap after them. A distributed label of
two characters started half a gap in and overflowed the box by as much:
"国家" in a 200px box drew 国 92px from the left edge.

Builders of horizontal text record how far each adjusted character's
letter-spacing departs from its span tracking, and `LayoutParagraph` takes
back half of that on each line, next to its baseline shifts: painting,
glyph and placeholder rects, line metrics, hit testing and text paths.
Span tracking keeps its half spacing before the line.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:36:40 +01:00
alonso.torres
1530ae23ed ✨ Add lang to text shapes and ranges in the Plugin API
The paragraph language selects the CJK typesetting rules (line breaking,
punctuation spacing, the default emphasis position and localized glyphs),
but plugins could not read or set it. `lang` holds it as a BCP 47 tag, as
the HTML `lang` attribute does; `null` or `''` removes it.

On a text shape it applies to every paragraph and reports 'mixed' when
they differ. On a text range it applies to the whole paragraphs the range
touches: `update-text-range-attrs` now writes paragraph attrs such as the
language there instead of copying them into spans. The tag pattern moves
to `schema:text-language`, shared by the content schema and the plugin
validator.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:36:40 +01:00
alonso.torres
ab07c0e79a 🐛 Set digits tate-chu-yoko upright and centred in vertical text
Four fixes to how `digits` tate-chu-yoko composites lay out in vertical
text:

- A lone digit combines too, as CSS `digits <n>` combines runs of n or
  fewer, so it stands upright instead of turning sideways. The renderer
  (`split_digit_runs`) and the export path (`digit-combine-segments`)
  share the rule.
- Composites centre on the OS/2 typographic metrics, like upright glyphs.
  The hhea ascent of CJK faces exceeds the em, which set digits about
  0.07em below the kanji beside them.
- Letter-spacing stays outside the composite, as in CSS. Counting the
  trailing spacing in its width moved the digits left by half of it.
- The regional Chinese profiles no longer add the Han/Western gap around
  a composite: it sets in one ideograph's cell, as JLREQ already does.

AI-assisted-by: claude-opus-5-5
2026-11-03 17:36:40 +01:00
alonso.torres
e4a3116efe 🐛 Join text spans left with the same style after range styling
Styling a range splits spans at its ends, and nothing joined them back, so
a text kept invisible boundaries between spans with equal styles. Two CJK
features depend on those boundaries: tate-chu-yoko `all` combines per
span, so "2" + "027" rendered as two upright blocks, and a ruby reading or
warichu note over a range that crossed a hidden boundary was rejected.

`merge-equal-spans` joins neighbouring spans of a paragraph whose styles
match, counting a missing attr as its default value. Spans with a reading
or a warichu note are never joined: each one is its own group. Both places
that split spans use it: `update-text-range-attrs` (plugin API) and
`apply-attrs-to-paragraph` (WASM editor styling).

AI-assisted-by: claude-opus-5-5
2026-11-03 17:36:40 +01:00
alonso.torres
0e4c17790e ✨ Add distributed text alignment and preserve fractional line spacing
Add distributed alignment to text styles, sidebar controls, CSS export,
and the Plugin API. Spread characters across horizontal and vertical
lines, including the final line, while preserving graphemes and annotations.

Keep fractional line heights consistent across painting, hit testing,
selection, and text paths to prevent baseline drift between lines.

Add tests for distribution, nonbreaking text, and fractional line geometry.

AI-assisted-by: gpt-6
2026-11-03 17:36:40 +01:00
alonso.torres
3270c64bb5 🐛 Restore selected text when composition is cancelled
Restore the pre-composition content and UTF-16 selection when an IME ends
with an empty replacement. Keep styles, selection direction and redo intact
without recording cancellation as an edit. Share snapshot restoration with
editor undo and redo.

Add a regression for styled selections and cancellation after undo. Verify
commit, cancellation, deletion and history in four saved showcase examples,
including emoji and decomposed Korean Jamo in both writing directions.

AI-assisted-by: GPT-6
2026-11-03 17:36:40 +01:00
alonso.torres
40c93dbf5a 📎 Cover native Hanja conversion in editor history
Exercise the Hangul-to-Hanja composition sequence through the public editor
bindings. Keep previews out of saved content and history, then restore the
styled source selection with one undo and the converted content with redo.
Cover both writing directions and Korean wrapping modes.

Verify native Fcitx Hangul conversion in all four showcase examples and
confirm the saved examples survive reopening.

AI-assisted-by: GPT-6
2026-11-03 17:36:40 +01:00
alonso.torres
dc61fb8ff3 🐛 Restore undo and redo inside the WASM text editor
Keep bounded content and selection history within each editing session.
Group committed compositions and styled insertions, omit IME previews,
and reset history when external content changes the editing baseline.

Route keyboard, workspace menu and browser history input to the editor.
Restore UTF-16 selections through a new WASM binding, preserving styles,
readings and paragraph settings without reaching earlier canvas edits.

Verify 1,587 frontend tests, 671 renderer tests and both WASM builds.
Add four editable showcase examples and verify them after reopening.

AI-assisted-by: GPT-6
2026-11-03 17:36:40 +01:00
alonso.torres
6ffc221ac1 🐛 Keep text editing open when an IME cancels composition
Remember when a key-down belongs to native composition. Consume its
Escape release after composition ends, preserving the active text editor.
Keep ordinary Escape behavior for leaving editing and closing menus.

Add regression coverage for native key ordering and composition markers.
Verify 1,583 frontend tests, lint and changed-file formatting. Check native
Pinyin, Zhuyin and Korean input with eight editable showcase examples.

AI-assisted-by: GPT-6
2026-11-03 17:36:40 +01:00
alonso.torres
8ccdadee98 🐛 Preserve emphasis across adjacent ruby spans
Recover merged annotation styles on the base character's own line, using
separate UTF-16 line anchors and UTF-8 style keys. Include emphasis marks
in horizontal text bounds so standalone exports keep each mark whole.

Add regression coverage for adjacent readings, wrapped text, emoji line
anchors, raster output and export bounds across all text grow modes.
Verify 670 renderer tests, formatting and both WASM builds. Add four
editable showcase examples and check their layout and exports.

AI-assisted-by: GPT-6
2026-11-03 17:36:40 +01:00
alonso.torres
99eba5eff5 🐛 Keep indivisible CJK units together in narrow text boxes
Retain legal line starts at the original wrap width before allowing an
indivisible punctuation or annotation unit to overflow together. Share the
layout plan and alignment offset across painting, editing and exports.

Cover mixed styles, nonbreaking controls, graphemes, annotations, alignment,
strokes and resize. All 666 renderer tests and both WASM builds pass.

AI-assisted-by: GPT-6
2026-11-03 17:36:40 +01:00
alonso.torres
6383c20e38 ✨ Add Mandarin Zhuyin reading layout
Add typed one-character Zhuyin readings with upright right-side symbols
and separate neutral and non-neutral tones in both writing modes.
Persist the mode through the editor, WASM bridge and Plugin API. Preserve
annotation clearance and canvas, PNG, PDF and SVG geometry.

Validate syntax and selection boundaries, and cover schema, bridge,
rendering, UI and API behavior. Native IME and reader review remain open.

AI-assisted-by: gpt-6
2026-11-03 17:36:40 +01:00
alonso.torres
e66e507002 ✨ Add Chinese emphasis and underline positions
Carry optional span positions through the schema, WASM header, editor,
Plugin API and CSS output. Expose Chinese controls with language defaults
and independent overrides for horizontal and vertical text.

Reserve automatic clearance per side and stack underlines, ruby and
emphasis. Preserve reading groups during decoration edits and retain
canonical position values in DOM editor style data.

Add protocol, geometry, editing and API tests, including the prior Japanese
mixed-font gutter behavior. Verify both WASM targets and live exports.

AI-assisted-by: GPT-6
2026-11-03 17:36:40 +01:00
alonso.torres
98926a04c7 ✨ Add Chinese Pinyin reading controls
Support typed character and group readings through the shared ruby model.
Expose Chinese labels, presentation controls and selection guards without
changing Japanese controls or generating pronunciation.

Preserve complete source ranges for glyphs sharing a combining cluster.
Add pinned horizontal and vertical tone-mark fixtures and editor tests.

AI-assisted-by: GPT-6
2026-11-03 17:36:40 +01:00
alonso.torres
fe0f7b1285 ✨ Add CJK layout validation across box sizes
Cover Chinese regional profiles and Korean wrap modes across fitting
widths, style boundaries, punctuation, numeric units and decomposed Jamo.
Check stable offsets and finite geometry in narrower horizontal boxes,
and keep indivisible punctuation units together in short vertical boxes.

AI-assisted-by: GPT-6
2026-11-03 17:36:40 +01:00
alonso.torres
645a977ece ✨ Add Chinese regional and Korean word layout
Add named Chinese punctuation presets and a Korean keep-words mode for
horizontal and vertical layout. Preserve the saved spaces-only mode and
keep source offsets stable through safe emergency wrapping.

Apply bounded CJK spacing and justification, expose the new choices in the
sidebar, and document preset bounds and remaining release checks.

Verify renderer, frontend, common and exporter tests, both WASM builds,
and sample save/reload plus PNG, PDF and SVG exports. Existing CI failures
remain documented in the layout notes.

AI-assisted-by: GPT-6
2026-11-03 17:36:40 +01:00
alonso.torres
a841f3cd8a ✨ Add language-specific CJK layout controls
Add independent Japanese, Chinese and Korean file switches and all-files
preferences under the shared vertical-text-layout feature flag. Keep
control availability separate from saved paragraph profiles and rendering.

Expose explicit language profiles in separate sidebar sections. Preserve
mixed values, report the active paragraph profile at the caret, and keep
shared writing-mode controls consistent without radio group collisions.

Validation: common JVM/JS and frontend suites, backend profile tests,
exporter tests and lint pass. Existing runner formatting and Spanish
translation QA issues remain unchanged.

AI-assisted-by: GPT-6
2026-11-03 17:36:40 +01:00
alonso.torres
2eb1fe946a ✨ Add a CJK text layout proof of concept
Choose paragraph layout rules from an optional language tag. Pass language
to horizontal and vertical shaping, add basic Chinese punctuation rules,
and add experimental Korean spaces-only wrapping.

Keep text boundaries grapheme-safe and preserve paragraph profiles across
editor splits and merges. Add reproducible font fixtures for localized
glyphs and regression tests for the shared WASM protocol and layout paths.

Validation: renderer, common JVM/JS, frontend and exporter tests pass.
Both WASM targets build. Full CI still reports baseline lint and format
errors and one backend multipart-upload test failure.

AI-assisted-by: GPT-6
2026-11-03 17:36:40 +01:00
alonso.torres
14193e05a1 🐛 Fix windows IME support 2026-11-03 17:36:40 +01:00
alonso.torres
a79292a898 ✨ Change iconography to reflect orientation 2026-11-03 17:36:40 +01:00
alonso.torres
c88cb7cd66 🐛 Fix issues with auto-width/height 2026-11-03 17:36:40 +01:00
alonso.torres
4898de7f51 ✨ Expose Japanese text layout in the Plugin API
Expose Japanese layout properties on Text and TextRange with validated
values and whole-shape writing mode behavior. Keep ruby readings tied
to their spans when applying styles through ranges.

Update public TypeScript declarations, the API changelog and runtime
and plugin test coverage, including active-page checks.
2026-11-03 17:36:40 +01:00
alonso.torres
a6ddae653d ✨ Add Japanese layout controls and opt-in settings
Enable Japanese layout per file through undoable changes, or for all
files through a saved profile preference. Show the controls when the
WASM renderer and either setting are active.

Add writing mode, orientation, ruby, emphasis, warichu and line fitting
controls with icons and English and Japanese translations. Cover the
file change, profile preference and control behavior.
2026-11-03 17:36:40 +01:00
alonso.torres
5d1c7992c2 ✨ Edit Japanese text with vertical cursors and IME input
Apply Japanese attributes to text selections and whole shapes without
splitting ruby or warichu spans. Respect vertical writing when resizing
text and placing selection handles and pointer cursors.

Anchor native IME input to the WASM caret, keep composition previews
out of the store until commit, and add an optional geometry overlay.
Cover editor styles, serialization, fallback fonts and cursor behavior.
2026-11-03 17:36:40 +01:00
alonso.torres
1a6dc46eb2 ✨ Preserve Japanese text in browser rendering and export
Render Japanese annotations in HTML and SVG and expose their styles
in CSS code generation. Preserve vertical position data from WASM,
while keeping the legacy SVG renderer on its horizontal layout path.

Cover generated markup, styles and copied SVG output.
2026-11-03 17:36:40 +01:00
alonso.torres
a641cbab5d ✨ Render Japanese text layouts in WASM
Lay out horizontal and vertical Japanese text with kinsoku, ruby,
emphasis, warichu, tate-chu-yoko and proportional font features.
Carry the layout through glyph paths, SVG export and text selection.

Update the Rust and ClojureScript binary formats together, including
the editor bridge and headless exporter. Resolve CJK fallback fonts
and add renderer tests and font fixtures.
2026-11-03 17:36:40 +01:00
alonso.torres
b3f7bbf119 ✨ Define Japanese text layout attributes
Add schemas, defaults and helpers for vertical writing, ruby, emphasis
and warichu. Keep readings and notes tied to their own characters when
copying styles, and preserve base text when pasting HTML ruby.

Cover the model, property copying, clipboard and library file builder.
2026-11-03 17:36:39 +01:00
Nimesh
88cd0933bd
🐛 Raise and make configurable the MCP request body size limit (#12450)
The MCP endpoint parsed request bodies with express.json() and its
default 100kb limit, so large tool calls such as execute_code failed
with HTTP 413 (PayloadTooLargeError). Read the limit from the new
PENPOT_MCP_MAX_BODY_SIZE env var, defaulting to 10mb, and document it.

Closes #12080

Signed-off-by: Nimesh Kulkarni <167794819+GitNimay@users.noreply.github.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-11-03 16:21:28 +01:00
GennoBou
029d9c0af7
🐛 Fix Japanese autodetection fallback on "ja" locale (#12283)
Most browsers return "ja" rather than "ja-JP" in navigator.language.
Because parse-locale expands "ja-JP" to ["ja_jp" "ja"] but keeps "ja"
as ["ja"], using "ja_jp" causes autodetection to fall back to English.

Normalize the Japanese locale identifier to "ja" and map jpn_JP.po to
translation.ja.js, matching other single-region locales like Ukrainian.

AI-assisted-by: gemini-3.8-flash
2026-11-03 16:11:22 +01:00
Álvaro Tejero-Cantero
b19ad65581
🔧 Store devenv workspace clones in the XDG data directory (#11986)
manage.sh has kept the ws1+ workspace clones in
~/.penpot/penpot_workspaces since parallel instances landed (#9906).
That directory sits outside the XDG base directory layout. The default
is now $XDG_DATA_HOME/penpot-devenv/workspaces, with ~/.local/share
standing in when the variable is unset or empty. It is the data
directory and not the cache, because a clone can hold commits that
exist nowhere else.

A directory that already exists at the former default keeps being
used, so existing clones are neither orphaned nor seeded again. An
exported PENPOT_WORKSPACES_DIR still overrides both.

AI-assisted-by: claude-opus-5-5

Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-11-03 16:06:44 +01:00
Florian Schrödl
d1c8b0f959
🐛 Report tokens named below another token with tokenscript (#12529)
A token cannot contain tokens: next to a token `a`, the tokens `a.b`
and `a.b.c` are now invalid with tokenscript, whatever the order of
their sets. As with StyleDictionary, their pills and the token form
show "Group name of a.b conflicts with a token of the same name in
another active set." and `{a.b}` is a missing reference.

The interpreter reports them as :processor/token-name-collision, which
the adapter maps to :error.token/name-collision. frontend/deps.edn
updates penpot/tokenscript-interpreter to v0.2.0, which adds the check.
Since v0.1.0 its errors carry codes and data only, which the adapter
already relies on.

Signed-off-by: Florian Schroedl <flo.schroedl@gmail.com>
2026-11-03 16:06:16 +01:00
penpot-renovate[bot]
2bf9f4c78b
⬆️ Update DHI base images (#12767)
- dhi.io/debian-base trixie-debian13-dev@af61a32 (docker/images/Dockerfile.backend)
- dhi.io/debian-base trixie-debian13-dev@af61a32 (docker/images/Dockerfile.backend)
- dhi.io/debian-base trixie-debian13@834d20c (docker/imagemagick/Dockerfile)
- dhi.io/debian-base trixie-debian13-dev@af61a32 (docker/imagemagick/Dockerfile)
- dhi.io/debian-base trixie-debian13-dev@af61a32 (docker/devenv/Dockerfile)
- dhi.io/nginx 1.31.8-debian13-dev@f69a0ea (docker/images/Dockerfile.frontend)
- dhi.io/node 24.22.2-debian13-dev@df819cf (docker/images/Dockerfile.media-processor)
- dhi.io/node 24.22.2-debian13-dev@df819cf (docker/images/Dockerfile.mcp)
- dhi.io/node 24.22.2-debian13-dev@df819cf (docker/images/Dockerfile.exporter)

Signed-off-by: penpot-renovate[bot] <338182062+penpot-renovate[bot]@users.noreply.github.com>
Co-authored-by: penpot-renovate[bot] <338182062+penpot-renovate[bot]@users.noreply.github.com>
2026-11-03 16:03:28 +01:00
penpot-renovate[bot]
b2be633b91
⬆️ Update pnpm to v12.16.1 (#12778)
- pnpm 12.16.1 (mcp/packages/browser-extension/package.json)

Signed-off-by: penpot-renovate[bot] <338182062+penpot-renovate[bot]@users.noreply.github.com>
Co-authored-by: penpot-renovate[bot] <338182062+penpot-renovate[bot]@users.noreply.github.com>
2026-11-03 16:02:56 +01:00
Andrey Antukh
be37341f36 Merge remote-tracking branch 'origin/staging' into develop 2026-11-03 15:07:18 +01:00
Andrey Antukh
610813281b Merge remote-tracking branch 'origin/main-2.18.x' into staging 2026-11-03 15:06:32 +01:00
Andrey Antukh
41654aa1b7
✨ Restrict management RPC methods to their declared shared keys (#12736)
* ✨ Restrict management RPC methods to their shared keys

Management methods now declare the shared-key ids allowed to call them
with ::rpc/shared-keys. A wrapper in wrap-management checks the key id
from the request before opening a transaction, so a valid key outside
the declared set answers 403 shared-key-not-allowed. Methods without
the metadata keep accepting any configured key.

Scope upload-tempfile to exporter and media-processor, file analysis to
nexus, durable jobs to exporter, and the Nitrate methods to
admin-console. The test helper injects a declared key for the allowed
path, and a coverage test fails when a management method is missing or
declares the wrong set.

Closes #12730

AI-assisted-by: deepseek-v4.1-flash

* ♻️ Harden shared-key authorization scope and tests

Rename the per-method metadata to ::rpc/allowed-shared-keys so it reads
as the allowed key ids, not the secrets. Fix the management test helper
to resolve the key from the supplied request and to keep an explicit nil,
which prevents a rejection test from silently using an allowed key.

Strengthen coverage: assert every management namespace is scanned, check
all four job methods reject a key they do not declare, verify a rejected
complete-job leaves the row and its events untouched, and exercise both
keys allowed on upload-tempfile. Log denied keys with the method name and
key id, without the secret, and update the audit-log memory for
push-audit-events.

Closes #12730

AI-assisted-by: deepseek-v4.1-flash
2026-11-03 13:49:36 +01:00
Alonso Torres
410bde8103
🐛 Move displaced path content back onto its box (#12754)
Some files have component copies whose path drawing (:content) sits away
from its box (:selrect and :points). The renderer draws the content, while
the sidebar, layouts and syncs use the box, so these icons render outside
their boards and are clipped or drawn over other shapes. Moving the shape
keeps the gap, and sync never repairs it because both groups are touched.

Add the migration 0034-fix-path-content-offset. For every path in pages
and in component objects, deleted components included, it derives the box
from the content as update-geometry does and, when the sizes agree but the
position does not, moves the content onto the stored box. The box, the
transform and the touched groups stay as they are; paths whose drawing has
another size are left untouched.

Closes #12752
2026-11-03 12:12:08 +01:00
Andrey Antukh
afe6c1b99c
🐛 Confine upload paths to temp dirs (#12772)
* 🐛 Confine upload paths to temp dirs

Reject upload maps whose path leaves the server temp dirs.
Transit bodies can forge a Path such as /etc/passwd and the
handlers open it directly. Centralize the realpath prefix
check in media validation and apply it to all nine upload
fields. Legit multipart files under /tmp/undertow/ or
/tmp/penpot/ still pass.

AI-assisted-by: muse-spark-1.3-contributor

* ♻️ Rename upload validators to check-*

Drop the validate prefix and the bang from the media
validation helpers for a consistent check naming. Move
the temp dir confinement into validation and cover all
nine upload fields. Add unit tests for each helper.

AI-assisted-by: muse-spark-1.3-contributor

* 🐛 Fix svg upload tests to use penpot tmp dir

SVG upload tests wrote tempfiles to the system tmp dir,
outside the allowed upload prefixes, so check-upload-path
rejected them. Point write-temp-svg at /tmp/penpot like
the other upload tests.

AI-assisted-by: muse-spark-1.3-contributor
2026-11-03 12:07:52 +01:00