45 Commits

Author SHA1 Message Date
Andrey Antukh
49119e0339
♻️ Rename nitrate config to admin-console (#10929)
* ♻️ Rename nitrate config to admin-console

Rename user-facing configuration from 'nitrate' to 'admin-console':
- Feature flags: :nitrate -> :admin-console, :nitrate-bulk-create-profiles -> :admin-console-bulk-create-profiles
- Config keys: :nitrate-shared-key -> :admin-console-shared-key, :nitrate-backend-uri -> :admin-console-uri
- Shared-keys map entry: :nitrate -> :admin-console (setup.clj + main.clj)
- Env vars: PENPOT_NITRATE_SHARED_KEY -> PENPOT_ADMIN_CONSOLE_SHARED_KEY, PENPOT_NITRATE_BACKEND_URI removed (consolidated into PENPOT_ADMIN_CONSOLE_URI)
- Docker/nginx: PENPOT_NITRATE_URI -> PENPOT_ADMIN_CONSOLE_URI

Code namespaces, file paths, CSS classes, and i18n keys stay as-is.

AI-assisted-by: mimo-v2.5-pro

* ♻️ Rename initialize-user-in-nitrate-organization to initialize-user-in-organization

Part of the nitrate -> admin-console rename series. The function and all 9 references across 6 files have been renamed.

* ♻️ Rename :nitrate-bulk-create-profiles-not-allowed to :bulk-create-profiles-not-allowed

* ♻️ Inline nitrate-permissions into app.common.types.organization

- Delete app.common.types.nitrate-permissions and its test
- Move permission rules (allowed?, can-send-invitations?, etc.) into organization.cljc
- Harmonize all consumers to use alias cto for app.common.types.organization
- Update test runner and create organization_test.cljc
2026-08-03 17:23:55 +02:00
Andrey Antukh
e741313add Merge remote-tracking branch 'origin/staging' into develop 2026-07-31 19:21:07 +02:00
Andrey Antukh
d04cbf175e
🐛 Fix nil dereference crash during flex layout drag operations (#10845)
Production crash where @(get bounds id) threw
"No protocol method IDeref.-deref defined for type null"
when a shape ID had no corresponding entry in the bounds map
during layout calculations.

Added defensive nil guards (when-let / when) to all unprotected
bounds dereference sites:

- flex_layout/bounds.cljc: layout-content-points (parent + child)
  and layout-content-bounds
- grid_layout/bounds.cljc: layout-content-points and
  layout-content-bounds
- min_size_layout.cljc: child-min-width grid branch (3 sites) and
  child-min-height grid branch

Added 7 new tests in geom_bounds_layout_nil_test.cljc covering all
nil-bounds edge cases for flex, grid, and min-size layout paths.
Registered in runner.cljc.

Closes #10843

AI-assisted-by: qwen3.7-plus
2026-07-31 12:51:35 +02:00
Alonso Torres
014ec34249
🐛 Fix system crash on grid element deletion of a component (#10956)
* 🐛 Add regression test: main-side component edits break copy swap slots

Reproduces the :missing-slot referential-integrity failure ("Shape has been
swapped, should have swap slot") that crashes files with component copies.

Root cause: reordering or deleting a nested sub-head IN THE MAIN of a component,
while copies exist, does not propagate swap slots to the copies. find-near-match
matches a copy's sub-heads to the main's children by POSITION, so once the main's
order changes the copies' shape-refs no longer match their position and, lacking a
swap slot, fail referential-integrity validation.

- Copy-side edits are handled correctly (characterization tests, pass today).
- The two main-side tests fail today with :missing-slot and go green once the
  sync assigns swap slots to copies on a main reorder/delete.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* 🐛 Fix integrity crashes from copy/main child-order divergence

Copy sub-heads were matched to their main's children purely by position
(find-near-match), while several code paths reorder or mutilate one side
only. Any of them made file validation fail with :missing-slot ("Shape
has been swapped, should have swap slot"), crashing the workspace on the
next validated commit, or persisting a corrupt file whose later edits
crash. Reproduced live: deleting a sub-head inside a copy (which only
hides it) and then reflowing the copy's grid moved the hidden (cell-less)
child to the front of :shapes, knocking every sibling out of its
positional slot.

Four fixes, one per divergence path:

- validate/check-required-swap-slot: a sub-head whose shape-ref is still
  a child of the near main parent is a REORDER (the component sync
  realigns it), not a swap; a swap slot is required only when the ref
  points outside the near main parent (a real swap). This matches how
  the sync engine itself pairs children (by shape-ref, not by position).
  comp-processors/fix-missing-swap-slots (migration 0019) is aligned:
  adding slots to merely-reordered sub-heads would freeze them out of
  normal synchronization.

- changes/:reorder-children now refuses to alter the child structure of
  component copies unless allow-altering-copies is set, mirroring the
  is-valid-move? rule of :mov-objects; that structure is owned by the
  component sync engine. Grid reflows emitted this change type with no
  guard. pcb/reorder-grid-children also skips copy grids producer-side.

- layout/reorder-grid-children keeps children that participate in no
  cell (hidden or absolute positioned) at their original index instead
  of lumping them at the front: moving them gratuitously changed their
  z-order and, in copies, broke the positional matching. Note :shapes
  stays reversed relative to the sorted cell order for in-cell children.

- logic/generate-delete-shapes: deleting shapes from inside a main
  (without deleting the main root, whose copies keep working against the
  deleted component) now also deletes the copy shapes that reference
  them, transitively (copies of copies) and across all pages of the
  file, so no dangling shape-refs remain. Skipped for
  allow-altering-copies flows (component swap replaces the shape and the
  sync reconciles copies via swap slots). Cross-page removals build
  redo/undo changes against that page's objects directly, since the
  changes-builder mounts only the current page; their undo mov-objects
  carry allow-altering-copies so restoring inside a copy is not rejected
  by the new guard.

The regression tests assert the fixed semantics: main-side reorders and
deletes keep copies valid, the previously crashing full chain (unvalidated
main reorder + later copy edit) stays healthy, :reorder-children cannot
scramble copies, and reorder-grid-children keeps cell-less children in
place. The namespace is now also registered in the JS test runner.

AI-assisted-by: Claude Opus 4.8 (1M context)

*  Extend composable slot cases to the grid-reflow crash sweep

Case D (CopySubheadDeletePreservesSlots) now sweeps which copy sub-head
is deleted (first or last) and whether the copy root is resized
afterwards, forcing a grid reflow: the reflow used to move the hidden
(cell-less) child to the front of the copy's children, shifting every
sibling out of its positional slot. Deleting the FIRST sub-head masked
the bug (moving it to the front is a no-op), which is why the case
passed before this sweep. The foundation layout is grid accordingly.

Case E (MainReorderKeepsCopySlots) no longer hangs the app now that a
main-side reorder leaves a valid file, so its warning docstring is
replaced: it runs as a routine test (verified headless, passing) and is
safe in a "run all".

SlotIntegrity's doc is updated to the new validator semantics (a slot is
required only for real swaps, not reorders); the positional alignment it
asserts remains the correct, stronger steady-state invariant for these
cases. The lockfile change materializes the playwright devDependency
already declared in package.json.

AI-assisted-by: Claude Opus 4.8 (1M context)

* 📚 Record copy/main order-divergence invariants in memories

Swap-slot semantics (membership, not positional; slots only for real
swaps), the copy-structure guards on :mov-objects/:reorder-children, the
grid reorder stability for cell-less children, and the main-side delete
propagation in generate-delete-shapes.

AI-assisted-by: Claude Opus 4.8 (1M context)

* 🐛 Add adjustements to the code

---------

Co-authored-by: Michael Panchenko <michael.panchenko@oraios-ai.de>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-30 13:25:03 +02:00
Andrey Antukh
972353eccb
🐛 Fix layout padding persisted as string after invalid input (#10758)
Combined fixes from PR #10656 (numeric-input redesign) and PR #10696
(global finite? guard) for issue #10638.

- Add (number? v) guard to cljs mth/finite? so strings are rejected
- Redesign numeric-input last-value* to store number, not formatted string
- Invalid-input fallback restores display without emitting on-change
- Esc now fully discards typed text (resets raw-value* + dirty flag)
- Token dedup by name instead of resolved value
- Defense-in-depth: d/parse-double at 4 padding/gap handlers
- Math tests (common), unit tests, Storybook play tests, Playwright E2E

AI-assisted-by: deepseek-v4-flash

Co-authored-by: Akshit Nassa <nassaakshit@gmail.com>
Co-authored-by: Ulises Millán <ulises.millanguerrero@gmail.com>
2026-07-30 12:51:44 +02:00
Andrey Antukh
7eb188b077
🐛 Fix text content repair and remove blast-radius containment (#10731)
Repair text shapes with empty/broken content at all three levels
(root, paragraph-set, paragraph) in migration 0025 to prevent
workspace update failures. Handle all corner cases: nil/empty/non-
vector children, non-map items, wrong types. Remove geometry-only
validation skip in changes.cljc so all shapes are validated.

AI-assisted-by: qwen3.7-plus
2026-07-28 11:00:53 +02:00
Andrey Antukh
4687b52bf3 📎 Fix fmt issues introduced in previous commits 2026-06-24 11:30:32 +02:00
Andrey Antukh
bfa65547f8 Add more testing related improvements 2026-06-24 11:17:47 +02:00
Andrey Antukh
4a8fb5af53 Merge remote-tracking branch 'origin/staging' into develop 2026-06-01 13:15:57 +02:00
Andrey Antukh
c5de4c27b0 Merge remote-tracking branch 'origin/main' into staging 2026-06-01 12:57:39 +02:00
Yamila Moreno
ddba2ffa75
📎 Update Kaleidos Copyright (#9929) 2026-05-29 11:24:58 +02:00
Andrey Antukh
3858993a57 Merge remote-tracking branch 'origin/staging' into develop 2026-05-27 13:37:02 +02:00
Andrey Antukh
40ce360c99
Improve performance and fix orphan detection in validate-file (#9789)
*  Improve performance and fix orphan detection in validate-file

- Add `*ref-shape-cache*` dynamic var to memoize `find-ref-shape`
  lookups per page, avoiding repeated O(depth) ancestor walks.
- Add `*children-sets*` pre-computed maps for O(1) parent-child
  containment checks, replacing linear `some` scans.
- Short-circuit `inside-component-main?` when the shape context
  already implies a main component.
- Use single-pass reduce with early exit for duplicate detection
  (children, swap slots) instead of count/distinct or frequencies.
- Guard `check-missing-slot` to skip expensive `find-near-match`
  when the shape already has a swap slot.
- Refactor variant-set validation to use `run!` with direct `get`.
- Refactor `check-ref-cycles` to use a single `reduce-kv` pass.
- Fix `get-orphan-shapes`: the original `map` pipeline produced
  nils so orphan shapes were never validated; rewrite with
  `reduce-kv` for correct results.
- Add `validate-file-affected!` for change-scoped validation,
  replacing full file validation in `process-changes-and-validate`
  to only validate pages and components touched by the changes.

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

*  Improved validation

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2026-05-27 12:36:21 +02:00
Alonso Torres
a7b17f54f1
🐛 Fix problem of path position on variant change (#9801) 2026-05-21 16:39:26 +02:00
Michael Panchenko
cec90416c2 Improve common JS test runner
Add focused common JavaScript test execution with log-level control and a quiet test wrapper matching the frontend workflow.

Update developer docs and testing memories to reflect the common/frontend test split, and document why runner helper extraction is deferred.

Signed-off-by: Codex <codex@openai.com>
2026-05-21 14:20:10 +02:00
María Valderrama
7c5fa038c1
Add Nitrate advanced permissions delete (#9416)
*  Add Nitrate advanced permissions delete

* 📎 Code review
2026-05-07 21:14:30 +02:00
Jeff
d09985edee
🐛 Preserve Inkscape labels when pasting SVGs (#9252)
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>
2026-04-30 16:17:34 +02:00
Andrey Antukh
7135782e7d Merge remote-tracking branch 'origin/main-staging' into staging 2026-04-24 08:19:47 +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
Yamila Moreno
3c542a1abc
🐛 Fix email validation (#9037) 2026-04-22 15:59:28 +02:00
Andrey Antukh
b5922d32ca Merge remote-tracking branch 'origin/main' into staging 2026-04-16 10:59:36 +02:00
Andrey Antukh
146219a439 Add tests for app.common.geom namespaces 2026-04-15 23:37:53 +02:00
Andrey Antukh
fa89790fd6 🐛 Fix grid layout case dispatch, divide-by-zero, and add set-auto-multi-span tests
Three critical fixes for app.common.geom.shapes.grid-layout.layout-data:

1. case dispatch on runtime booleans in get-cell-data (case→cond fix)
   In get-cell-data, column-gap and row-gap were computed with (case ...)
   using boolean locals auto-width? and auto-height? as dispatch values.
   In Clojure/ClojureScript, case compares against compile-time constants,
   so those branches never matched at runtime. Replaced both case forms
   with cond, using explicit equality tests for keyword branches.

2. divide-by-zero guards in fr/auto/span calc (JVM ArithmeticException fix)
   Guard against JVM ArithmeticException when all grid tracks are fixed
   (no flex or auto tracks):
   - (get allocated %1) → (get allocated %1 0) in set-auto-multi-span
   - (get allocate-fr-tracks %1) → (get allocate-fr-tracks %1 0) in set-flex-multi-span
   - (/ fr-column/row-space column/row-frs) guarded with (zero?) check
   - (/ auto-column/row-space column/row-autos) guarded with (zero?) check
   In JS, integer division by zero produces Infinity (caught by mth/finite),
   but on the JVM it throws before mth/finite can intercept.

3. Exhaustive tests for set-auto-multi-span behavior
   Cover all code paths and edge cases:
   - span=1 cells filtered out (unchanged track-list)
   - empty shape-cells no-op
   - even split across multiple auto tracks
   - gap deduction per extra span step
   - fixed track reducing budget; only auto tracks grow
   - smaller children not shrinking existing track sizes (max semantics)
   - flex tracks causing cell exclusion (handled by set-flex-multi-span)
   - non-spanned tracks preserved via (get allocated %1 0) default
   - :row type symmetry with :column type
   - row-gap correctly deducted in :row mode
   - documents that (sort-by span -) yields ascending order (smaller spans
     first), correcting the misleading code comment

All tests pass on both JS (Node.js) and JVM environments.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-15 23:37:53 +02:00
Andrey Antukh
6d1d044588 ♻️ Move app.common.types.color tests to their own namespace
Tests that exercise app.common.types.color were living inside
common-tests.colors-test alongside the app.common.colors tests. Move
them to common-tests.types.color-test so the test namespace mirrors
the source namespace structure, consistent with the rest of the
types/ test suite.

The [app.common.types.color :as colors] require is removed from
colors_test.cljc; the new file is registered in runner.cljc.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-14 21:25:09 +02:00
Andrey Antukh
a81cded0aa
Make the common fressian module more testable (#8859)
*  Add exhaustive unit tests for app.common.fressian encode/decode

Add a JVM-only test suite (41 tests, 172 assertions) for the fressian
serialisation layer, covering:

- All custom handlers: char, clj/keyword, clj/symbol, clj/vector,
  clj/set, clj/map, clj/seq, clj/ratio, clj/bigint, java/instant,
  OffsetDateTime, linked/map (order preserved), linked/set (order preserved)
- Built-in types: nil, boolean, int, long, double (NaN, ±Inf, boundaries),
  String, byte[], UUID
- Edge cases: empty collections, nil values, ArrayMap/HashMap size boundary,
  mixed key types
- Penpot-domain structures: shape maps with UUID keys, nested objects maps
- Correctness: encode→decode→encode idempotency, independent encode calls

* ♻️ Extract fressian handler helpers to private top-level functions

Extract adapt-write-handler, adapt-read-handler, and merge-handlers
out of the letfn in add-handlers! into reusable private functions.
Also creates xf:adapt-write-handler and xf:adapt-read-handler
transducers and adds overwrite-read-handlers and overwrite-write-handlers
for advanced handler override use cases.
2026-04-14 10:48:58 +02:00
Andrey Antukh
e46b34efc7 📎 Fix formatting issues 2026-04-13 15:41:38 +02:00
raguirref
f656266e5c Fix builder bool and media handling
Fixes three concrete builder issues in common/files/builder:\n- Use bool type from shape when selecting style source for difference bools\n- Persist :strokes correctly (fix typo :stroks)\n- Validate add-file-media params after assigning default id\n\nAlso adds regression tests in common-tests.files-builder-test and registers them in runner.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: raguirref <ricardoaguirredelafuente@gmail.com>
2026-04-13 15:40:40 +02:00
Andrey Antukh
2ca7acfca6
Add tests for app.common.geom and descendant namespaces (#8768)
* 🎉 Add tests for app.common.geom.bounds-map

* 🎉 Add tests for app.common.geom and descendant namespaces

* 📎 Fix linting issues

---------

Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
2026-04-02 09:50:34 +02:00
Andrey Antukh
a149f31d56
Add comprehensive tests for shape layout namespace (#8759)
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-30 11:03:53 +02:00
Andrey Antukh
4174d6a05b
🎉 Add tests for undo-stack helper function on common (#8766) 2026-03-26 19:44:49 +01:00
Andrey Antukh
737e04fe2c
🐛 Fix nil deref on missing bounds in layout modifier propagation (#8735)
* 🐛 Fix nil deref on missing bounds in layout modifier propagation

When a parent shape has a child ID in its shapes vector that does
not exist in the objects map, the layout modifier code crashes
because it derefs nil from the bounds map.

The root cause is that children from the parent shapes list are
not validated against the objects map before being passed to the
layout modifier pipeline. Children with missing IDs pass through
unchecked and reach apply-modifiers where bounds lookup fails.

Fix by adding nil guards in apply-modifiers to skip children
without bounds, and changing map to keep to filter them out.

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

* 📎 Add tests for nil bounds in layout modifier propagation

Tests cover flex and grid layout scenarios where a parent
frame has child IDs in its shapes vector that do not exist
in the objects map, verifying that set-objects-modifiers
handles these gracefully without crashing.

Tests:
- Flex layout with normal children (baseline)
- Flex layout with non-existent child in shapes
- Flex layout with only non-existent children
- Grid layout with non-existent child in shapes
- Flex layout resize propagation with ghost children
- Nested flex layout with non-existent child in outer frame

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

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-25 15:36:21 +01:00
Andrey Antukh
11a1ac2a09 🐛 Fix RangeError (stack overflow) in find-component-main
Refactor find-component-main to use an iterative loop/recur pattern instead of direct recursion and added cycle detection for malformed data structures.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-11 16:15:47 +01:00
Andrey Antukh
132f7d6d3e
♻️ Add minor refactor on tokens main form (#7690) 2025-11-05 10:37:38 +01:00
Alonso Torres
deee7f7334
Merge pull request #7366 from penpot/niwinz-develop-page-data-type
 Add several enhancements for reduce workspace file load time
2025-09-29 12:43:34 +02:00
Andrés Moya
194eded930 🔧 Unify path name helper functions 2025-09-29 12:16:42 +02:00
Andrey Antukh
7fefe6dbc8 🎉 Add multiplatform impl of ObjectsMap
The new type get influentiated by the ObjectsMap impl on backend
code but with simplier implementation that no longer restricts keys
to UUID type but preserves the same performance characteristics.

This type encodes and decodes correctly both in fressian (backend)
and transit (backend and frontend).

This is an initial implementation and several memory usage
optimizations are still missing.
2025-09-29 12:06:56 +02:00
Andrey Antukh
f86ce38f04 🎉 Add Fills binary data type 2025-06-04 13:03:51 +02:00
Andrey Antukh
b97a3f9783 Add tests for common buffers namespace to the cljs test runner 2025-06-04 12:46:49 +02:00
Andrey Antukh
1fea1e8f5b 🔥 Remove unused svg parsing code from common 2025-05-20 13:06:07 +02:00
Andrey Antukh
1fc0203c38 🎉 Add full integration with path data type feature 2025-05-06 13:39:10 +02:00
Andrey Antukh
b9907ec401 🎉 Add PathData data type
That replaces the ad-hoc helpers for convert path content from
plain with encapsulated type that implements the appropriate
clojure protocols and interfaces that helps interacting with
binary encoded path data
2025-03-26 13:17:25 +01:00
Florian Schroedl
19daffd1c0 Implement drag & drop for set groups
🚧 PR feedback

♻️ Remove unused functions

♻️ Throw on non-allowed changes

🚧 key fixes

🚧 Fix tests

🚧 FMT

🚧 Add set group test

🚧 Remove 'drop' in name

🚧 Add group tests

🚧 FMT

🚧 Always return changes
2025-01-24 09:50:50 +01:00
Andrey Antukh
42acdbd135 ♻️ Reorganize common types tests 2025-01-16 17:55:28 +01:00
Andrey Antukh
3addc4ae42 🐛 Fix tests 2024-11-18 18:06:42 +01:00
Andrey Antukh
27690c3da6 Add test runner for cljs on common module
In the same way as frontend tests are run and fix some
tokens related tests
2024-11-18 17:51:23 +01:00