1823 Commits

Author SHA1 Message Date
Dexterity
3885c9ee74
🔥 Remove stray prn debug log in stroke-row* render (#9318) 2026-05-06 17:43:58 +02:00
Andrey Antukh
34cc0e9d56 🔥 Materialize the canary feature flag across the codebase
Remove the :canary flag from the flags definition and make all
features gated behind it always available:

- Enable "download font" option in dashboard fonts context menu
- Enable Tab/Shift+Tab keyboard navigation for renaming shapes
  in layer items
- Enable "duplicate color" option in asset panel when applicable
- Enable "duplicate typography" option in asset panel when applicable
- Enable "copy as image" context menu option for frame shapes

Also remove unused [app.config :as cf] requires from files that
no longer reference it after the materialization.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-05-06 11:19:04 +02:00
Eva Marco
a2bcbe81dd
🎉 Add token numeric inputs for inputs on right sidebar (#9143)
Co-authored-by: Xavier Julian <xavier.julian@kaleidos.net>
2026-05-04 13:02:19 +02:00
FairyPiggyDev
ed021711b6
♻️ Extract make-delete-asset-group-fn helper for assets panel (#9211)
Reviewer follow-up on PR #9151. The "Delete group" handler was
duplicated across the three assets-panel sections (colors,
typographies, components), each carrying the same skeleton — filter
by group path, build an undo-id, run the deletes inside one
transaction, and show the same confirm modal — with only the path
predicate and the per-asset delete event differing.

Add `app.main.ui.workspace.sidebar.assets.common/make-delete-asset-group-fn`
that takes the differing parts as options:

- `:assets`             collection to filter.
- `:on-clear-selection` invoked before the deletes.
- `:delete-events`      `(fn [matching-assets] => seq-of-events)`.
- `:path-filter`        predicate (defaults to `str/starts-with?`),
                        overridden to `cpn/inside-path?` for
                        components so nested group paths match the
                        same way the existing ungroup/combine helpers
                        do.

The factory returns `(fn [path] …)` so each call site stays a
straight `mf/use-fn`. The variant-container dedup in components
(one `delete-shapes` per container, not one per sibling variant)
moves into that section's `:delete-events` fn and is unchanged in
behavior.

Cleanup
-------

The `:as i18n` alias is no longer needed in any of the three section
files (its only use was `i18n/c` for the modal count, which the
helper now handles); reduced to `:refer [tr]`.

Github #9141

Signed-off-by: FairyPigDev <luislee3108@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-04-30 08:48:04 +02:00
Dexterity
f530a0ba26
🔥 Remove stray debug log in color-row component (#9243) 2026-04-29 18:47:27 +02:00
Jack Storment
710fd30f78
🐛 Preserve renamed layer name when re-entering edit mode (#9231)
* 🐛 Preserve renamed layer name when re-entering edit mode

When a layer was renamed and the user clicked its name again to edit
it, the input opened with the type-based default name instead of the user's saved name. Pressing Enter then
silently overwrote the saved name with the default. Read the current
shape :name when seeding the rename input so the user's previous
rename is preserved.

Signed-off-by: jack-stormentswe <crazycoder131@gmail.com>

* 💄 Remove redundant DOM-refresh effect from layer rename input

Signed-off-by: jack-stormentswe <crazycoder131@gmail.com>

---------

Signed-off-by: jack-stormentswe <crazycoder131@gmail.com>
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-04-29 18:07:48 +02:00
Dream
d668744a1f
Add search to board size presets dropdown (#9117)
Closes #4658

Signed-off-by: eureka0928 <meobius123@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-04-29 15:44:53 +02:00
FairyPiggyDev
61ce4b9e0d
Add "Delete group" to assets panel context menu (#9151)
When working with large asset groups, users asked for a one-click way
to remove every asset under a group path. Multi-select across hundreds
of items is impractical, and ungrouping first and then deleting leaves
the orphaned items in the flat list.

This change adds a "Delete group" option to the assets-panel
context-menu for three asset types that already carry group structure:

- Components (including variants — sibling variants sharing a variant
  container are deduplicated, and the container is deleted once via
  the same dispatch the per-item delete uses in file_library.cljs).
- Colors.
- Typographies.

A confirmation modal is shown before deletion, with the count of
assets to be removed, so the action is never silent. All deletes run
inside a single undo transaction, so one Cmd+Z restores the whole
group.

Changes
-------

- `assets/groups.cljs` — `asset-group-title*` accepts an optional
  `on-delete-group` prop and conditionally adds the menu entry
  between "Ungroup" and "Combine as variants". When the callback is
  not supplied the option is hidden, so asset sections that do not
  implement it stay unaffected.
- `assets/components.cljs` — threads `on-delete-group` through the
  recursive `components-group*` and defines the section-level
  handler, dispatching to `dwsh/delete-shapes` for variant containers
  and `dwl/delete-component` for plain components.
- `assets/colors.cljs` — same threading + a simple `dwl/delete-color`
  dispatch per color in the group.
- `assets/typographies.cljs` — same threading + a
  `dwl/delete-typography` dispatch per typography in the group.
- `translations/en.po` — three new strings: the menu label
  (`workspace.assets.delete-group`) and the modal title/message
  (`modals.delete-asset-group.title`/`.message`, plural-aware).

Github #9141

Signed-off-by: FairyPigDev <luislee3108@gmail.com>
Signed-off-by: FairyPiggyDev <luislee3108@gmail.com>
2026-04-28 15:59:05 +02:00
Andrey Antukh
b0ce644752 Merge remote-tracking branch 'origin/staging' into develop 2026-04-28 10:12:40 +02:00
Andrey Antukh
d9f099841a
🔥 Remove redundant mf/props metadata from modern components (#9192)
The ::mf/props and ::mf/wrap-props metadata keys are no-ops on modern
components (those defined with mf/defc and the * suffix) since the *
suffix already triggers the props behavior these keys attempt to
configure. This cleanup removes the redundant metadata from modern
components across all UI directories.

Changes:
- comments/: comments
- dashboard/: comments, deleted, files, fonts, grid, import, libraries,
  pin_button, projects, search, sidebar, subscription, team, templates
- exports/: files
- modal/: modal
- settings/: subscription
- static/: static
- viewer/: comments, interactions, viewer
- workspace/: context_menu, libraries, sidebar/assets,
  viewport/gradients, tokens/settings/menu
2026-04-28 09:23:56 +02:00
Jack Storment
aa5bfe6dda
Add customizable pixel grid color (#9155)
Let users pick the pixel grid color from a standard color picker.
The grid color was previously hardcoded, making it invisible on
mid-tone canvases. Choice is stored on the file so it persists
across sessions. Defaults preserve the current appearance when
unset.

Closes #7750

Signed-off-by: jack-stormentswe <crazycoder131@gmail.com>
2026-04-27 21:37:27 +02:00
Milos Milic
bd1e0fb23f
Add Alt+click to expand a layer subtree in the Layers sidebar (#9179)
Closes #7736.

The Layers sidebar offered no way to expand every nested level of a
single subtree at once. Unfolding a layer that wraps a deep tree
required clicking each disclosure indicator one level at a time -
O(siblings * depth) clicks. The asymmetry was particularly visible
next to the existing Shift+click gesture, which collapses every
layer in the panel in a single action via `dwc/collapse-all`, with
no expand counterpart for either a single subtree or the whole
tree.

Add a new `dwc/expand-subtree` event in
`app.main.data.workspace.collapse` that uses
`cfh/get-children-ids-with-self` to gather the shape's id together
with every descendant id, then merges `{descendant-id true}` entries
into `[:workspace-local :expanded]` so the entire subtree opens in
one update. Existing expansion state on unrelated branches is left
untouched (`merge`, not `assoc`), matching the per-key shape used by
`toggle-collapse` and `expand-collapse`.

Wire the gesture into `layer_item.cljs` `toggle-collapse` callback as
a third branch:

  - Shift+click while expanded - collapse every layer (existing).
  - Alt+click while collapsed   - expand the entire subtree (new).
  - Otherwise                   - toggle this single level (existing).

Alt is chosen instead of Shift to avoid the ambiguity the issue
author flagged: "for a layer of middle depth it is unclear whether
[Shift+click] should fold all (up to the topmost parent) or expand
all (only the current subtree)". Alt is a common platform
convention for "do this recursively" (Finder, file managers,
several IDEs), so the asymmetric mapping matches user expectations.
The callback's `mf/deps` vector is extended with `id` and `objects`
so the closure refreshes when the shape tree changes.

CHANGES.md entry added under the 2.17.0 New features section.
2026-04-27 21:36:15 +02:00
Andrey Antukh
01d68ec09b Merge remote-tracking branch 'origin/staging' into develop 2026-04-24 14:16:03 +02:00
Alejandro Alonso
89a1ee7813 Merge remote-tracking branch 'origin/main-staging' into staging 2026-04-24 12:06:27 +02:00
Alejandro Alonso
58fae0a04d 🐛 Fix text.cljs error from staging merge 2026-04-24 10:10:00 +02:00
Eva Marco
5a7ba7ee7e
🐛 Fix multiple selection on shapes with token applied to stroke-color (#9110)
*  Remove the need to navigate to page for deletion operation

* 🐛 Fix multiple selection with applied-tokens on stroke-color

* 🐛 Fix button position on page header

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-04-24 09:47:44 +02:00
Alejandro Alonso
984d292ab2 Merge remote-tracking branch 'origin/staging' into develop 2026-04-24 09:29:24 +02:00
wdeveloper16
e280168de9
Add read-only preview mode for saved versions (#7622) (#8976)
*  Add read-only preview mode for saved versions (#7622)

* 🔧 Address review feedback on version preview (#7622)

* 🐛 Fix version preview for WASM renderer (#7622)

* 🐛 Fix stylelint color-named and color-function-notation in preview banner (#7622)

* 🐛 Fix invalid-arity call to initialize-workspace in exit-preview (#7622)

* 🐛 Fix unclosed defn paren in exit-preview (#7622)

* ♻️ Refactor version preview/restore flow

Separate enter-preview and enter-restore flows with dedicated dialogs
instead of a persistent banner. Removes preview-banner component in favor
of inline actions dialog. Uses backup/restore pattern for exit-preview
instead of full workspace reinitialization. Adds analytics events for
preview/restore actions.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>

*  Extract on-name-input-focus as namespace-level private function

The callback had no dependencies on component-local state or props,
making it a pure function that can be hoisted to a defn-. This avoids
recreating the same callback identity on every render of version-entry*.

*  Extract extract-id-from-event helper to deduplicate snapshot callbacks

Three callbacks in snapshot-entry* shared the same DOM extraction logic
(get current target, read data-id, parse UUID). Extracted into a private
defn- to remove the duplication and simplify each callback.

*  Extract pure state-update callbacks from versions-toolbox* to namespace level

Eight callbacks that only emit fixed Potok events with no meaningful
deps were hoisted out of the component as defn- functions:

- on-create-version
- on-edit-version
- on-cancel-version-edition
- on-rename-version
- on-delete-version
- on-pin-version
- on-lock-version
- on-unlock-version

These no longer need mf/use-fn wrappers since namespace-level functions
have stable identity across renders, avoiding unnecessary callback
recreation on each render cycle.

*  Rename filter parameter to filter-value in on-change-filter to avoid core shadowing

The parameter name 'filter' shadowed clojure.core/filter within the
function scope. Renamed to 'filter-value' for clarity and to prevent
potential bugs if core/filter were needed in future changes.

* 🔧 Fix linter warnings and errors across version-related namespaces

frontend/src/app/main/ui/workspace.cljs:
- Remove unused requires: app.common.data, app.main.data.notifications,
  app.main.data.workspace.versions

frontend/src/app/main/data/workspace/versions.cljs:
- Remove unused require: app.common.uuid
- Fix duplicate reify type: enter-restore used ::restore-version
  (same as the private restore-version fn), renamed to ::enter-restore
- Remove unused bindings: state in enter-restore, team-id in
  exit-preview and restore-version-from-plugin

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Signed-off-by: wdeveloper16 <wdeveloer16@protonmail.com>
Co-authored-by: wdeveloper16 <wdeveloer16@protonmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-04-24 08:13:16 +02:00
Eva Marco
0c60db56a2
🐛 Fix multiselection error with typography texts (#9071)
* 🐛 Ensure typography-ref attrs are always present and fix nil encoding

Add :typography-ref-file and :typography-ref-id (both defaulting to nil)
to default-text-attrs so these keys are always present in text node maps,
whether or not a typography is attached.

Skip nil values in attrs-to-styles (Draft.js style encoder) and in
attrs->styles (v2 CSS custom-property mapper) so nil typography-ref
entries are never serialised to CSS.

Replace when with if/acc in get-styles-from-style-declaration to prevent
the accumulator from being clobbered to nil when a mixed-value entry is
skipped during style decoding.

* 🎉 Add test

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-04-23 16:08:56 +02:00
Eva Marco
d6b341c053
🐛 Fix color token (#9095) 2026-04-23 10:51:30 +02:00
Eva Marco
5c9696e20c
🐛 Fix color dropdown option update (#9100) 2026-04-23 10:51:20 +02:00
Eva Marco
28b33b9acc
🐛 Fix props on text components (#9099) 2026-04-23 10:49:48 +02:00
Andrey Antukh
ba42cc04b7 ♻️ Derive v-sizing from values instead of passing as prop
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-22 14:17:15 +00:00
Eva Marco
c02f0a2bc9
🐛 Fix grow options (#9097)
* 🐛 Fix grow options

* 🐛 Fix props when hidden is nil
2026-04-22 11:09:44 +02:00
Andrey Antukh
81faa5a728 Replace duplicate on-blur lambda with stable on-text-blur ref
The inline (fn [] (ts/schedule ...)) passed as :on-blur to text-options
was an exact copy of the on-text-blur callback already defined via
mf/use-fn earlier in the same let block. Pass on-text-blur directly
to avoid allocating a new function object on every render.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-22 08:55:29 +02:00
Andrey Antukh
7751d9a69b Remove spurious deps from toggle callbacks in text-menu*
toggle-main-menu and toggle-more-options close over a state atom and
need no external deps. The declared deps (main-menu-open? /
more-options-open?) were unused inside the function bodies, causing
each callback to be reallocated on every toggle — self-reinforcing
churn. Drop the mf/deps calls to make both callbacks stable.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-22 08:55:29 +02:00
Andrey Antukh
74d1288003 Hoist token-typography-row? flag check to namespace level
(contains? cf/flags :token-typography-row) is a pure constant:
cf/flags is immutable after startup. Define it once as a private
namespace-level var token-typography-row? instead of re-evaluating
the check on every render in text-decoration-options* and text-menu*.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-22 08:55:29 +02:00
Andrey Antukh
d28c0ea066 Memoize label computation in text-menu* component
Wrap the (case type (tr ...) ...) expression in mf/with-memo [type]
so the translation is resolved only when the type prop changes
instead of on every render.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-22 08:55:29 +02:00
Andrey Antukh
a94a7221fb Memoize options in text-decoration-options* component
Wrap the two radio-button option maps in mf/with-memo [token-applied]
so the vector and its (tr ...) calls are evaluated only when the
token-applied prop changes, not on every render.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-22 08:55:29 +02:00
Andrey Antukh
b8aa243c2b Memoize static options in grow-options* component
Wrap the three radio-button option maps in mf/with-memo [] so the
vector and its (tr ...) calls are evaluated once per mount instead
of on every render.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-22 08:55:29 +02:00
Andrey Antukh
dfd992aa49 Memoize static options in text-direction-options* component
Wrap the two radio-button option maps in mf/with-memo [] so the
vector and its (tr ...) calls are evaluated once per mount instead
of on every render.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-22 08:55:29 +02:00
Andrey Antukh
466f27eb7c Memoize static options in text-align-options* component
Wrap the four radio-button option maps in mf/with-memo [] so the
vector and its (tr ...) calls are evaluated once per mount instead
of on every render.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-22 08:55:29 +02:00
Andrey Antukh
6c19c7c0c4 Memoize static options in vertical-align* component
Wrap the radio-button options vector in `mf/with-memo []` so the
vector allocation and `(tr ...)` calls happen once per component
mount instead of on every render.

Also document the translation memoization rule in frontend/AGENTS.md:
`(tr ...)` must never be called at namespace level (locale is
runtime-only), and static option lists should always be wrapped in
`mf/with-memo []`.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-22 08:55:29 +02:00
Andrey Antukh
6723e3bbea 🐛 Fix issues after staging merge 2026-04-21 22:52:19 +02:00
Andrey Antukh
c259fbdb5b Merge remote-tracking branch 'origin/staging' into develop 2026-04-21 22:43:46 +02:00
Andrey Antukh
c1688edf66 ♻️ Convert frame-grid and grid-options to modern * suffix
Rename grid-options -> grid-options*, frame-grid -> frame-grid*.
Update internal call and call site in shapes/frame.cljs to [:> ...] syntax.
2026-04-21 20:31:30 +02:00
Andrey Antukh
08247aec3f ♻️ Convert svg-attrs-menu and attribute-value to modern * suffix
Rename attribute-value -> attribute-value*, svg-attrs-menu -> svg-attrs-menu*.
Update all call sites (circle, path, svg_raw, rect, group) to use [:> ...] syntax.
2026-04-21 20:31:30 +02:00
Andrey Antukh
95ca68e2b8 ♻️ Convert history-entry-details to modern rumext * format
Rename to history-entry-details* and update internal call site.
2026-04-21 20:31:30 +02:00
Andrey Antukh
e9e6796f05 ♻️ Convert text-menu and sub-components to modern rumext * format
Rename text-align-options, text-direction-options, vertical-align,
grow-options, text-decoration-options and text-menu to their * variants.
Update all call sites in shapes/text.cljs, shapes/group.cljs and
shapes/multiple.cljs.
2026-04-21 20:31:30 +02:00
Andrey Antukh
0e5d3e2619 ♻️ Convert blur-menu, constraints-menu and stroke-menu to modern rumext * format
Rename components to blur-menu*, constraints-menu* and stroke-menu*.

Update :refer imports and all [:& ...] call sites to [:> ...*] for
blur-menu*, constraints-menu* and stroke-menu* across all nine
shapes-specific options panels.
2026-04-21 20:31:30 +02:00
Andrey Antukh
d8340d765a Merge remote-tracking branch 'origin/staging' into develop 2026-04-21 20:28:38 +02:00
Andrey Antukh
2bbd63287f Merge remote-tracking branch 'origin/main' into staging 2026-04-21 19:22:50 +02:00
Andrey Antukh
6eccffb8bb
🐛 Fix incorrect handlig of version restore operation (#9041)
- Add session ID tracking to RPC layer (backend and frontend)
- Send session ID header with RPC requests for request correlation
- Rename file-restore to file-restored for consistency
- Extract initialize-file function from initialize-workspace flow
- Improve file restoration initialization with wait-for-persistence
- Extract initialize-version event handler for version restoration
- Fix viewport key generation with file version numbers for proper re-renders
- Update layout item schema and constraints to use internal sizing state
- Add v-sizing state retrieval in layout-size-constraints component
- Refactor file-change notifications stream handling with rx/map
- Fix team-id lookup in restore-version-from-plugins

Improves request traceability across frontend/backend sessions and streamlines
the workspace initialization flow for file restoration scenarios.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-21 19:19:51 +02:00
Andrey Antukh
aed2f8a8f8
🐛 Fix removeChild errors from unmount race conditions (#8927)
Guard imperative DOM operations (removeChild, RAF callbacks) against
race conditions where React has already unmounted the target nodes.

- assets/common.cljs: add dom/child? guard before removeChild in RAF
- dynamic_modifiers.cljs: capture RAF IDs and cancel them on cleanup;
  add null guards for DOM nodes that may no longer exist
- hooks.cljs: guard portal container removal with dom/child? check
- errors.cljs: extract is-ignorable-exception? to a top-level defn
  and add NotFoundError/removeChild to ignorable exceptions, since
  these are caused by browser extensions modifying React-managed DOM
- Add unit tests for is-ignorable-exception? predicate

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-21 17:31:05 +02:00
Eva Marco
f18670ed00
🐛 Fix errors from numeric input design review (#8993)
* 🐛 Fix blur input after enter value

* 🐛 Catch error on invalid maths

* 🐛 Fix race condition

* 🎉 Add tests that cover issues

* 🐛 Fix padding applying only to one side

* 🐛 Fix show broken pill when reference is on not active set
2026-04-21 14:39:06 +02:00
Xaviju
78c48f1953
🐛 Fix broken update library notification link UI (#9070)
* 🐛 Fix broken update library notification link UI

* ♻️ Format and lint
2026-04-21 13:33:38 +02:00
alonso.torres
3da74ed864 🐛 Fix problem with component thumbnails in swap component panel 2026-04-21 13:11:03 +02:00
Elena Torro
62ec66b974 Add lazy async rendering for component thumbnails 2026-04-21 11:40:53 +02:00
Alejandro Alonso
0d17debde7 Merge remote-tracking branch 'origin/staging' into develop 2026-04-21 08:24:29 +02:00
Xaviju
e9105f3670
♻️ Fix linter errors under legacy resources scss (#9035) 2026-04-20 23:58:53 +02:00