24525 Commits

Author SHA1 Message Date
Álvaro Tejero Cantero
5255fa1d60
🌐 Translate the copy notices of the merge dialogs
Adds the Spanish texts of the copy notices in the compare panel and
the conflict dialog, and a line that names the component a copy
follows a change through.

AI-assisted-by: claude-opus-5-5
2026-10-28 17:27:17 +01:00
Álvaro Tejero Cantero
10c603dbee
✨ List copies that follow through a nested component
The copies summary now follows the same cascade the post-merge sync
does: when a copy that follows a changed component sits inside another
component's main, that outer component's copies are listed too, marked
with the component they follow through. The summary is computed only
when a caller asks for it (the compare view and the conflict
responses), so the ahead and behind counts skip it.

AI-assisted-by: claude-opus-5-5
2026-10-28 17:27:17 +01:00
Álvaro Tejero Cantero
313d37ce91
✨ Show in the merge dialogs what happens to component copies
The compare panel and the conflict dialog list the copies that will
follow each changed component and the overrides each copy keeps. A
conflict on a component's main says how many copies the choice also
decides. A copy entry shows what the copy rule decided, and on a
conflict notes that the choice decides only the open properties. Copies
the rule settled entirely show as rows without a checkbox, outside the
counts.

AI-assisted-by: claude-opus-5-5
2026-10-28 16:59:34 +01:00
Álvaro Tejero Cantero
95ad841ed5
✨ Report what a merge does to component copies
The merge summary now says, for each component whose main the incoming
side changed, which copies will follow it after the integration and
which keep an override, per sync group and token slot. Each copy entry
lists what the copy rule settled, with the side and the reason, and
what the user still decides. A copy the rule settles entirely stays in
the summary as an informational entry instead of disappearing, and
applies nothing. A conflict on a component's main counts the copies
its resolution also decides.

AI-assisted-by: claude-opus-5-5
2026-10-28 16:59:34 +01:00
Álvaro Tejero Cantero
f598e73bec
✨ Make tokens on copies follow their component through a merge
A copy's applied tokens were one attribute outside every sync group,
so when both sides applied different tokens to a component, every
untouched copy showed a conflict of its own.

The copy rule now covers applied tokens one slot at a time, as
Penpot's own component sync does: a slot follows the component unless
the sync group of the attribute it drives is overridden. A token
applied to a copy by hand on one side wins, and only a slot both sides
applied differently by hand stays a conflict. The side the user picks
for that conflict decides only the open slots.

AI-assisted-by: claude-opus-5-5
2026-10-28 16:19:41 +01:00
Álvaro Tejero Cantero
0632188950
✨ Prove a reset of copy overrides survives a branch merge
A copy whose override the base holds can be reset on one side while
the other side leaves it alone. The tests check that the reset reaches
the other file in both directions: the copy loses its override, takes
the component's value, and follows a later component edit. The merge
direction failed before copies followed their component.

AI-assisted-by: claude-opus-5-5
2026-10-28 15:20:14 +01:00
Álvaro Tejero Cantero
d7cce4a930
✨ Make copies follow their component through a branch merge
A merge or an update from main compared a component's main and each
copy as separate shapes. A copy added on the other side kept the old
look, and when both sides changed the component the dialog showed one
conflict per copy, so a copy could end up disagreeing with its
component without being marked as overridden.

Copies now follow the merged component, as they do when the component
is edited. Per copy attribute: if no side overrode it, it raises no
conflict and follows the component; if one side overrode it, that
side wins; only overrides on both sides that differ stay a conflict.
After the merge or update applies, the copies of every local component
whose main changed are synced, nested copies included, and the sync
changes are validated and recorded with the rest. A clean merge of a
copy no longer marks it as overridden on the target.

AI-assisted-by: claude-opus-5-5
2026-10-28 14:29:34 +01:00
Álvaro Tejero Cantero
0c662250dc
🐛 Guard every reference a branch remap must rewrite
The schema safety net covered shapes only. It now also walks the file
data schema and each change type of the change schema, and fails when
a uuid field is neither a reference the remap rewrites nor classified
as none. Behaviour tests plant an id at every reference and check the
remap rewrites it and keeps every other id.

The nets found two gaps, both fixed: `remap-refs` kept the owning
`:file-id` of each media entry, which `remap-changes` already rewrote
in `:add-media`, and `remap-changes` skipped `:assign` operations of a
`:mod-obj`, which the change schema accepts.

AI-assisted-by: claude-opus-5-5
2026-10-28 13:44:12 +01:00
Álvaro Tejero Cantero
03eb22b945
⚡ Relink file and media references only where shapes keep them
Every branch read and compare re-points the references a document
holds to a file or a media object. The generic walk visited every map
of the document: 704 ms on a file of 90,720 shapes. The relinker now
visits only the places listed in `cfh/shape-ref-paths` (plus the paths
older data versions used, which binfile import still meets), compiled
once into closures that rebuild only what changed: 100 ms on the same
file, with an equal result. `remap-changes` dispatches on the change
type instead of walking whole change maps.

A new test derives every uuid path from the shape schema and fails
when a path is neither listed as a reference nor classified as none,
so a reference field added later cannot be missed silently. The grid
colour schema now declares the library reference it already carried.

