222 Commits

Author SHA1 Message Date
Álvaro Tejero-Cantero
dcedd40a42
✨ Tell merged and archived branches apart (#12572)
* 🐛 Refuse saves to a branch kept after its merge

A branch kept after its merge stays an ordinary file with status
"merged", so every save passed the edition check and appended ops to
an op log that no command integrates any more. Refuse such saves with
a :branch-merged validation error instead of stranding them. An
archived branch still takes saves: restoring it brings them back.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>

* ✨ Tell merged and archived branches apart

The workspace treated "merged" and "archived" as one state: an
archived branch read "Merged branch" and lost every action, and a
merged row offered Restore, which the backend always refused.

Each status now has its own presentation. A kept merged branch keeps
"Merged branch" and View in main, and opens read-only: the workspace
permissions drop :can-edit while it is the open file, the way a
viewer gets a read-only workspace. An archived branch says so in the
banner and offers Compare and Restore. The header notes each status,
the switcher lists merged and archived branches in two collapsible
groups, the all-branches dialog gets a Merged tab, and the branch info
dialog offers Restore and Compare for an archived branch.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>

* ✨ Refuse saves to an archived branch

An archived branch is now read-only, like a merged one: the user
restores it, or turns it into an ordinary file, to keep editing.
persist-branch-file! refuses its saves with a :branch-archived
validation error, distinct from :branch-merged so the client can say
which way out applies. A restored branch takes saves again, and so
does an archived branch turned into an ordinary file.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>

* ✨ Open archived branches read-only with their ways out

An archived branch now opens read-only, like a merged one. Its banner,
its branch info dialog and its row in the all-branches dialog offer
the two ways back to editing, Restore and Turn into an ordinary file,
next to Compare. The header and switcher note reads "Archived, read
only".

A save the server refuses because the branch is merged or archived
now shows a warning that names the way out, instead of the generic
autosave error with a report link.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>

---------

Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-10-22 20:54:15 +02:00
Álvaro Tejero-Cantero
d669eec0c0
✨ Load the branch list only when the switcher opens (#12571)
* ✨ Add a branch count query for the workspace header

The header shows its branch row only when the file has branches, and
it learnt that from get-file-branches, which compares every diverged
branch against main. get-file-branch-count answers the same question
with one indexed count: archived and merged branches included, counted
from main when the file is itself a branch, with no comparison and no
climit, like the branches_count subquery of the dashboard listings.

The listing keeps its climit, and :get-file-branches/global now sets
the 60 s timeout that the header of climit.edn promises, so a waiter
behind six cold listings gets an error instead of waiting without
bound. Its rationale comment names the callers it serves now.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>

* ✨ List branches only when the switcher opens

Opening a file ran the counted branch listing for every user, because
the header decided whether to show its branch row from that listing.
A cold listing runs a three-way comparison per diverged branch, and
one save to main makes every branch of the file a cache miss again.

The header now loads the branch count and the branch context with the
file. The listing loads when the branch switcher or the all-branches
dialog opens, and both show "Loading branches…" until it arrives
instead of a list from an earlier opening.

A failed listing used to read as an empty list, which on main hid the
branch row and sent the shortcut to the create dialog. It now ends in
an error state: the switcher and the dialog say "Couldn't load
branches" with a Retry action, and the row stays, since its visibility
comes from the count. A successful listing refreshes the count, so
deleting the last branch from the list still hides the row.

Creating a pull request from the branch banner looked the branch up
in the listing to decide whether to save pending edits first. Without
the listing loaded at open it would skip the save, so it now reads the
branch context of the open file.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>

* ✨ Show the branch row when the branch count fails

A failed get-file-branch-count left no count, so on main the header
hid its branch row and the shortcut opened the create dialog on a file
that may have branches. The failure is now kept as an error state
under :workspace-branch-count, and has-branches? reads it as true.

The row then shows the Main chip and, where the status note goes,
"Couldn't load branches" in the warning colour. Opening the switcher
loads the listing through the existing loading and Retry path, and a
successful listing settles the count, which replaces the note.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>

* 📚 Name the callers of the branch listing limit again

The merge of iop-vc-preview took its climit.edn, whose comment on
:get-file-branches/global still described a panel open. Restore the
comment this branch wrote: the listing runs when the switcher, the
all-branches dialog or the dashboard popover opens, and a file open
only counts branches.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>

* 🐛 Mock the branch count in the library copy tests

The header now shows its branch row from get-file-branch-count, not
from the listing, so the library copy tests mock the count as well.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>

---------

Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-10-22 20:50:58 +02:00
Álvaro Tejero-Cantero
d4f999e6dd
✨ Tell a library branch that its consumers read main (#12570)
Files that use a shared library read its main, never a branch, so a
branch of a library is not isolated in the sense the create dialog
promised. On a shared library the create dialog now says that files
using the library can adopt changes from main only, the chip of a
library branch says the same, and the note beside main reads
"Default branch (read by library consumers)". Other files keep their
copy.

get-file-branch-info returns source-is-shared so a branch knows its
main is a library.

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

Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-10-22 20:04:23 +02:00
Juan de la Cruz
720b2f193d
✨ Move branching into the workspace header (#12359)
* ✨ Move the branch switcher to the workspace header

* ✨ Add branch DS components and the all-branches dialog

* 💄 Polish the branch switcher and its menu entry

* ✨ Keep the dashboard to switching branches

* 💄 Polish the branch and pull request review UI

* ♻️ Move the branching UI onto the design system

* 🐛 Address the review of the header branch switcher

The bottom banner turned the nil counts that get-file-branch-info sends
for a branch it cannot compare into zeros, so it said "Up to date with
main". It now says "Couldn't compare this branch with main" in the
warning colour, as the header row already did.

During a review the review banner replaces the branch banner, which was
the only component loading the branch context. The header row now loads
it too, so a review link opened fresh shows the branch under review
instead of "Main".

On a branch, "New branch" in the all-branches dialog is disabled with a
tooltip saying branches are created from main: the server refuses to
branch a branch.

With "Try interactions" gone, the create-review note says reviewers
open the live branch and View mode plays it, and the unused
open-pull-request-viewer event goes.

The pull request specs follow the new banners, and three tests cover
the fixes above.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>

---------

Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
Co-authored-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-10-22 16:50:19 +02:00
Álvaro Tejero Cantero
2a5a235063
✨ Add the design preview of file branching and pull requests
This commit carries the design-branching preview by Juan de la Cruz:
elhombretecla/penpot:branching at 0ac265a3f5, which holds 45 commits
and 3 develop merges and was last synced with develop on 2026-09-16.
The branch is squashed together with a merge of origin/develop at
7c039231f7, so the tree is the merged result.

The merge resolved conflicts in six files:

- backend/src/app/migrations.clj: develop's 0155 and 0156 steps run
  first, then the branching steps; the names differ.
- backend/src/app/rpc/commands/viewer.clj: both requires stay.
- common/src/app/common/flags.cljc: both flags stay.
- frontend/src/app/main/ui/dashboard/file_menu.cljs: "Create branch"
  becomes a menu-item* with the same id, guard and handler.
- frontend/src/app/main/ui/dashboard/grid.cljs: the portal goes with
  develop's menu rewrite; the branches popover stays in the trigger.
- frontend/translations/en.po: both sides' entries stay, with
  develop's source references.

Co-authored-by: Juan de la Cruz <delacruzgarciajuan@gmail.com>
AI-assisted-by: mixed models
2026-10-06 10:36:54 +02:00
Andrés Moya
3cd9bfa9de
✨ Add sync with design tokens in external libraries (#10293)
* ✨ Auto link tokens when adding external libraries (provisional)

* 🔧 Refactor tokens-lib initialization

* 🔧 Add separated TokenStatus to store status apart of TokensLib

* 🔧 Make all status operations use the new data structure

* 🔧 Normalize status helper functions and access token sets by id

* 🔧 Rename :tokens-file to :tokens-source

* 🎉 Allow the user to choose the tokens-source of a file

* 🎉 Make tokens library readonly when it's in an external file

* 🎉 Show tokens in library summaries

* 🎉 Show source info in sidebar

* 🔧 Fix integration tests

* 🐛 Propagate changes of token values in external library

* 🎉 Layout updates

* 🔧 Refactor tokens source calculations

* 🔧 Add harder checks for nil or empty values in everything

* 🐛 Fix some integration tests

* 🔧 Add integration tests for tokens in external libs

* 🔧 Validate and repair missing tokens status

* 🎉 Make ui changes optional with config flag

* 🐛 Propagate tokens after synchronizing components in ext library

* 🐛 Propagate tokens after creating new instances

* 🐛 Propagate tokens after synchronizing tokens in ext library

* 🐛 Add a tokens source icon to libraries section (#11439)

* 🐛 Add a tokens source icon to libraries section

* 🐛 Fix ellipsis on library names

* ♻️ Remove code under flag on legacy component

* 🐛 Fix token theme name on inspect tab

* 🎉 Add changes notification (#11476)

* 🎉 Add changes notification

* ♻️ Change fn names

* 🐛 Fix tokens source label truncation and missing translations (#11533)

* 🐛 Fix tokens source label truncation and missing translations

The tokens source file name always showed, even for the current file,
and long names wrapped onto a second line instead of truncating
because the header used flex-wrap and overflow-wrap: break-word
instead of single-line ellipsis.

Show the source row unconditionally (it now displays "This file" when
the source is the current file, matching the connected-library case),
truncate the file name to one line with an ellipsis, and only attach a
tooltip with the full name when the text is actually truncated.

Replace the hardcoded UI strings with translated ones and add their
English and Spanish entries.

AI-assisted-by: claude-sonnet-5

* 🐛 Remove redundant effect dependency in tokens source

file-name-truncated? was listed as a dependency of the with-effect
that checks and observes label truncation, even though it isn't
read inside the effect body. Since the effect itself flips that
state via check-file-name-truncated, including it as a dependency
caused the ResizeObserver to be needlessly disconnected and
reconnected on every truncation change.

AI-assisted-by: claude-sonnet-5

* 🐛 Fix small visual error

* 🐛 Fix problem with plugins

* 🐛 Fix playwright tests

---------

Co-authored-by: Eva Marco <evamarcod@gmail.com>
Co-authored-by: Eva Marco <eva.marco@kaleidos.net>
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2026-09-21 15:37:45 +02:00
elhombretecla
a91d81c695 🎉 Add link preview metadata for shared links 2026-09-15 17:10:54 +02:00
Andrey Antukh
ba608f8c77 Merge remote-tracking branch 'origin/staging' into develop 2026-09-14 14:50:32 +02:00
Shreyash
6a46f8beb5
🐛 Fix email already used showing a toast instead of an input error (#11647)
* 🐛 Fix email already used showing a toast instead of an input error

Registering with an email that already exists reported the problem with a
toast while the email input stayed in its valid state, which reads as if the
form was accepted. The message now lands on the email input itself, the same
way the recovery and change password forms report server side errors.

The error is written to :extra-errors because the form mutator recomputes
:errors from the schema on every change, so a value written there is dropped
on the next render.

Fixes #10890

* 📎 Update CHANGES.md to remove email registration fix

Removed a note about a fix for email registration error display.

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

---------
Co-authored-by: Shreyash Agare <agareshreyash26@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-09-14 10:13:54 +02:00
Eva Marco
c0221a9bf8
🐛 Hide "Create typography style" button for shapes with missing fonts (#11527)
The button let users convert a text shape's inline styles into a
typography asset even when the shape's font-id couldn't be resolved
(e.g. a custom/team font that was removed or isn't loaded), silently
baking a missing font into the new typography asset.

Guard the button on the font actually resolving via
app.main.fonts/fontsdb, in addition to the existing checks (no
typography or token already applied, single selection).

Added e2e coverage for all four conditions that must independently
hide the button: missing font, applied typography asset, multiple
selection with differing values, and applied typography token.

AI-assisted-by: claude-sonnet-5
2026-09-11 13:41:11 +02:00
Elena Torró
0913545b41
🐛 Fix letter spacing and position when flattening text to path (#11555)
* 🐛 Fix letter spacing and position when flattening text to path

* ♻️ Simplify text to path conversion and add flatten tests
2026-09-11 08:20:22 +02:00
Elena Torró
9b24907992
🐛 Fix render text-decoration on spans and update regression tests (#11583) 2026-09-10 09:42:09 +02:00
Elena Torró
af5b767933
🐛 Fix microinteractions on text shape selrects for autowidth/autoheight (#11068) (#11545)
Co-authored-by: Belén Albeza <belen.albeza@kaleidos.net>
2026-09-08 10:29:15 +02:00
Elena Torró
7b135b80b2
🐛 Fix background blur clipping (#11532) 2026-09-07 15:51:27 +02:00
Eva Marco
53985dc630
🐛 Fix setting dark theme on onboarding (#11212)
* 🐛 Fix setting dark theme on onboarding

* 🎉 Add test
2026-08-11 13:49:48 +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
Eva Marco
5de06e8f77
🎉 Add customizable shortcuts (#10237)
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2026-07-24 10:36:32 +02:00
Elena Torró
4383cf183a
✨ Add background blur to strokes (#10716) 2026-07-22 17:07:16 +02:00
Elena Torro
001dba1bef ✨ Add background blur to text shapes 2026-07-16 08:33:36 +02:00
Alejandro Alonso
b82ab0c830 Merge remote-tracking branch 'origin/staging' into develop 2026-07-02 13:52:25 +02:00
Belén Albeza
6d458c80a1
🐛 Fix font and variant dropdowns on mixed text styles (#10520)
* 🐛 Fix dropdown shown Mixed Font Families for same family with different variant

* 🐛 Fix variants dropdown appearing blank on mixed variants but same family

* ✨ Add playwright test for mixed font families/variants
2026-07-01 20:15:20 +02:00
Eva Marco
920c6d4e76
🐛 Fix token tooltips on numeric-input, color and typography (#10480) 2026-06-30 16:26:49 +02:00
Luis de Dios
28afe223f4
🐛 Invitations to a team can be sent to existing members without displaying any error (#10489)
* 🐛 Fix team invitations can be sent to existing members without displaying any error

* ✨ Add tests
2026-06-30 15:39:59 +02:00
Alejandro Alonso
99638fa60c
🐛 Fix wasm render playwright tests (#10462)
* 🐛 Fix playwright wasm test for updating canvas background

* 🐛 Fix playwright wasm test for rendering blurs

* 🐛 Fix invisible emoji texts in render-wasm playwright data
2026-06-29 17:03:45 +02:00
Eva Marco
2a098e5b16
🎉 Add background blur (#10034)
* 🎉 Add background blur

* 🎉 Add test

* 🎉 Add background blur info to plugins API

* 🎉 Suport in wasm for both layer and background blur

* 🐛 Fix failing test

* ♻️ Fix comments

---------

Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2026-06-16 19:46:03 +02:00
alonso.torres
70e8dbb38a 🐛 Fix cropped outer stroke of rotated board in view mode 2026-06-09 09:40:26 +02:00
alonso.torres
6a0f24e691 🐛 Fix view mode child click blocked by parent mouse-leave interaction overlay 2026-06-03 16:10:13 +02:00
Andrey Antukh
feca7cef41 Merge remote-tracking branch 'origin/staging' into develop 2026-06-02 17:50:45 +02:00
Belén Albeza
7fdd2ceb5c
🐛 Fix crash when dismissing the restore version modal (#9969) 2026-06-02 11:33:06 +02:00
Andrey Antukh
4a8fb5af53 Merge remote-tracking branch 'origin/staging' into develop 2026-06-01 13:15:57 +02:00
Alejandro Alonso
88f2366c6f
🎉 Enable render switch and wasm info by default and simplify feature helpers to use pre-computed features set (#9942)
The setup-wasm-features function is the single source of truth for
    resolving the renderer choice (URL param > profile preference > team
    flags), storing the result in state[:features]. Several helpers were
    re-deriving the same priority chain independently, duplicating logic:

    - wasm-enabled?, wasm-url-override, wasm-url-override-ref
    - enabled-by-flags?, enabled-without-migration?

    This change removes all duplicated helpers and simplifies the
    remaining functions to rely exclusively on the pre-computed
    :features set:

    - active-feature? — now just checks (contains? (:features state)
      feature) without special-casing render-wasm/v1
    - use-feature — uses the reactive features-ref for all features
    - initialize/recompute-features effects — use the local features
      binding directly

    Since :features is rebuilt by setup-wasm-features on every
    initialization and recompute, this preserves correctness while
    eliminating ~50 lines of duplicated code.
2026-06-01 12:52:34 +02:00
Luis de Dios
0b56fd2f77
🐛 Fix locked flex and grid elements cannot be selected in viewer role (#9865)
* 🐛 Fix locked flex and grid elements cannot be selected in viewer role

* ✨ Add playwright test
2026-06-01 08:30:27 +02:00
Andrés Moya
429103d076 🐛 Fix errors when token name conflicts with group name 2026-05-29 12:28:10 +02:00
Belén Albeza
0dd40776f8
🐛 Fix default path stroke thickness 2026-05-27 14:47:11 +02:00
Eva Marco
9439d63682
🐛 Fix rename on non empty page (#9850) 2026-05-26 08:58:09 +02:00
Belén Albeza
3fd114550f
🐛 Fix library update tab UX (#9812)
* 🐛 Fix update library modal UI not working properly when updating a lib

* ✨ Add playwright test for bug 14214
2026-05-21 19:50:12 +02:00
Andrés Moya
25ee8dee78
🐛 Fix editing a text element detaches applied tokens (#9525) 2026-05-18 12:28:48 +02:00
Alejandro Alonso
164f0cba7a Merge remote-tracking branch 'origin/staging' into develop 2026-05-04 11:56:07 +02:00
alonso.torres
f6bd991968 🐛 Improved e2e tests stability 2026-05-04 09:04:30 +02:00
Alejandro Alonso
984d292ab2 Merge remote-tracking branch 'origin/staging' into develop 2026-04-24 09:29:24 +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
Elena Torro
96722fde4b 🐛 Support EvenOdd SVG attribute across all path operations 2026-04-23 12:02:40 +02:00
Eva Marco
a3f7a1def6
🐛 Fix bugs with multiselection (#8932)
* 🐛 Fix app crash when selecting shapes with one hidden

* 🐛 Fix opacity input when mixed values
2026-04-13 18:29:15 +02:00
Alejandro Alonso
6403c8deee 🐛 Fix inset child clip for frames with inner stroke 2026-03-26 13:33:15 +01:00
Elena Torro
1641eec672 🎉 Add stroke to path 2026-03-26 11:43:06 +01:00
alonso.torres
a5d908629b 🐛 Fix problems with z-index 2026-03-25 15:56:56 +01:00
alonso.torres
6b609566e1 🐛 Fix resize text modifiers 2026-03-25 07:29:20 +01:00
Elena Torro
5ba53f7296 🎉 Add background blur for wasm render 2026-03-18 17:43:27 +01:00
alonso.torres
1ab1d4f6ca 🐛 Fix problem with snap pixel transforms 2026-03-13 12:47:34 +01:00
Elena Torro
e72e2bf176 🐛 Fix stroke closing on outer strokes on paths 2026-03-12 09:02:37 +01:00