Omit nil optional profile fields before frontend schema validation and RPC persistence. Preserve omitted language and theme values in backend updates, and add regression coverage for partial profile saves.
AI-assisted-by: gpt-5.6-luna
When copying an access token over plain HTTP (non-secure context), the
browser does not expose navigator.clipboard, causing to-clipboard to
return a rejected Promise. The caller was ignoring the Promise entirely,
so the rejection became an unhandled exception that crashed the UI.
Fix: chain .then/.catch on the returned Promise so that a successful
copy shows the existing success toast and a failure (including
insecure-origin) shows an error toast using the existing
errors.clipboard-api-unavailable translation key.
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
The grid-item-metadata* component always used :will-be-deleted-at (falling
back to :modified-at) and always showed the "Will be deleted %s" tooltip,
even for files in the Recent tab that have no deletion date.
Now the component branches on the presence of :will-be-deleted-at:
- Deleted files: show the deletion timeago with the existing
"Will be deleted %s" tooltip.
- Regular files: show :modified-at timeago with a new
"Last modified %s" tooltip key (dashboard.grid.last-modified-at).
Closes#10873
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
The grid-item-metadata* component always used :will-be-deleted-at (falling
back to :modified-at) and always showed the "Will be deleted %s" tooltip,
even for files in the Recent tab that have no deletion date.
Now the component branches on the presence of :will-be-deleted-at:
- Deleted files: show the deletion timeago with the existing
"Will be deleted %s" tooltip.
- Regular files: show :modified-at timeago with a new
"Last modified %s" tooltip key (dashboard.grid.last-modified-at).
Closes#10873
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Pasting text could throw "Unknown node type" and lose the paste. The
insertion paths assume the caret sits on a text node or a <br>, but the
browser can report it on a container element (the offset being a child
index, common in Firefox) or, for an empty text shape that was just
focused, on nothing at all: selectAll() returned early without ever
setting a selection.
Add resolveTextNodePosition(), which walks a (node, offset) pair down to
the addressed text node or line break and returns null instead of
throwing when it cannot. The selection controller normalizes the caret
with it before inserting text or a pasted fragment, and selectAll() now
collapses on the line break of an empty editor so the caret is always
usable.
Closes#11149
AI-assisted-by: longcat-2.0-free
Make sd-token-uuid nil-safe when accessing .original.id to prevent
crashes when StyleDictionary emits group nodes alongside real tokens.
Group nodes have an original object but no id property, causing
undefined is not an object errors during interactive token resolution
in the edit modal.
Closes#11143
AI-assisted-by: qwen3.7-plus
The sidebar measures panel numeric inputs (X, Y, width, height,
rotation) emitted one full apply-modifiers commit per DOM event with
no throttle: every arrow key-repeat, wheel tick and scrub pointermove
became update-positions / update-dimensions / increase-rotation. A
sustained gesture starved the React renderer and crashed the
workspace with error #185 (Maximum update depth exceeded).
Coalesce those bursts at the data layer (potok), following the
update-position-data debounce pattern in texts.cljs:
- update-positions is now burst-coalesced in place (its only caller
is the measures panel); new update-dimensions-coalesced and
increase-rotation-coalesced variants are used by the measures
panel, while the immediate events keep serving plugins, variants
and token application (including the delta? rotation path).
- The first event of a burst commits immediately (leading edge, so
single edits stay synchronous); further ticks commit at most once
per 50 ms (throttle); a trailing debounced flush guarantees the
exact final value lands. All payloads are absolute values, so
keeping the latest queued value per shape/attribute is lossless.
- Pending payloads are drained atomically and stale shape ids
(deleted mid-burst) are skipped. The drain stream lives until the
workspace is finalized, so bursts reuse a single subscription.
- Fewer commits per burst also means fewer undo entries; scrub drags
still produce a single entry via the input's outer transaction.
Tests: new frontend-tests.logic.sidebar-transform-coalescing-test (8
tests, legacy SVG and WASM renderer branches) guards the invariant
that a 20-event burst commits the exact final value in a handful of
commits. The previously unregistered update-position-test is wired
into the runner with WASM mock fixtures (it fails in full-suite
context without them due to a pre-existing global mock-state issue).
AI-assisted-by: kimi-k3
* 🐛 Fix editor v3 quitting when changing typography options
* 🎉 Apply text styles to collapsed caret
* 🐛 Fix not persisting the new selrect
* 🐛 Fix selrect not being recomputed on caret style changes
* 🐛 Fix quitting the editor when changing typography on empty texts
Enable Playwright's JSON reporter alongside `list` and publish a
summary of flaky tests to the job step summary. The JSON report is
kept as an artifact for 30 days so flakiness rates can be aggregated
over time.
CI already runs with `retries: 2`, so unstable tests have been passing
silently on retry. This only surfaces what the suite already absorbs;
no test behaviour changes.
The reporter in `frontend/scripts/test-e2e` becomes overridable via
`PLAYWRIGHT_REPORTER` so the local developer default stays untouched.
Enable Playwright's JSON reporter alongside `list` and publish a
summary of flaky tests to the job step summary. The JSON report is
kept as an artifact for 30 days so flakiness rates can be aggregated
over time.
CI already runs with `retries: 2`, so unstable tests have been passing
silently on retry. This only surfaces what the suite already absorbs;
no test behaviour changes.
The reporter in `frontend/scripts/test-e2e` becomes overridable via
`PLAYWRIGHT_REPORTER` so the local developer default stays untouched.
The comments layer lives in the viewport overlays, which are absolutely
positioned above the canvas, and the container itself carries a high
z-index. A comment bubble panned into the ruler bars therefore painted
on top of them, covering the ticks and numbers.
Clip the comments container to the area outside the ruler bars while
the rulers are visible, the same thing the `clip-handlers` clip path
already does so the selection handlers stay off the rulers. Clipping
only the comments container leaves the text editing overlay, which
shares the viewport overlays, untouched.
Fixes#11163.
Signed-off-by: Filip Sajdak <filip.sajdak@siili.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The design sidebar named the same "mixed values" concept with two
different translation keys. Most sections use settings.multiple, while
the blur options and the design system numeric input used
labels.mixed-values.
Both read "Mixed" in English, so the split is invisible in the default
locale, but labels.mixed-values has no translation at all in 16 locales
and a different wording in 8 more. Where it is missing the string falls
back to the default language, so those controls rendered the English
word next to sections showing the localized one; where both exist, a
single sidebar named the same concept two ways (fr "Divers" against
"Melange", ru "Smeshanyy" against "Smeshat").
Point the two outliers at settings.multiple, the key the rest of the
sidebar already uses and the one translated in every locale that ships
a translation for it.
Fixes#11148.
Signed-off-by: Filip Sajdak <filip.sajdak@siili.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Add `project-id` to the guard condition in `use-plugin-register`'s layout effect so the plugin "Try out" flow waits until projects have loaded.
Previously, only `plugin-url` was checked, which allowed the fetch to fire
before projects were available, sending a nil `project-id` and causing a 400
validation error from the backend.
AI-assisted-by: mimo-v2.5-pro
The fetch-manifest function previously had no timeout, causing the
plugin installation flow to hang indefinitely if the server accepted
the connection but never completed the response.
Added a 15-second timeout using rx/timeout to abort the request
automatically.
Closes#11119
AI-assisted-by: qwen3.7-plus
* ✨ Add headless wasm render backend to the exporter
* ♻️ Move render-wasm bridge to common and split wasm builds
* 🔧 Upload builtin font variants in the wasm exporter
* ♻️ Move shared font and resources utils out of render_wasm
* ⚡ Fetch only the exported roots in the wasm exporter
* ⚡ Bound save_layer rects in the vector export path