AI-assisted-by: claude-opus-5-5
2026-10-28 13:32:22 +01:00
Álvaro Tejero Cantero
8444ee5595
Merge branch 'iop-vc-preview' into branch-id-reroute 2026-10-28 12:40:25 +01:00
Álvaro Tejero Cantero
902cbae781
💄 Indent the tail of materialize-file-branch
Two forms in materialize-file-branch sat at the indentation of an outer
form, which left the file failing the cljfmt check.

AI-assisted-by: claude-opus-5-5
2026-10-28 12:33:21 +01:00
Álvaro Tejero Cantero
939eb1a9d7
🐛 Copy the inherited images into a branch made an ordinary file
A branch reads the images it inherited through main's media rows. When
materialize turned it into an ordinary file, the file kept pointing at
those rows and released the base that kept main's file-gc away from
them, so an image main later dropped, or main's deletion, broke the
materialized file.

Materialize now copies every media row the document uses that another
file owns into the branch file under a fresh id, and re-points the
document at the copies before it persists it.

AI-assisted-by: claude-opus-5-5
2026-10-28 12:31:16 +01:00
Álvaro Tejero Cantero
87d61cbdb4
🐛 Give a branch file its own id in the document it derives
A branch derives its document from main's base snapshot, and the derive
never re-pointed main's ids: :data :id stayed main's, and so did every
inherited reference to the file itself (component instances, library
colour and typography refs). The branch workspace then took its own
components for an external library it never loads and read its own
tokens through main: validation reported :component-main-external and
:component-not-found on a fresh branch, the local component sync
skipped inherited copies, an update from main refused once the branch
held its own copy of a base component, and materialize detached the
main instance of every inherited component.

The derive now sets the branch's id on the document before the replay,
so a change or migration comparing a reference with the document's id
sees the branch, and re-routes main's id to the branch's after it, so
op log rows written before this fix are covered too. The base keeps
main's ids because it shares main's fragments; comparisons already map
the branch's id back to main's.

relink-refs rebuilds a collection only when something inside it
changed (median 803 ms instead of 1506 ms on a 90,612-shape file), and
it now relinks the :ref-file of shadow and grid colours, the key
migration 0015 gave them, which it had missed since.

AI-assisted-by: claude-opus-5-5
2026-10-28 12:31:12 +01:00
Álvaro Tejero Cantero
7f07f13940
🐛 Merge the tokens source of a branch
The merge engine never compared the file's tokens source, so a branch
that switched its tokens to a library, or back to its own, lost that
change on merge, and main's change was lost on an update from main.

The source is now a mergeable kind, :tokens-source, compared raw (nil
means the file itself, and a value naming the file reads as nil). A
change on one side applies as a :set-tokens-source change, and a
change on both sides is a conflict resolved like the base font size.

remap-refs now also rewrites the document's own :id and its
:tokens-source through the id map, and remap-changes rewrites the
:library-id of a :set-tokens-source change, so a branch document moved
into main's id space names main as itself. The compare panel labels
the new kind.

AI-assisted-by: claude-opus-5-5
2026-10-28 12:31:05 +01:00
Álvaro Tejero Cantero
5e9238f773
📚 Say in confirm-merge! that a merge deletes the branch
The docstring still offered to keep the branch archived, which the merge
dialog no longer does.

AI-assisted-by: claude-opus-5-5
2026-10-28 12:29:36 +01:00
Álvaro Tejero Cantero
6e3acf7482
♻️ Delete every merged branch, as penpot#12596 decided
The merge into iop-vc-preview kept the "Archive this branch" option of
the later merge dialog. Juan's decision in penpot#12596 holds instead: a
merged branch is always deleted, never archived. The merge and conflict
dialogs lose the option, merge-file-branch no longer takes keep-branch,
merged branches leave the branch list and count again, and the tests
for kept merged branches go, as in the pull request. The merge comment
stays. The warning about left-out changes no longer suggests archiving.

AI-assisted-by: claude-opus-5-5
2026-10-28 12:27:38 +01:00
Álvaro Tejero Cantero
90525f91a8
Merge remote-tracking branch 'origin/juan-compare-conflicts-modal' into iop-vc-preview 2026-10-28 12:06:14 +01:00
Álvaro Tejero Cantero
74e33d05b6
Merge branch 'juan-vc-ui-tweaks-01' into iop-vc-preview
Takes penpot#12596: branch access by team role alone (no file role on
a branch, the owner role on materialise, branches move with their
source file, migrations 0807 and 0808), the merge record with its
comment (migration 0806) and the reviews dialog, the new git icons, and
the workspace tidy-up.

Where Juan's later redesign of the compare, conflict and merge dialogs
(c68daed816, e4e237346e) already changed the same code, the redesign
wins: the old dialogs stay in branch_review/, the redrawn compare
icon stays, and the merge keeps its "Archive this branch" option. So
merge-file-branch accepts both `comment` and `keep-branch`, a kept
merged branch is listed and counted again, and the keep-branch tests
the PR removed are restored. The merge dialog asks for the comment
beside the archive option, and the conflicts dialog sends it on.

