* 🐛 Fix incorrect invitation token handling on register process
- Reject prepare-register-profile when an active profile already
exists for the requested email.
- Stop embedding an existing profile's :profile-id into the
prepared-register JWE. Profile resolution in register-profile is
now done exclusively by email lookup, never by a JWE claim.
- Add created? guard to the invitation-success branch in
register-profile, so existing profiles (active or not) cannot
reach session creation via anonymous registration.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* ♻️ Restructure invitation handling inside register-profile
Move the invitation-success branch into the created? sub-cond so it
sits alongside the other post-creation branches, making the control
flow consistent.
- Active new profile + matching invitation: mint session and return
:invitation-token (frontend redirects to :auth-verify-token).
- Not-yet-active new profile + matching invitation: embed the
invitation token inside the verify-email JWE and send the
verification email. When the user clicks the link, they get
logged in and the frontend completes the team-invitation flow.
- Extend send-email-verification! with an optional invitation-token
parameter propagated into the verify-email JWE claims.
- Update the frontend verify-email handler to navigate to
:auth-verify-token when the response carries :invitation-token.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* 🐛 Handle email-already-exists error on registration form
Add a specific handler for the [:validation :email-already-exists] error
code in the registration form's on-error callback. The backend raises
this error when an active profile already exists for the requested email,
but the frontend was falling through to the generic error message.
Now it shows the existing "Email already used" i18n message instead of
the generic "Something wrong has happened" toast.
* 🐛 Reset submitted state on registration form error
The on-error handler in the registration form was not resetting the
submitted? state, causing the submit button to remain disabled after
any error. The completion callback in rx/subs! only fires on success,
not on error.
Add (reset! submitted? false) at the beginning of the on-error handler
so the form becomes submittable again after any error, allowing the user
to fix their input and retry.
---------
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* 🐛 Fix incorrect invitation token handling on register process
- Reject prepare-register-profile when an active profile already
exists for the requested email.
- Stop embedding an existing profile's :profile-id into the
prepared-register JWE. Profile resolution in register-profile is
now done exclusively by email lookup, never by a JWE claim.
- Add created? guard to the invitation-success branch in
register-profile, so existing profiles (active or not) cannot
reach session creation via anonymous registration.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* ♻️ Restructure invitation handling inside register-profile
Move the invitation-success branch into the created? sub-cond so it
sits alongside the other post-creation branches, making the control
flow consistent.
- Active new profile + matching invitation: mint session and return
:invitation-token (frontend redirects to :auth-verify-token).
- Not-yet-active new profile + matching invitation: embed the
invitation token inside the verify-email JWE and send the
verification email. When the user clicks the link, they get
logged in and the frontend completes the team-invitation flow.
- Extend send-email-verification! with an optional invitation-token
parameter propagated into the verify-email JWE claims.
- Update the frontend verify-email handler to navigate to
:auth-verify-token when the response carries :invitation-token.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* 🐛 Handle email-already-exists error on registration form
Add a specific handler for the [:validation :email-already-exists] error
code in the registration form's on-error callback. The backend raises
this error when an active profile already exists for the requested email,
but the frontend was falling through to the generic error message.
Now it shows the existing "Email already used" i18n message instead of
the generic "Something wrong has happened" toast.
* 🐛 Reset submitted state on registration form error
The on-error handler in the registration form was not resetting the
submitted? state, causing the submit button to remain disabled after
any error. The completion callback in rx/subs! only fires on success,
not on error.
Add (reset! submitted? false) at the beginning of the on-error handler
so the form becomes submittable again after any error, allowing the user
to fix their input and retry.
---------
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
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>
The shape API method .component() used locate-component which walks
to the outermost instance root via get-instance-root. For nested
component instances (e.g. a button inside a card), this incorrectly
returned the outer component (the card) instead of the nearest one
(the button).
Added locate-head-component in utils.cljs which uses get-head-shape
to find the nearest component head, and updated the :component
property in shape.cljs to use it.
Fixes#9183
`(.log js/console "load-ref" iframe-dom)` was left in the iframe
ref callback of `frame-preview`. Mirrors the defect PR #9243
removed from `color-row*` — fires on every ref invocation and
pollutes the browser console.
Signed-off-by: jack-stormentswe <crazycoder131@gmail.com>
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Steps to reproduce: paste an SVG authored in Inkscape (or any editor
that follows the inkscape:label convention) into a penpot file. The
group/element names visible in the source editor are dropped — penpot
shows generic auto-ids like 'g1234' or 'path5678' instead.
Root cause: parse-svg-element in common/src/app/common/files/shapes_
builder.cljc derived the shape name from (or (:id attrs) (tag->name
tag)). Inkscape stores user-given element labels in the inkscape:label
and sodipodi:label namespaced attributes while id holds an auto-
generated technical id, so the operator's chosen name was always
overridden by the technical id when present.
tubax/xml->clj (the SVG parser the import pipeline already uses for
upload, paste, and library import) keeps namespaced attributes as
:prefix:name keywords — the same shape this file already reads
:xlink:href from on line 134, and that app.common.svg uses for the
xlink: namespace at lines 300-307.
Fix: extract the name-resolution logic into a public resolve-element-
name helper that prefers :inkscape:label, then :sodipodi:label, then
:id, then (tag->name tag). Existing SVGs that don't carry either label
namespace fall through the same chain as before, so the behaviour for
non-Inkscape-authored SVGs is unchanged.
This restores the behaviour dfelinto's penpot-icon-generator-plugin
relies on (linked from the issue body) — that plugin reads element
names from the imported SVG to map Blender icons to penpot components.
Tests: 6 deftest blocks in common/test/common_tests/files/shapes_
builder_test.cljc covering the priority order (inkscape > sodipodi >
id > tag), each fallback in isolation, and the empty-attrs case.
Registered in common-tests.runner.
Closes#7869
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Copying a component with variants from a shared library file ("Lib")
and pasting it into a file that uses that library ("Using Lib") would
crash the destination file with the referential-integrity validator
error:
{:code :component-main-external
:hint "Main instance should refer to a component in the same file"}
Root cause
----------
Paste goes through `generate-duplicate-shape-change` in
`common/src/app/common/logic/libraries.cljc`. When the shape is a
main instance of a known component and the copy set includes its
variant container, dispatch lands in `duplicate-variant`, then
`generate-duplicate-component`, and finally `duplicate-component`,
which clones the main-instance shape tree. Its `update-new-shape`
helper already re-links the new outer main's `:component-id` to the
freshly created local component (`new-component-id`), but it never
touches `:component-file`. The cloned shape therefore inherits
`:component-file` from the source library while the new component is
registered in the destination's local library
(`:apply-changes-local-library? true`), leaving the main-instance
dangling.
Fix
---
Extend `update-new-shape` with a second clause, sibling to the
existing `:component-id` rewrite: when a destination file id is
provided and differs from the new main's current `:component-file`,
re-root the shape. The same `(= (:component-id new-shape) (:id
component))` guard already used for the id rewrite ensures only the
outer main-instance is touched; nested shapes are unaffected.
The destination file id is threaded from the paste entry point
through the two orchestration functions that already knew the
source/destination distinction:
- `generate-duplicate-shape-change` — supplies the destination
`file-id` it already has in scope when dispatching to
`generate-duplicate-component-change`.
- `generate-duplicate-component-change` — accepts `:new-component-file`
as a kwarg; renames its internal `file-id` binding to
`source-file-id` for clarity (it was always the component's
originating library file); forwards `new-component-file` to
`duplicate-variant`.
- `duplicate-variant` — takes and forwards the `new-component-file`
positional arg.
- `generate-duplicate-component` — accepts `:new-component-file` kwarg
and passes it to `duplicate-component`.
- `duplicate-component` — applies the rewrite inside
`update-new-shape`. The `new-component-file` parameter is placed
right after `new-component-id` since component-id and component-file
are typically managed together.
Same-file duplication is not affected: without `:new-component-file`
the new clause is skipped, and when source and destination match the
`(not= new-component-file (:component-file new-shape))` guard fails.
Tests
-----
Added in `common/test/common_tests/logic/comp_creation_test.cljc`:
- `test-duplicate-component-rewrites-component-file-to-destination`
asserts that passing `:new-component-file` to
`generate-duplicate-component` produces a main-instance with
`:component-file` equal to the destination id.
- `test-duplicate-component-keeps-component-file-without-dest`
baseline: without `:new-component-file`, `:component-file` is left
untouched, matching pre-existing same-file behavior.
Github #8144
Signed-off-by: FairyPigDev <luislee3108@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Step toward issue #9260 (incremental migration of legacy UI
components to the modern `*`-suffixed syntax, removing the per-render
JS-to-Clojure props conversion overhead).
Component
---------
`app.main.ui.releases.common/navigation-bullets` is a small (7-line)
self-contained presentational component used by every release-notes
modal to render the slide-progress dots. It already used standard
keyword destructuring (`[{:keys [slide navigate total]}]`), had no
`?`-suffixed props, no `unchecked-get`, no `obj/merge!`, no
`::mf/wrap-props false`, and (importantly) no `::mf/register`, so it
satisfies the migration pre-flight checks unchanged.
Changes
-------
- `releases/common.cljs` — definition renamed to `navigation-bullets*`.
Body, props and metadata are otherwise unchanged.
- `releases/v1_4.cljs` … `v2_15.cljs` (29 files) — every existing call
site `[:& c/navigation-bullets {…}]` becomes
`[:> c/navigation-bullets* {…}]`. The `:slide`, `:navigate`, `:total`
props are passed exactly as before. The `:as c` alias of the require
is unchanged, so no require edits are needed.
No props were renamed (none ended in `?`); no helpers had to be
swapped for `mf/spread-props` / `mf/props` (callers pass plain literal
maps); no metadata had to be removed (none of the legacy options were
in use).
Github #9260
Signed-off-by: FairyPigDev <luislee3108@gmail.com>
* ✨ Add HEX, HSB, and HSL support in the third color tab
Relabel the existing HSVA tab to HSBA (the math was already HSB) and add
an inline HSB ↔ HSL model toggle inside the tab, matching Figma's color
panel. Sliders, gradients, and labels update dynamically per mode; HSL
values roundtrip through RGB/HSV so no color-storage changes are needed.
Model choice persists across sessions.
* 💄 Fix lint errors
Signed-off-by: juan-flores077 <toptalent399@gmail.com>
* 🐛 Fix Plugin API token application for JS array of strings (#9166)
* 🐛 Fix Plugin API token application for JS array of strings
Plugin code calling `shape.applyToken(token, ["fill"])` or
`token.applyToShapes([rect], ["fill"])` from JavaScript supplies a JS
array of strings. The plugin proxies expected a Clojure set of
keywords, and two coupled defects made the calls silently no-op (or,
with `throwValidationErrors` enabled, throw "check error"):
1. `token-attr-plugin->token-attr` only consulted its alias map when
the input was already a keyword. String inputs like "fill" fell
through to the identity branch, so the downstream
`cto/token-attr?` predicate (which checks against a set of
keywords) returned false for every string. Coerce strings to
keywords first.
2. The `applyToken` / `applyToShapes` / `applyToSelected` schemas
used plain `[:set ...]`, which has no `:decode/json` transformer
for JS array → Clojure set coercion. Switch to the registered
`[::sm/set ...]` (in `app.common.schema`) which provides the
array → set decoder. After the switch, the standard JSON pipeline
converts `["fill"]` to `#{"fill"}`, then the inner
`[:and ::sm/keyword [:fn token-attr?]]` decodes each element to a
keyword and validates it.
Also extends the docstring on `token-attr-plugin->token-attr` to make
the string-friendly contract explicit, and registers a new
`tokens-test` ns under `frontend/test/frontend_tests/plugins/` with
six `deftest` blocks covering:
- known keywords passing through unchanged
- keyword aliases (`:r1` → `:border-radius-top-left`, etc.)
- string inputs coerced to keywords (regression for #9162)
- `token-attr?` accepting both keyword and string inputs
- `token-attr?` rejecting unknown attrs and nil
Closes#9162
* 🐛 Fix wrong direction in plugin-name alias tests
The added tests in tokens_test.cljs and the new docstring in tokens.cljs
described the alias resolution in the wrong direction. The map is
{:r1 :border-radius-top-left, …} then map-invert'd, so
token-attr-plugin->token-attr maps verbose plugin-side names
(:border-radius-top-left) to canonical internal short names (:r1),
not the other way around. Inputs already in canonical form (:r1, :fill,
"fill", …) pass through unchanged. Flipped the alias-resolution test
expectations and the keyword/string-input cases, refreshed the docstring
and the regression-coverage comment to match.
---------
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
* 💄 Fix sucess typo in subscription dialog i18n keys (#9204)
Rename subscription.settings.sucess.dialog.{title,footer} to
subscription.settings.success.dialog.{title,footer} in en.po and
update the three callsites in subscription.cljs.
Closes#9203
Signed-off-by: jack-stormentswe <crazycoder131@gmail.com>
* 🐛 Fix HSVA → HSBA test rename and Prettier formatting
Signed-off-by: juan-flores077 <toptalent399@gmail.com>
* 🐛 Fix CI failures and address review feedback for HSB color tab
Signed-off-by: juan-flores077 <toptalent399@gmail.com>
* 💄 Resolve Conflicts
Signed-off-by: juan-flores077 <toptalent399@gmail.com>
---------
Signed-off-by: juan-flores077 <toptalent399@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>
Co-authored-by: boskodev790 <boskomaljkovic790@outlook.com>
Co-authored-by: Jack Storment <88656337+jack-stormentswe@users.noreply.github.com>