23499 Commits

Author SHA1 Message Date
Madalena Melo
7348620fcd
📚 Update CONTRIBUTING.md with AI policy (#11782)
Include policy for AI assisted contributions and fix "link to title format" section

Signed-off-by: Madalena Melo <madalena.melo@kaleidos.net>
2026-09-18 13:09:35 +02:00
Andrey Antukh
5b3e36489c 📚 Document int?/integer? predicate coverage in Clojure memory
Review assumed int? was 32-bit; it covers Long/Integer/Short/Byte.
Note it in mem:clojure/idioms so the mistake is not repeated.

AI-assisted-by: muse-spark-1.3-contributor
2.18.0-RC7
2026-09-17 20:23:22 +02:00
Alonso Torres
2fc2a9064a
🐛 Add download report to the error toast (#11762)
* 🐛 Add download report to the error toast

* 🐛 Restore old behavior for some cases
2026-09-17 17:05:06 +02:00
Alejandro Alonso
14c3135e21 Merge remote-tracking branch 'origin/main' into staging 2.18.0-RC6 2026-09-17 11:26:23 +02:00
Alonso Torres
420aa981b2 🐛 Fix stalled saving states (#11699) 2026-09-17 11:25:36 +02:00
Andrey Antukh
9ae967c621 📚 Add plan status lifecycle and review log
Give every plan a write-restricted Status (draft, reviewed,
done) and an append-only Review Log with UTC ISO 8601 lines.

Make-a-plan creates plans as draft and is the only flow
writing reviewed, on explicit user apply. Review-plan stays
read-only. Implement-plan closes the plan to done with the
issue URL when one exists, in the same commit as the code.
Document the lifecycle in the agents README.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-16 18:12:12 +00:00
Andrey Antukh
ebba70ad2b 📚 Normalize plan followup and sub-plan naming
Define derived plan naming for .agents/plans/.

Parent basename stays intact and derivatives append
--review-NN for review followups and --task-NN for
roadmap sub-plans, with no new date so ls groups them.
Document the rule in the planner skill, the in-place
vs new-file policy in make-a-plan, and examples in
the agents README.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-16 18:00:55 +00:00
Andrey Antukh
30e52af22e 📎 Backport creating-issue serena memories from develop 2026-09-16 19:48:50 +02:00
David Barragán Merino
443622b2f9 🐳 Keep dist-upgrade unattended across the image builds
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-16 18:28:48 +02:00
Andrey Antukh
8ff99f0766 📚 Document how to add issues as sub-issues
Add the verified REST procedure for linking an issue as a
sub-issue of an umbrella/EPIC: get the REST id, POST to the
parent's sub_issues endpoint with a typed -F field, and verify
both directions. Route it from the create-issue skill.

AI-assisted-by: deepseek-v4.1-flash
2026-09-15 19:56:48 +02:00
Andrey Antukh
e07bda4fe2 📎 Backport .agents from develop 2026-09-15 19:56:37 +02:00
Andrey Antukh
02e573100a 📚 Update changelog 2026-09-15 19:03:39 +02:00
Chulgil Lee
cdda6ae33c 🐛 Fix form select label resetting to its default option (#11664)
select* resets its label to `default-selected` whenever its options
change identity. The access token dialog builds its options vector
inline, so picking an expiration re-rendered the dialog and the label
snapped back to "Never", although the form kept the picked value and
the token was created with it.

form-select* now passes the form value as `default-selected` when it
holds a non-blank string, so the reset lands on the picked option. The
caller's default still applies while the form value is blank.

Closes #11663

AI-assisted-by: claude-opus-5

Signed-off-by: chulgil <2044587+chulgil@users.noreply.github.com>
Co-authored-by: chulgil <2044587+chulgil@users.noreply.github.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-09-15 18:56:12 +02:00
David Barragán Merino
1f64cc3e52 🐛 Make integration test summary non-fatal on staging
The summary step reads a filter from .github/scripts, which never
landed on this branch, so every run failed with exit code 2 even when
all tests passed. Add the filter and tolerate both a missing file and
a jq error.

Use `!cancelled()` instead of `always()` on the merge job, so it no
longer runs after cancel-in-progress killed the shards without
uploading their blobs.

Lower the JSON report retention to 7 days, the repository maximum.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-15 18:55:11 +02:00
Andrey Antukh
39ca4c264c
🔥 Remove onboarding A/B test and welcome file creation (#11707)
Drop the onboarding-03 experiment consulted through
external-feature-flag, keeping the false-branch behavior: registration
never requests a welcome file and the workspace never shows the
onboarding modals. Remove the now-unused welcome-file machinery on the
backend (RPC wiring, welcome_file namespace, welcome-file-id prop and
the post-login redirect). Keep the external-feature-flag helper as the
seam for future experiments and note it in mem:frontend/core.

Closes #11705

AI-assisted-by: muse-spark-1.3-contributor
2026-09-15 14:14:32 +02:00
Andrey Antukh
5d5f7fcc14 🐛 Fix workspace crash on non-string thumbnail URIs
Thumbnail :uri values arrive from the server over transit, so
media-ids decode to UUID objects instead of strings. The
resolved-uri? helper added in #11563 called
clojure.string/starts-with? on them unconditionally, raising
TypeError: str.lastIndexOf is not a function and crashing the
workspace on frame render. Guard with string? so non-string
URIs fall through to resolve-media, which stringifies them.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-14 16:33:54 +02:00
Andrey Antukh
f4cf6f46f8
🐛 Use constant-time comparison for management API shared key auth (#11534)
The management API shared-key-auth middleware was using the standard = operator for key comparison, which is vulnerable to timing attacks. The RPC middleware already uses constant-time comparison via MessageDigest/isEqual.

This change:
- Makes constant-time-eq? public in app.http.middleware
- Updates app.http.management/shared-key-auth to use mw/constant-time-eq?
- Fixes an inconsistency where the nil-key branch returned a 2-arg function
- Adds comprehensive tests for the management shared-key-auth middleware

Closes #11426

AI-assisted-by: qwen3.7-plus
2026-09-14 13:26:16 +02:00
Andrey Antukh
5931f60d53
🐛 Fix workspace crash when resolving thumbnail data URIs (#11563)
The workspace-thumbnail-by-id ref unconditionally called resolve-media
on thumbnail URIs, which caused a stack overflow when the URI was a
data URI (which can be megabytes long for large images).

Data URIs contain thousands of '/' characters (base64 uses '/' as one
of its 64 characters), causing lambdaisland.uri/join to iterate
thousands of times in remove-dot-segments and overflow the JavaScript
call stack.

Add a resolved-uri? helper that checks if the URI already starts with
'blob:' or 'data:', and skip resolve-media for those cases. Only call
resolve-media when the URI is a plain UUID (media-id from the server).

Closes #11562

AI-assisted-by: qwen3.7-plus
2026-09-14 13:25:31 +02:00
Andrey Antukh
8128e350c5
Add expires-in TTL option to demo profile creation (#11574)
*  Add expires-in option to create-demo-profile

Allow passing an optional expires-in duration when creating a demo profile so its purge is scheduled sooner than the global deletion delay. Values below 5 minutes or above the global delay are rejected with an invalid-expires-in validation error, resolved before any profile is created. 

Closes #11573 

AI-assisted-by: muse-spark-1.3-contributor

* 🐛 Make duration schema decoding total instead of throwing

parse-duration returned by the duration schema decoder threw DateTimeParseException on invalid strings, escaping params validation as a raw error. It now returns the input unchanged so invalid values fail the duration predicate with a clean params-validation error. Closes #11573 AI-assisted-by: muse-spark-1.3-contributor

* 📎 Fix doc version for expires-in change entry

The expires-in change entry was documented under 2.20 but the current version is 2.18. 

AI-assisted-by: muse-spark-1.3-contributor
2026-09-14 13:24:38 +02:00
Andrey Antukh
8629dc6b2c
🔥 Remove unused comment profiles code in the frontend (#11624)
* 🔥 Remove unused comment profiles fan-out in dashboard

The dashboard event retrieve-unread-comment-threads issued one
get-profiles-for-file-comments call per distinct file id and stored
the result in :current-team-comments-users, a state key no one
reads. The dashboard renders each thread author from the thread
payload via get-owner, so the fan-out was N+1 dead work.

Drop the per-file branch and the state write; the event now issues
a single get-unread-comment-threads call.

AI-assisted-by: deepseek-flash

* 🔥 Remove unused file comments users event and state

fetch-file-comments-users had no callers and passed a :team-id
where get-profiles-for-file-comments requires :file-id. Its only
effect was writing :file-comments-users, a viewer state key no
one reads.

Remove the event and the unused state key. The viewer still
loads comment profiles through fetch-profiles.

AI-assisted-by: deepseek-flash
2026-09-14 13:23:44 +02:00
Elenzakaleidos
8ae188ad0b
💄 Update README.md (#11665)
Updated the description of Penpot Enterprise and added an image.

Signed-off-by: Elenzakaleidos <elena.scilinguo@kaleidos.net>
2026-09-14 12:12:29 +02:00
Andrey Antukh
e5f375edbc Disable newsletter telemetry fallback on official hosts
Skip the limited newsletter report when the public-uri host
belongs to penpot.dev or penpot.app, so the SaaS never sends
subscriber emails to its own telemetry endpoint.

Defer the subscriptions query with delay so it only runs when
a report is actually going to be sent.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-14 07:29:34 +00:00
Andrey Antukh
37f7ba4833 📚 Note general subagent delegation in flows intro
Delegating plan and review to a subagent (engineer-* or the
builtin general) starts a clean context instead of growing the
main session. Delegating to general keeps the same model.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-11 15:44:15 +00:00
Andrey Antukh
ed4367a782 📎 Add missing optional system prompt file for enginer agent 2026-09-11 17:39:57 +02:00
Andrey Antukh
74fd3ac8c3 📚 Restructure .agents README as agentic devenv guide
Turn the skills-only file into a full intro to opencode inside
plain devenv: setup, providers, models, opencode.json example,
gh auth, flows, and a skills summary at the end.

Provider, model, and flow sections follow Andrey's own setup
notes; the FAQ stays as a stub for later.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-11 15:34:29 +00: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
Alonso Torres
b598d7d72e
🐛 Fix problem in plugins api when removing interactions (#11621) 2026-09-11 13:19:55 +02:00
Andrey Antukh
06239844b1
🐛 Fix chunked upload storage amplification and cap chunk size (#11635)
* 🐛 Reject duplicate chunk index in chunked uploads

Repeat uploads of the same chunk index each stored a new
object because upload-chunk only checked index bounds. Run the
handler in a transaction, lock the session row and reject an
already-stored index with :duplicate-chunk-index.

Also harden assemble-chunks to require exactly indices 0..n-1
 so gaps or duplicates fail instead of assembling a corrupt
file. Covers media, fonts and binfile through the shared
helper.

Closes #11634

AI-assisted-by: muse-spark-1.3-contributor

*  Cap upload chunk size at 30 MiB by default

Chunks were only bounded by the 350 MiB HTTP body limit while the
30 MiB caps applied to the assembled file. Add :upload-max-chunk-size
(default 30 MiB, tunable via env) and reject oversize chunks in
upload-chunk with :validation/:chunk-too-large before anything is
stored. App clients slice at 25/10 MiB, so no frontend change needed.

AI-assisted-by: muse-spark-1.3-contributor

* 🐛 Fix tx-run! call and storage resolve in upload-chunk

Pass cfg as first arg to db/tx-run!, which expects [system f & params; without it every chunk upload raised invalid system/cfg provided and no chunk was stored, breaking assemble with missing-chunks. Also resolve storage without reuse-conn: put-object! writes to the backend outside any transaction, so reusing the tx connection gives no atomicity. Media, font and storage suites green, lint and format clean. AI-assisted-by: muse-spark-1.3-contributor
2026-09-11 12:10:57 +02:00
Andrey Antukh
09736aa4c9 Enforce commit body line wrapping
Add a body line-length validator to scripts/check-commit. It
fails when a body line exceeds 76 characters, exempting
trailers, URLs, and unbreakable tokens. The 76 limit leaves
room for git log's four-space indent in an 80-column
terminal.

Align the subject limit with the documented 70 characters;
the checker allowed 90 before.

Document the rule as a hard, verifiable requirement in
AGENTS.md, CONTRIBUTING.md, the create-commit skill, and
the workflow memory, and point at scripts/check-commit.

Add tests for the validator and the subject length rule.

AI-assisted-by: deepseek-flash
2026-09-11 08:10:49 +00:00
Andrey Antukh
f9c02926b9 Merge remote-tracking branch 'origin/main' into staging 2026-09-10 20:21:41 +02:00
bameda
bae3900537 ♻️ Rebalance CI runners and drop pinned ubuntu-24.04
Move build-docker and build-docker-devenv jobs from penpot-extended-runner
to penpot-standar-runner, point tests-exporter at the canonical
penpot-extended-runner label instead of the stale penpot-runner-02 alias,
and switch build-tag/release notify jobs from ubuntu-24.04 to ubuntu-latest.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-10 19:23:13 +02:00
bameda
757a5bd479 ♻️ Rebalance CI runners and drop pinned ubuntu-24.04
Move build-docker and build-docker-devenv jobs from penpot-extended-runner
to penpot-standar-runner, point tests-exporter at the canonical
penpot-extended-runner label instead of the stale penpot-runner-02 alias,
and switch build-tag/release notify jobs from ubuntu-24.04 to ubuntu-latest.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-10 19:22:46 +02:00
Andrey Antukh
8952d70fd2
Optimize get-profiles-for-file-comments query (#11622)
Rewrite sql:file-comment-users to join comment with
comment_thread and union the requesting profile id, then
join the resulting small id set against profile.

The previous "id IN (subquery) OR id = ?" forced a
sequential scan over the whole profile table with a hashed
subplan filter, taking ~1.9s on large instances. The
semi-join lets the planner use profile_pkey, dropping the
query to sub-millisecond time. UNION (not UNION ALL) keeps
the previous dedup semantics when the requesting profile is
also a commenter.

AI-assisted-by: deepseek-flash
2.18.0-RC5
2026-09-10 16:45:22 +02:00
Andrey Antukh
4ce459d720
🐛 Escape LDAP filter values and use directory email in retrieve-user (#11085)
Fix LDAP injection vulnerability (T5-N1-03) where the client-supplied email was used directly in the LDAP search filter without escaping RFC 4515 special characters (*, (, ), \, NUL), and the profile email was taken from client input instead of the LDAP directory attribute.

Changes:
- Add escape-ldap-filter-value per RFC 4515 section 3
- Apply escaping in search-user before building LDAP filter
- Add get-attr helper for multi-valued LDAP attributes
- Fix retrieve-user to use directory email (attrs-email) instead of client email
- Use cuerdas.core instead of clojure.string

Closes #11084

AI-assisted-by: mimo-v2.5-pro
2026-09-10 16:39:35 +02:00
Luis de Dios
c589563912
♻️ Replace digit with number in password validations (#11609) 2026-09-10 12:15:58 +02:00
andrés gonzález
0eb3179016
💄 Adjust release notes 2.18 titles (#11608) 2.18.0-RC4 2026-09-10 11:50:57 +02:00
Andrey Antukh
d1ebf4cda2 📎 Update changelog 2026-09-10 10:41:10 +02:00
Andrey Antukh
fdb9e97572 📎 Update planner skill and AGENTS.md 2026-09-10 10:12:09 +02:00
Elenzakaleidos
9cd3b63eea
📚 Update README.md (#11602)
Added a new section for Penpot Enterprise detailing its features and benefits for organizations.

Signed-off-by: Elenzakaleidos <elena.scilinguo@kaleidos.net>
2.18.0-RC3
2026-09-10 09:52:06 +02:00
David Barragán Merino
94555c027e 🔧 Sync .github/workflows with develop
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-10 09:47:10 +02:00
Juan de la Cruz
b283d952a8
Add new slides content for 2.18 release (#11222)
*  Add new slides content for 2.18 release

* ♻️ Use buttons from DS

* ♻️ Use new SCSS guidelines

* ♻️ Use a base stylesheet for all version files

* ♻️ Use new SCSS guidelines

*  Add new images and wording

---------

Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
2026-09-10 09:35:34 +02:00
Alejandro Alonso
ac84557740
🐛 Re-upload WASM text after WebGL context restore (#11589)
During reload-renderer!, reloading? keeps initialized?/ready? false
while set-objects runs (especially the sync path for small files).
Text content used that guard and was skipped; geometry already used
live?. Gate use-shape, has-shape, and set-shape-text-content on
wasm/live? so text is restored with the rest of the shapes.
2.18.0-RC2
2026-09-09 17:25:18 +02:00
Andrey Antukh
43f7e49aa0 📎 Update changelog 2026-09-09 11:48:37 +02:00
Andrey Antukh
eca1d81692 🔧 Remove legacy pnpm build key and clarify updating doc
Drop the ignored-since-pnpm-11 onlyBuiltDependencies entry from
render-wasm/pnpm-workspace.yaml, keeping allowBuilds as the single
source of build approvals. Clarify the updating-pnpm gotcha so it no
longer claims pnpm writes ignoredBuiltDependencies.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-09 11:45:00 +02:00
Pablo Alba
d263c23a58
🐛 Add ssrf check for nitrate sso and add timeouts to http client (#11576) 2026-09-09 11:23:53 +02:00
Andrey Antukh
8a3540336b 📚 Add local-ci skill and scripts/ci memory
Teach agents to verify their changes with ./scripts/ci: module list,
task selection flags, log locations under .ci-logs/, and per-module
workflows (lint-only pass, --fix, --paren-repair, common/ consumer
checks). Register the skill in the skills README, point AGENTS.md at
the new memory, and add the script to the critical-info dev scripts.

AI-assisted-by: omen-alpha
2026-09-09 11:01:49 +02:00
Andrey Antukh
6f4a68100d Add direct mode to implement-plan flow
The flow only supported continue mode (existing branch) or standalone
mode (issue + branch). Add a "direct" argument (aliases: "no branch",
"direct commit") that skips both and commits on the current branch as
it is, even when it is a base branch — for small tooling changes the
user wants committed in place.

AI-assisted-by: omen-alpha
2026-09-09 11:01:49 +02:00
esther-moreno
62fdc02c0f
📚 Adding enterprise section in the user guide (#10821)
* 📚 Add enterprise section

* 📚 Update images

* 📚 Add SSO module section to Enterprise plan user guide

* 📚 Add more FAQs to Enterprise plan user guide

* 📚 Add SSO configuration screenshot to Enterprise plan user guide

* 📚 Replace membership screenshot with webp to fix broken reference

The membership image reference already pointed to .webp while the tracked
file was .png, leaving a broken image. Swap the asset to match.

* 📚 Document how to access the Admin Console

Add a paragraph explaining how to reach the Admin Console and an
accompanying screenshot to the Enterprise plan user guide.

* 📚 Update Admin Console access screenshot

* 📚 Open Enterprise plan images in a new tab on click

Wrap the enterprise screenshots in a target="_blank" link so they open
full size, matching the behavior on other user-guide pages. The
organization hierarchy diagram is left inline.

* 📚 Document identity provider configuration for SSO

* 📚 Refine SSO session, Admin Console docs, and screenshots

* 📚 Split Organizations content out of the Enterprise plan page

* 📚 Fix wording in Enterprise plan FAQ answers

---------

Co-authored-by: miryamgduque <miryam.gonzalez@kaleidos.net>
2026-09-09 10:59:17 +02:00
Eva Marco
011feeaf71
🐛 Fix missing text in legacy SVG board thumbnails (#11552)
* 🐛 Fix missing text in legacy SVG board thumbnails

Board thumbnails rendered by frame-imposter used React's
renderToStaticMarkup, a synchronous pass with no live DOM to measure
text against. Text shapes without a persisted position-data value
therefore rendered as nothing, so a cached board thumbnail silently
lost its text until the board was hovered, selected, or the canvas
was zoomed past 130%, all of which bypass the cached thumbnail in
favor of live content.

frame-imposter now provides the same is-render? context the
standalone exporter already sets, so text without position-data
falls back to the synchronous foreignObject renderer instead of
rendering nothing.

Thumbnails cached before this fix stay broken until something
regenerates them, so on each page load, board thumbnails containing
text are opportunistically regenerated once per browser (tracked via
local-storage) so existing files self-heal without requiring an
edit.

AI-assisted-by: claude-sonnet-5

* ♻️ Use a transducer in heal-stale-text-thumbnails

Fixes a PR review comment: the frame filtering/mapping was spread
across four separate rx operators. Collapse it into a single
transducer pass over frame-ids, leaving only one rx/map to perform
the mark-healed side effect and build the update-thumbnail action.
2026-09-09 09:56:10 +02:00
Andrey Antukh
e96a75d366 Send telemetry event batch as plain JSON vector
The event batch sent to the telemetry server was encoded as a
fressian+zstd base64 blob. Send it as a plain vector of event maps
instead: the JSON encoder handles UUID and temporal types natively,
the payload becomes inspectable, and the receiver schema coerces
values back to proper types.

The receiver (penpot-telemetry) now accepts both the blob and the
plain vector, so it must be deployed before this backend change.

AI-assisted-by: omen-alpha
2026-09-09 09:39:30 +02:00