AI-assisted-by: claude-opus-5-5
2026-10-28 12:06:11 +01:00
Álvaro Tejero Cantero
7e5099727a
🐛 Make the base the only snapshot a file-gc test relies on
The save in file-gc-keeps-the-fragments-a-base-names took an automatic
snapshot of pre-edit main, which names the same fragments as the branch
base, so the test passed even with bases left out of liveness. It now
drops every other snapshot before the pass.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-10-28 11:48:12 +01:00
Álvaro Tejero Cantero
e53719f2b7
🐛 Keep a page reorder on main visible to a bounded compare
The page order lives in the document root, not in a page fragment, so
a reorder on main moves no fragment, and a compare bounded to the moved
pages compared the order of no page at all. moved-pages now leaves the
whole-file comparison in place when main orders the pages it shares
with the base differently from the base.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-10-28 11:42:49 +01:00
Álvaro Tejero Cantero
9d722cfb99
🔥 Remove the stray npm lockfile from the repository root
The preview commit added a package-lock.json beside pnpm-lock.yaml. The
repository installs with pnpm, so the npm lockfile was an artefact of a
local npm run and no tool reads it.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-10-28 11:40:18 +01:00
Álvaro Tejero Cantero
709a92b9c0
⚡ Bound the main side of a branch compare to the pages main moved
A page whose fragment id main shares with the branch's base holds the
same document on both sides, because a fragment row never changes after
it is written. The derive now hands back the fragment ids of the base
pages, and a comparison loads only the pages of main whose id differs:
the base's realized page stands in for every other page. The direction
that reports main's changes (the compare's main->branch and the reverse
pass of the diff counts) is bounded to those pages with :only-pages.

A base that was migrated, or that holds no pointers, gives no ids and
keeps the whole-file comparison. On a copy of the scaling corpus rung L7
(69 pages, 47,514 objects) the main->branch compare drops from 5,690 ms
to 1,965 ms and the branch->main compare from 3,254 ms to 1,981 ms; what
remains is the branch derive, which still realizes the whole base.

The compare's size gate now runs after the derive, because main's
document is assembled from the base. The depth gate still runs before
it, and the base passed the size gate when it was written.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-10-28 11:36:52 +01:00
Álvaro Tejero Cantero
885099326a
✨ Store branch bases as pointers into the source file
A branch base was a fully realized copy of main. With fdata/pointer-map,
creation now stores main's fragment pointers: the base costs one pointer
per page and shares main's fragment rows.

Fragment rows are keyed by (file_id, id), so the base readers (the branch
derive and get-base-data) realize the base with the source file id
through realize-branch-base. The branch file's own loader finds none of
main's fragments.

Every writer of a snapshot that names main's fragments reads main's row
FOR SHARE, so it serializes with file-gc, which takes the row FOR UPDATE
and marks every fragment no committed snapshot names. Creation, the
pre-merge snapshot, and the base repositioned by an update from main
follow that rule. The merge and the update put main's stored pointer
back on every page they left unchanged, so the merge rewrites only the
pages it changed and a repositioned base shares main's fragments.

The hand-written pmap bindings move to fdata/with-tracked-pointers.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-10-28 11:20:26 +01:00
elhombretecla
d7ede341c7 ✨ Say what goes with an added or deleted layer
When a layer goes, Penpot also removes the prototype interactions that
lead to it and the flows that start on it, and resizes the boards with
a layout around it. The diff lists those changes as entries of their
own, with nothing that ties them to their cause.

The detail pane of an added or deleted layer now ends with what goes
(or comes) with it:

- Interactions that led into it and the flows that started on it, as
  warnings, naming the layers and flows they belong to.
- Each board with a flex or grid layout around it, and how it resizes,
  or how many of its other layers move when its size is fixed.
- The names of the layers folded under it.

Added and deleted flow entries carry the flow as `:flow`, as shape
entries carry `:shape`, so the pane can name the board a deleted flow
started on.
2026-10-28 09:50:32 +01:00
elhombretecla
d103805086 🐛 Round the numbers of the compare dialog once a plugin has loaded
Sizes, positions and other numbers in the compare dialog showed every
decimal (162.99999952316284 × 42.99999808520081) as soon as a plugin
had loaded. The plugin runtime's SES lockdown turns
`Number.prototype.toLocaleString` into `toString`, which ignores the
two-decimal limit and the locale.

The numbers now go through `Intl.NumberFormat`, which the lockdown
leaves alone, and fall back to the default locale when none is set.
2026-10-28 09:50:32 +01:00
elhombretecla
876ab29ba9 ✨ Fold the layers deleted with their container into one row
Deleting a group listed the group and each of its layers as rows of
their own, so one deletion read as four changes. Each row could be
left out alone too, which allowed a merge to keep a group while
deleting what it holds.

A layer added or deleted with its container now folds under the
outermost container added or deleted with it. The row says how many
layers it holds and its checkbox takes them along, so they travel or
stay together. The detail pane lists them as its contents.

The filters, the footer, the merge button and the merge confirmation
count the rows the list shows, so they agree with one another.
2026-10-28 08:52:19 +01:00
elhombretecla
9ae262b0ff ✨ Show added and deleted layers in the board that holds them
The compare dialog drew nothing for a deleted layer when the branch was
open, and nothing for an added one when main was: the diff named the
layer by id and label only, and the open file lacks it. The detail pane
was left with the layer's name and nothing else.

Added and deleted shape entries now carry the whole shape, as the
document that holds it has it, under `:shape`. The key names no side,
so it reads the same in both directions.

The previews use it. A layer added or deleted inside a board is shown
in that board on both sides, outlined in green or red on the side that
holds it, so the change reads as what the board gains or loses. The
facts of the detail pane (where it was, type, size) read it as well.

A board that moved or resized now draws at its own size on the side
the open file lacks: the diff leaves out `:selrect` and `:points`, so
they are rebuilt from position and size instead of kept from the open
side.
2026-10-28 08:45:58 +01:00
Álvaro Tejero Cantero
607920fad3
Merge branch 'issue-12410' into iop-vc-preview
Brings in the fragmented file data stabilization across snapshots and
GC (penpot#12410) and the per-library pointer loader fix. Four files
conflicted.

file_gc.clj `process-file!` keeps the branch's `:is-branch` skip and
its warning; ordinary files run the new body under `with-pointers`
(snapshot liveness, reattach of unchanged fragments, fragment
cleaning).

files_snapshot.clj `restore-file-snapshot` takes the new backup
transaction, which reads the file FOR SHARE and gives the guard revn.
`fsnap/check-restorable!` now runs on that read, before the backup
snapshot is created, so a branch file is still refused without a
committed backup.

binfile_test.clj and rpc_file_snapshot_test.clj keep the tests both
sides appended.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-10-28 06:34:39 +01:00
Álvaro Tejero Cantero
1342b27f6f
Merge remote-tracking branch 'origin/develop' into iop-vc-preview
Merges develop at f6f9df9a1a. Twelve files conflicted; each keeps
both intents.

files_update.clj: develop's per-stage validation timings wrap the
branch save path. `update-file` keeps the branch's early timer and
passes it to `update-file*`, inside develop's try/catch timing log.
`process-changes-and-validate` times the changes application and
still returns the file, the fixed-up change vector and the media
index. The audit props carry develop's revn and modified-at plus the
branch props. The timing test destructures the new return shape.

v3.clj keeps the branch-file overwrite guard and takes develop's
`configure-import-transaction`. migrations.clj puts develop's 0155
to 0157 before the branch's 0800 to 0805, so the branch's stay the
tail of the vector.

errors.cljs checks the merged and archived branch refusals before
develop's retrying case. left_header.cljs keeps the branch wrapper,
the review badge and the branch bar on develop's markup: the status
dot without icons and the DS library icon on the shared badge.
palette.scss and assets.scss keep the branch clearance terms on
develop's logical properties. The test runners, requires and
deftests are unions.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-10-28 06:28:08 +01:00
Álvaro Tejero Cantero
a6c20b3e95
🐛 Realize library pointers with each library's own loader
Fragment rows are keyed by file id, and every caller binds the
fragment loader of the file it works on. get-resolved-file-libraries
returned libraries with their pages still as pointers, so the first
read of a library page under fdata/pointer-map asked the consuming
file for the fragment and failed with :fragment-not-found. That
broke migrations and file validation for every file using a
pointer-mapped library.

The weak-map loader now realizes each library's pointers with the
library's own id. The test fails without the change.

AI-assisted-by: claude-opus-5-5
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-10-28 06:19:29 +01:00
Andrey Antukh
42a2e33ffe
✨ Stabilize fragmented file data across snapshots and GC
Snapshots, automatic and user, now store pointer-based rows that share
fragments with the live file instead of duplicating page content.
`get-file-snapshot` resolves those pointers on the server so version
previews keep rendering plain data, and `restore!` reuses the shared
rows. Export still materializes (the package carries no fragments) and
import regenerates them.

`file-gc` treats the main file and every unpurged snapshot as liveness
roots, reads the file unrealized, and reattaches the original pointer
wherever the cleaned value is unchanged, so a pass over an untouched
file writes no fragments and a pass over an edited file keeps the ids
of the pages it did not touch. Snapshot creation and the restore
backup take the file row `FOR SHARE`, which serializes them with the
pass and closes the race that let a new snapshot reference fragments
the pass was about to mark.

All backend pointer bindings go through the `fdata/with-pointers`,
`with-tracked`, `with-tracked-pointers` and `with-untracked` macros.
The loader reads fragments a previous pass marked until `objects-gc`
purges them, and the frontend fails loudly with retry instead of
dropping an unresolved fragment.

Review fixes in this branch: the automatic snapshot attaches before
pending migrations, `realize-pointers` is the single verb,
`reattach-unchanged` takes the identity fast path, the restore guard
takes its revn from the backup read, and `PointerMap.withMeta`
generates a fresh id when metadata changes.

Tests cover both snapshot writers (red without the lock), snapshot
sharing and pin release through the real delete and purge paths,
reattach against orphan marking, preview after offload, the pointer
binding contracts and the with-meta id change.

Closes #12410

AI-assisted-by: muse-spark-1.3-contributor
AI-assisted-by: deepseek-v4.1-flash

♻️ Pin no-churn behavior for untouched pages

Pin the behavior the churn verification measured: a save keeps the
fragment id of every page it does not touch, with a page of eleven keys
(PersistentHashMap), a 46-shape objects map and a nine-page index (hash
map). Also pin the mixed case: a data-level objects-map flip rewrites
the old pointer pages once and the next save keeps the new ids.

The memory records the identity rule behind it: re-assoc with the same
value returns the same map in array and hash maps alike.

Closes #12410

AI-assisted-by: deepseek-v4.1-flash

⚡ Stop loading untouched pages when enabling objects-map

`enable-objects-map` wrapped every page on every save, and reading a
page's objects forced its PointerMap to load the fragment. A save of
one page in a seven-page file therefore made seven fragment reads.

Skip unloaded pointers: their fragment was already wrapped by the
encode-file that wrote it, so loading it only to wrap it again is a
no-op. The save now loads only the pages it edits. A page with a plain
fragment (a data-level feature flip) keeps its plain fragment until it
is first loaded or edited; a migration that enables the feature must
rewrite the data itself.

Cover the change with load-count tests for save and restore, update the
flip test to the new contract, and drop the stale restore comment.

Closes #12410

AI-assisted-by: deepseek-v4.1-flash

⚡ Keep page fragment ids through pending migrations

Applying a pending migration on read realized the file, ran the
migration and persisted it again; the persist wrapped every plain page
in a new PointerMap, so every page got a new fragment id even when the
migration did not change it. That leaves the fragment cache cold and
rewrites every fragment on the first read after a deploy.

Capture the original pointers before realizing, reattach the unchanged
positions after migrating (same value comparison file-gc already uses),
persist the reattached file and return the realized one: the read
contract is plain data. Read-only connections skip the persist and the
reattach. A migration that adds objects-map skips the reattach so the
pages get wrapped.

Move the three helpers to fdata so file-gc and the migration path share
them, and cover the path with a real 0031 migration test: the unchanged
page keeps its id, the changed page gets a new one, the caller gets
realized data and a second read does not migrate again.

Closes #12410

AI-assisted-by: deepseek-v4.1-flash
2026-10-28 06:16:37 +01:00
Eva Marco
f6f9df9a1a
♻️ Use DS icons in the layer row buttons (#12477)
* ♻️ Use DS icons in the layer row buttons

The expand, visibility and lock buttons of the layer rows drew their
icons with the deprecated icon set (arrow, shown, hide, unlock, lock).
Use the DS icon instead. The buttons stay raw: they are segments of the
layer row, with backgrounds that follow the row state.

The icons get a class each, so their colors move from svg rules to
those classes, and they keep the old stroke weight. The DS icon draws
12px inside a 16px box:

- The expand button start padding goes from 8px to 6px, so the arrow
  stays in place.
- The visibility and lock buttons leave 12px for the icon, so their
  icons do not shrink (flex-shrink: 0) and stay centered, uncut.

The rows look the same as before, pixel for pixel. Add a visual test
with a selected, hidden and blocked layer and a hovered row.

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

* ♻️ Move the layer name styles off the deprecated styles

The layer name and its rename input used the deprecated body-small
typography, mixins, spacing and token aliases. They now use the DS
body-small typography, sizes, borders and px2rem(), and the layer row
color aliases resolve to their DS tokens, in layer_item.scss too.

The DS body-small has a 1.3 line-height instead of 1.4, so the layer
names sit 1px higher. The name color is one local variable for every
state; the rename input sets it too, since it also has .element-name.
A visual test now covers the rename input.

AI-assisted-by: claude-opus-5-5
2026-10-27 16:01:53 +01:00
elhombretecla
e4e237346e ✨ Sum up what a merge applies and leaves out
The compare dialog no longer opens on an overview, so the merge
confirmation now says what the merge does: how many changes go into
main and how many were left out.

When changes are left out and the branch is not kept archived, a
warning replaces the deletion note: deleting the branch loses those
changes. The counts show when the compare dialog of that branch asked
for the merge, as it holds the comparison.
2026-10-27 15:59:40 +01:00
elhombretecla
c68daed816 ✨ Redesign the branch compare and conflict dialogs
The compare and conflict dialogs listed raw entries with their raw
property values. They now read as a review: each change says what it
did in words, where it lives and what it affects, and only what truly
needs a choice asks for one.

Both dialogs move out of the branches sidebar namespace into
branch_review/ (layout, compare, conflicts, previews, facts and the
pure helpers in common) and share one frame: the route between the
branch and main, a bar of category filters, the list beside a detail
pane, and a footer. They size to the window within bounds, so the
panes and previews grow on large screens and stay usable on small ones.

Compare dialog:

- Changes are grouped by page and by top-level board, with decisions
  first and pages and library entries in groups of their own. A board
  folds its changes only when it holds two or more of the listed ones.
- Category filters (Decisions, Copy, Color, Layout, Components,
  Variants, Tokens, Other) are neutral chips with an icon; colour is
  kept for what happened to an entry: added, modified, deleted or in
  conflict.
- Each row carries a one-line summary of the kinds of change ("Fill
  changed", "Moved") and the detail shows the values, before and after.
- Previews show main and the branch side by side or overlaid, and an
  expanded view hands them the whole dialog. The side the open file
  lacks is rebuilt from the changed attributes; a side with nothing to
  draw says why (not in main, deleted, no preview).
- New and deleted pages and layers say what they hold and where they
  land; deleted components, colours, typographies and tokens count the
  layers of the open file that depend on them.
- Changes can be marked as reviewed while moving through the list.

Conflict dialog:

- A conflict compares each property with its base value. Properties
  that one side changed alone come filled in from that side, so only
  the real clashes wait for a choice, property by property.
- Text conflicts show the paragraphs each side keeps and drops, and
  entities that cannot be split offer the two versions to pick from.
- Main, branch and result previews and a note of what the decision
  drops sit beside the property table.

The compare buttons take the new git-compare-arrows icon.
2026-10-27 15:59:33 +01:00
elhombretecla
f56da68b95 ✨ Add change review components and icons to the DS
Add four product components that the branch review dialogs build on:

- tag*: a short status word, as a small code-font label or a rounded
  pill, in neutral or one of six tones, with an optional icon.
- filter-chip*: a rounded toggle that filters a list by category. A
  selected chip draws its outline, text and icon in its tone, or in the
  accent colour when neutral.
- change-row*: one entry of a change list, a short status bar beside a
  card with an icon, a title over a summary, and a slot at each end.
- preview-card*: a framed preview of one side of a comparison (base,
  main, branch or result) over a transparency checkerboard. A container
  can size its stage through two custom properties.

Each one ships with its Storybook story and docs, and is exported from
the DS entry point.

Add three icons: side-by-side and collapse for the comparison views,
and git-compare-arrows for the compare action.
2026-10-27 15:59:14 +01:00
Dr. Dominik Jain
9d5dad3de7
⬆️ Upgrade to Serena v2.1.0 (#12482)
* ⬆️ Upgrade to Serena v2.1.0

Switch to REPL interface (configured in project)

Additional changes:
* Remove additional Serena project in subfolder mcp/

* 🐛 Fix outdated memories

---------

Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2026-10-27 14:59:22 +01:00
Eva Marco
c26a67a8dd
♻️ Use a DS button for previous in the onboarding questions (#12550)
* ♻️ Use a DS button for previous in the onboarding questions

The "Previous" button of the onboarding questions becomes a DS
secondary button; it was a raw button with the deprecated modal cancel
styles. It is a bit narrower and its label uses the DS typography.

"Next" stays: it is the shared forms submit button. The "Other" option
icon stays too: it belongs to the forms image radio buttons, which move
with the radio buttons group. A visual test covers the step 2 buttons
at rest and with "Previous" hovered.

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

* ♻️ Move onboarding questions styles closer to the DS

The onboarding questions stylesheet resolves its legacy modal color
aliases to DS tokens, uses logical properties and the DS typography
module without a namespace. refactor/ stays only for the modal
placeholders (%modal-*), which move with the modals refactor. No visual
change (checked on steps 1 and 2; only the shuffled options differ
between runs).

AI-assisted-by: claude-opus-5-5
2026-10-27 14:07:42 +01:00
Eva Marco
29e79edc66
♻️ Use DS buttons and icons in the dashboard team pages (#12531)
* ♻️ Use DS buttons and icons in the dashboard team pages

The team pages (members, invitations, webhooks and settings) and their
modals move off the deprecated icons and raw buttons:

- The member and webhook row menus become ghost DS icon buttons named
  "Options", with aria-expanded.
- The member and invitation role selectors become buttons with
  aria-haspopup and aria-expanded, and use the DS arrow icon. They were
  divs; one carried a role and an id repeated on every row.
- The four modal close buttons become ghost DS icon buttons named
  "Close"; the class that places them goes on the tooltip wrapper.
- "Create webhook" becomes a DS primary button.
- The webhook status, team image and settings icons use the DS icons.

The invitation checkbox tick stays until the checkbox migration. A
visual test covers the role and row menus, the resend and webhook
modals and the team image overlay.

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

* ♻️ Move dashboard team styles to logical properties and DS tokens

The remaining legacy color aliases resolve to their DS tokens, physical
offsets and margins become logical properties, and the DS typography
module is used without a namespace. Four selectors that team.cljs does
not use are commented out with a TODO.

refactor/ stays only for component-family placeholders: %input-checkbox
and %checkbox-icon (checkboxes) and %modal-* (modals). The shared
dashboard header styles (common-dashboard) are used by ten dashboard
modules and move in their own change. No visual change.

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

* 🐛 Open the team row menus below their button

The member and webhook row menus were placed from the bottom of their
cell with a fixed offset, so their position depended on the menu
height and the button height. The two-item webhook menu covered its
button, the member menu covered the lower part of it, and the taller DS
icon button moved both 3px down. They now open 4px below the button.

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

* 🔥 Remove unused styles from the dashboard team pages

The SCSS refactor left four rules commented out with a TODO because
team.cljs does not use them. CSS modules scope the classes to this
module and no other source uses them, so they go: .dashboard-top-cta,
.error-notification, .empty-invitations-buttons and
.invitations-dropdown. No visual change.

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

* 🐛 Find the member role selector as a button in the dashboard test

The "Bug 9443" test looked for the member role selector as a combobox.
The selector is now a button with aria-haspopup, so the test failed in
CI. It now looks for a button named "Admin", and checks that the owner
has no such button.

AI-assisted-by: claude-opus-5-5
2026-10-27 12:19:47 +01:00
Alejandro Alonso
deb5771248
✨ Use WASM for SVG markup in code inspect and copy (#12567) (#12608)
When render-wasm is active, generate SVG for the Code inspector and
Copy as SVG from the WASM export path instead of classic SVG codegen.
Add multi-root render_shapes_svg so multi-select stays one document.

Closes #12567
2026-10-27 12:05:30 +01:00
Eva Marco
112e6dedd7
♻️ Use DS icons in the copy button (#12558)
* ♻️ Use DS icons in the copy button

The shared copy button (Inspect attribute rows and code blocks, share
link, integrations) draws its clipboard and tick icons with the DS
icons at the small size, colored through the color property. The
button itself stays: it wraps whole rows. The icons are the same
drawings, so nothing changes on screen.

A visual test covers an Inspect computed attribute row and the code
block copy button, at rest, on hover and after copying.

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

* ♻️ Move copy button styles off the deprecated styles

The copy button stylesheet no longer imports refactor/: the
button-style, flex-center and copy-wrapper-base mixins are inlined with
DS values, the tertiary button and icon color aliases resolve to their
DS tokens, and physical properties become logical ones. No visual
change (whole viewer visual spec compared; the code tab syntax
highlighting loads late on a cold first run, with either stylesheet).

AI-assisted-by: claude-opus-5-5
2026-10-27 11:41:36 +01:00
Eva Marco
8800ff4bc9
♻️ Use DS buttons and icons in the assets tab (#12559)
* ♻️ Use DS buttons and icons in the assets tab

- "Add library" and "Manage library" become DS primary and secondary
  buttons, full width with the label centered; they keep their test id.
- The filter button stays a raw segment of the search bar; its legacy
  icon becomes the DS filter icon. Its opened state comes before the
  focus and hover rules, as the legacy placeholder did, so the focus
  border still shows.
- The create/rename group modal gets a ghost DS icon button named
  "Close", placed on the tooltip wrapper, and DS cancel (secondary) and
  create/rename (primary) buttons instead of raw inputs.

A visual test covers the add and manage library buttons, the filter at
rest, on hover and opened, and the create group modal.

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

* ♻️ Move assets tab styles off the deprecated styles

assets.scss no longer imports refactor/: its mixins, sizes, spacing and
input / button icon color aliases resolve to DS values and tokens, with
logical properties. The unused .sections-container, .section-item and
.section-btn rules are deleted (the filter menu is the shared context
menu).

assets/groups.scss does the same and keeps refactor/ only for the modal
placeholders (%modal-*) and %input-with-label. Its legacy uppercase
title becomes the DS headline-small typography, which differs only in
its size (12px instead of 11px): the group modal grows 2px.

AI-assisted-by: claude-opus-5-5
2026-10-27 11:41:23 +01:00
Eva Marco
33d304c0af
♻️ Use DS icons in the dashboard sidebar (#12534)
* ♻️ Use DS icons in the dashboard sidebar

The search, clear search, pinned projects placeholder and logout icons
of the dashboard sidebar use the DS icons, and they get their color
from the color property. The two search buttons had untranslated test
names as their labels ("dashboard-search"); they are now named "Search"
and "Clear search". The pin keeps its place: the DS small icon draws
12px inside a 16px box, so its margins shrink by 2px.

New key: dashboard.clear-search (en, es). The dashboard page object
looks the search button up by its new name. A visual test covers the
search box with and without a term, the pinned projects placeholder and
the profile menu.

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

* ♻️ Move dashboard sidebar styles off the deprecated styles

Legacy color aliases resolve to their DS tokens, deprecated mixins,
sizes and font variables are inlined with DS values, physical
properties become logical ones and the DS typography module is used
without a namespace. An invalid max-width with two values, which the
browser ignored, is gone.

Two rules that match nothing are commented out with a TODO: the profile
menu arrow svg rule (the arrow is the DS icon svg itself) and the
upgrade plan section, which sidebar.cljs does not use.

refactor/ stays only for the profile menu placeholders (%menu-dropdown,
%menu-item-base) and the shared dashboard header styles
(common-dashboard). No visual change.

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

* 🔥 Remove unused styles from the dashboard sidebar

The SCSS refactor of the dashboard sidebar left rules commented out with
a TODO because the module does not use them, or because they target an
svg inside a DS icon that is the svg itself. CSS modules scope the
classes to this module and no other source uses them, so they go:
.profile-dropdown-item .open-arrow svg, .upgrade-plan-section,
.penpot-free, .no-limits, .power-up. No visual change.

AI-assisted-by: claude-opus-5-5
2026-10-27 11:41:19 +01:00
Eva Marco
afd838ea5b
♻️ Use DS buttons in the dashboard team modals (#12537)
* ♻️ Use DS buttons in the dashboard team modals

The close buttons of the create/rename team modal, the team permission
notice and the two "before you leave" modals become ghost DS icon
buttons named "Close"; the class that places them goes on the tooltip
wrapper. The cancel and "promote and leave" inputs of the leave modals
become DS buttons: secondary, and destructive once a member is picked
(primary and disabled before).

Two fixtures make the leave and reassign modal testable (the logged-in
user owns the second team, which has another member). A visual test
covers the create team modal and the leave and reassign modal.

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

* ♻️ Move dashboard team modal styles closer to the DS

The change owner and team form stylesheets resolve their legacy color
aliases, spacing and typography mixins to DS tokens and use the DS
typography module without a namespace. The legacy uppercase title
becomes the DS headline-small typography, which differs only in its
size (12px instead of 11px): the "before you leave" title grows 1px and
the modal 2px. Unused selectors are commented out with a TODO.

refactor/ stays only for the modal placeholders (%modal-*), which move
with the modals refactor, and the input placeholders.

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

* 🔥 Remove unused styles from the dashboard team modals

The SCSS refactor of the dashboard team modals left rules commented out
with a TODO because the module does not use them, or because they target
an svg inside a DS icon that is the svg itself. CSS modules scope the
classes to this module and no other source uses them, so they go:
.input-wrapper, .cancel-button, &.danger. No visual change.

AI-assisted-by: claude-opus-5-5
2026-10-27 10:58:13 +01:00
Eva Marco
19fc9ebce8
♻️ Use DS icons in the viewer flows and interactions menus (#12520)
* ♻️ Use DS icons in the viewer flows and interactions menus

The flows and interactions triggers in the viewer header were divs with
a click handler, and their menus lived inside them. They are now
buttons with aria-expanded and aria-haspopup, and each menu sits next
to its button in a wrapper, since a button cannot hold the menu
entries. The play, arrow and tick icons use DS icons. The menu entries
stay as they are.

A visual test covers the interactions menu closed and open; the test
files have no flows, and the flows trigger shares its styles.

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

* ♻️ Move viewer interactions styles off the deprecated styles

Spacing, sizes, borders, typographies and color aliases move to DS
tokens and logical properties. The DS icons take their color from the
text and no longer shrink, so the old icon placeholders go away. The
DS body-small line-height is more compact, so the trigger labels sit
1px higher. The refactor/ import stays only for the shared menu
placeholders.

AI-assisted-by: claude-opus-5-5
2026-10-27 10:47:07 +01:00
Eva Marco
9854f8e54f
♻️ Move dashboard subscription styles off the deprecated styles (#12538)
* ♻️ Move dashboard subscription styles off the deprecated styles

The subscription stylesheet inlines the deprecated button-style and
flex-center mixins, resolves its legacy color aliases and sizes to DS
tokens, uses logical properties and the DS typography module without a
namespace. Three rules that match nothing (the arrow svg, .cta-link and
.open-arrow) are commented out with a TODO.

The subscription controls need no DS change: the plan card, the current
plan card and the manage subscription link have no DS equivalent, and
they use no legacy icons. refactor/ stays only for the power up menu
item placeholder and the shared dashboard header styles.

A visual test covers the sidebar plan card (closed and open), the team
settings plan and the members seats warnings. No visual change.

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

* 🔥 Remove unused styles from the dashboard subscription

The SCSS refactor of the dashboard subscription left rules commented out
with a TODO because the module does not use them, or because they target
an svg inside a DS icon that is the svg itself. CSS modules scope the
classes to this module and no other source uses them, so they go:
.icon-dropdown svg, .cta-link, .open-arrow. No visual change.

AI-assisted-by: claude-opus-5-5
2026-10-27 10:23:55 +01:00
Eva Marco
411f661bdb
♻️ Use DS buttons in the delete shared libraries modal (#12554)
* ♻️ Use DS buttons in the delete shared libraries modal

The delete shared libraries modal (unpublish a library, delete a shared
file or project) gets a ghost DS icon button named "Close", placed on
the tooltip wrapper, and DS buttons for cancel (secondary) and accept
(destructive for the danger style, primary otherwise) instead of raw
inputs. The buttons are narrower and the accept button takes the DS
destructive red.

A new empty library references fixture and a visual test cover the
unpublish library modal from the workspace main menu.

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

* ♻️ Move delete shared libraries modal styles closer to the DS

The delete shared libraries stylesheet resolves its legacy modal color
aliases to DS tokens, uses a logical property and the DS typography
module without a namespace. refactor/ stays only for the modal
placeholders (%modal-*), which move with the modals refactor. Two
unused selectors are commented out with a TODO. No visual change.

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

* 🔥 Remove unused styles from the delete shared libraries modal

The SCSS refactor of the delete shared libraries modal left rules
commented out with a TODO because the module does not use them, or
because they target an svg inside a DS icon that is the svg itself. CSS
modules scope the classes to this module and no other source uses them,
so they go: &.transparent, .list-wrapper. No visual change.

AI-assisted-by: claude-opus-5-5
2026-10-27 10:20:11 +01:00
Pablo Alba
e4c1b154e3
✨ Add profile name and email to nitrate audit log (#12636)
* ✨ Add profile name and email to nitrate audit log

* ✨ Mr changes
2026-10-27 09:30:28 +01:00
David Barragán Merino
4b13562d02 👷 Use only the closest tag in the build key
The full `git describe` output is not stable across checkouts: the
length of its -g<sha> suffix depends on the local object count, so
build-bundle and build-docker could compute different keys for the same
commit on different runner workspaces. The sha already pins the commit,
so the closest tag is enough to tell versions apart.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-10-26 20:30:08 +01:00
David Barragán Merino
cf19d7f870 👷 Include the version in the bundle and image build key
The bundle stamps the version into version.txt, so a commit that gets a
new tag (2.18.1-RC1 -> 2.18.1) needs a new bundle and image set. Keying
them by commit sha alone reused the RC artifacts for the final release.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-10-26 19:55:11 +01:00
Eva Marco
f07452e36b
♻️ Use DS buttons and icons in the inspect code tab (#12521)
* ♻️ Use DS buttons and icons in the inspect code tab

"Copy all code" becomes a DS secondary button, and the expand panel
buttons ghost DS icon buttons labelled "Expand panel" (new key, en and
es). The collapse toggles of the CSS and markup blocks stay raw
buttons, since they sit in a 12px column; their arrow uses DS icons and
they get a name and aria-expanded. The copy buttons belong to the
shared copy button and move with it.

A visual test covers the code tab buttons, expanded and collapsed.

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

* ♻️ Move inspect code tab styles off the deprecated styles

Spacing, sizes, borders, typographies and color aliases move to DS
tokens and logical properties. The 11px uppercase titles become the
12px DS headline-small, the only difference. The code typography keeps
its values with a TODO: the DS code-font names "Roboto Mono", while
the app loads "robotomono". The full-width "Copy all code" DS button
centers its label explicitly. The refactor/ import stays only for the
copy buttons, styled here until the shared copy button moves to the DS.

AI-assisted-by: claude-opus-5-5
2026-10-26 17:55:54 +01:00