* ✨ Add size limits to profile props and plugin registry
Bound the total serialized size of profile settings to 2 MiB
(:profile-props-max-size), checked on the merged result before
persisting, with a controlled :props-too-large error. Profiles
that already exceed the limit can still shrink but cannot grow.
Cap plugin registry entries in the shared schema (code 1 MiB, 50
plugins max, bounded name/host/description/icon) and restore rate
limiting on the plugin RPCs (profile-mutations bucket, one write
at a time per profile). The plugin manager now asks for
confirmation before removal and ignores repeated clicks while a
persist request is in flight.
Closes#11592
AI-assisted-by: muse-spark-1.3-contributor
* 🐛 Enforce plugin count cap, byte sizes and removal guard
Enforce the declared 50-plugin cap in add-profile-plugin with a
specific :too-many-plugins error (updates of existing entries
still pass); the cap lives in a shared max-plugins constant.
Measure profile props size in UTF-8 bytes instead of chars so
multibyte content cannot slip past the limit.
Cover install/remove persist logic with mocked-RPC frontend tests
(release semantics, in-flight dedupe, validation vs rollback
split) and add the missing boundary tests in common.
Expose the in-flight persist set from the plugin registry and
disable the remove button of entries being saved.
Closes#11592
AI-assisted-by: muse-spark-1.3-contributor
* 🐛 Fix rollback loops and restore paths in plugin registry
Restore the previous plugin version instead of dropping the entry
when a validation error rejects an update of an installed plugin.
Make compensating writes one-shot with terminal callbacks so a
persistent failure cannot ping-pong between install and remove.
Restores keep the original list position; the unused public
plugin-persisting? predicate is removed.
Pin count-before-size precedence with a dedicated test and fix
translation source refs to their canonical lines.
Closes#11592
AI-assisted-by: muse-spark-1.3-contributor
* 🐛 Guard notifications write and fix restore ordering
Route update-profile-notifications through check-props-size! so
oversized profiles cannot grow through that path; document the
exempt system writers. Remove the duplicated stale entries in
en.po, keeping the canonical translation refs.
Restore rejected plugin updates at their original list position
instead of leaving the optimistic move in place.
Closes#11592
AI-assisted-by: muse-spark-1.3-contributor
* 🐛 Skip no-op plugin removal and clarify size comments
Return early from remove-profile-plugin when the id is absent:
no wasted write, no size check, and no manufactured :plugins key
that could spuriously fail on oversized profiles.
Clarify that per-field string caps count chars while the byte
budget is enforced by profile-props-max-size.
Closes#11592
AI-assisted-by: muse-spark-1.3-contributor
* 📎 Fix formatting in rlimit.edn for profile operations
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* 📎 Fix formatting of import-binfile/global entry
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* ♻️ Simplify props size check and tighten plugin entry caps
Measure props with transit bytes directly instead of the
PGobject string roundtrip.
Rename check-props-size! to check-props-size: single hard limit
on the merged props, no growth comparison, and return props so
writers thread the check into the update.
Move the 2 MiB default into default-props-max-size on the
profile namespace, still overridable with the optional
:profile-props-max-size config entry.
Tighten registry-entry :code and :icon to 500 chars: they hold
manifest paths, not content.
Closes#11592
AI-assisted-by: muse-spark-1.3-contributor
* 🐛 Fix compatibility problems
---------
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
Drop 277 keys nothing references from en.po (verified against
frontend/src and common/src) and let sync propagate the
deletions to every locale. Clear all 10 fuzzy entries: fill
the 5 empty translations, keep the 4 valid ones, and drop the
duplicated max-quote-reached in favor of max-quota-reached
(the backend code stays, the UI maps it to the quota text).
Recover 22 used-but-missing keys with translations: the 19
shortcuts section/subsection labels plus connected-to,
pixel-grid-color and tokens.add-set. Make the rest
statically visible to rehash instead: :label fns on shortcut
commands, sections and subsections (one debug-only and one
colorpicker-local id exempt); case branches in place of
dm/str-built keys (export modal, text decoration and
transform, undo history with raw-key fallback); hoist
conditionals out of tr calls; pre-translate modal props and
role labels; replace the lone (i18n/tr ...) site with tr.
Turn static :error/code data into eager :error/fn calls in
the common schemas and the auth/password forms. Rename the
two keys containing spaces and point team leave at
max-quota-reached. Backend-driven keys stay dynamic by
design, declared with (tr ...) comments: the five
weak-password details, team and organization notifications.
Tooling: rehash also scans common/src and no longer treats
a missing -l as no locale; new clj-kondo tr-dynamic warning
flags non-literal tr args (lint scripts use --fail-level
error so it never fails CI); tr docstring states the
literal-only rule. Tests cover the shortcut label wiring,
the undo-history fallback and the :error/fn schemas.
Translations memory rewritten to match; es check word list
gains three entries.
Rebased onto develop: adopt the register field-error UX
(the weak-password declarations move onto the :options
code), keep develop's newer keys (connection-error,
account-locked, save-retrying, tokens-source strings) with
fresh references, and reword the shortcuts.cljs prose
comment so rehash does not invent a "literal" key.
AI-assisted-by: muse-spark-1.3-contributor
copyAssets uses rsync --delete which removes the sprites/ directory
from resources/public/images/ because it only exists in public, not in
the source resources/images/ tree. compileSvgSprites must run AFTER
copyAssets so the generated sprites are not deleted before
compileTemplates reads them.
The initial build sequence (lines 65-70) already had the correct order;
the watch handlers in watch.js and watch-storybook.js were reversed.
* 🌐 Complete Catalan translations in frontend
Complete the Catalan (ca.po) locale to 100% coverage against en.po,
using es.po as support reference. Adds the 1439 missing entries
across workspace, dashboard, labels, shortcuts, subscription,
errors, modals and onboarding, keeping vosaltres treatment and
IEC/Termcat terminology consistent with the existing strings.
Normalizes placeholders and plural forms, drops the 14 stale
obsolete entries and canonicalizes the file with the repo
translations script.
Closes#11739
AI-assisted-by: muse-spark-1.3-contributor
* 📚 Add frontend translations memory with Catalan criteria
Record the PO workflow, the sync fuzzy-flag gotcha and the
Catalan glossary and tone agreed upon while completing ca.po,
and link the new memory from the frontend core routing.
AI-assisted-by: muse-spark-1.3-contributor
* 🔧 Add gettext to devenv image
Provide msgfmt and msgattrib in the dev environment for
checking PO translation files.
AI-assisted-by: muse-spark-1.3-contributor
* 🌐 Fix Catalan translations and add PO checker
Review of the missing-whitespace pattern found ~90 glued words
across 75 entries, plus 4 lost plural forms and 2 placeholder
mismatches verified against tr call sites. All fixed in ca.po.
Adds frontend/scripts/check-translations.js (vocabulary-free PO
QA: glued words, punctuation, placeholders, plurals) with
--self-test, wired as pnpm run check-translations and
documented in mem:frontend/translations.
AI-assisted-by: muse-spark-1.3-contributor
* 🌐 Multi-locale PO checker with word catalogs
Split the checker engine from its word lists: ca/es catalogs now
live in scripts/check-translations/words.<locale>.txt and all
messages are in English. Adds an es seed (calibrated to zero
errors) and fixes 7 typos it found in es.po. Universal checks
(placeholders, plurals, punctuation) run without a catalog.
AI-assisted-by: muse-spark-1.3-contributor
* 🌐 Merge PO checker into translations.js
Fold check-translations.js into translations.js as a check
subcommand reusing its locale helpers; word lists stay in
scripts/check-translations/words.<locale>.txt. Also fixes the
getopts stopEarly bug that made -l useless after the command
(sync -l ca synced every locale), drops dead lodash import
and code, unifies help and exit codes. Removes the
check-translations package alias; use translations.js
check -l <locale> with explicit -l.
AI-assisted-by: muse-spark-1.3-contributor
* 🌐 Keep unused placeholders out of the gate
Reverts the %s-stripping on unused auth.terms-privacy-agreement:
the links mirror its markdown sibling and a reactivation may
need them. Placeholder mismatches on #, unused keys now warn
instead of failing, and the rule is recorded in
mem:frontend/translations.
AI-assisted-by: muse-spark-1.3-contributor
* ⬆️ Update devenv dependencies
Update Node.js, OpenCode, clj-kondo, Babashka, Pixi, GitHub CLI, uv,
and Serena to their current stable releases.
AI-assisted-by: gpt-5.6-sol
* ⬆️ Update devenv to Java 27
Use Zulu JDK 27 in the development image for compatibility testing.
Update the official checksums for both supported architectures.
AI-assisted-by: gpt-5.6-sol
* 🐳 Replace MinIO with RustFS in devenv
Run RustFS as the development S3 service and wait for its health check.
Install a pinned AWS CLI with checksums and use it to create the bucket
idempotently from each backend entry point.
Keep the old MinIO volume untouched and use a new RustFS volume.
AI-assisted-by: gpt-5.6-sol
* 🐳 Replace MailCatcher with persistent Mailpit
Run Mailpit as the devenv SMTP sink while preserving mailer:1025 and the
localhost:1080 UI.
Store its SQLite inbox in a named volume and wait for the readiness
endpoint before starting runtime containers. Bind the web UI to loopback so
development emails stay local.
AI-assisted-by: gpt-5.6-sol
* ⬆️ Update Node.js to 24.21.0
Align the host NVM version with the Node.js version used by devenv.
AI-assisted-by: gpt-5.6-sol
* ⬆️ Update devenv to PostgreSQL 18.6
Run PostgreSQL 18 with its versioned volume layout and a TCP readiness
check that ignores the temporary initialization server.
Install the matching client, create penpot_nexus, and preserve the old
PostgreSQL 16 volume for rollback or logical migration.
AI-assisted-by: gpt-5.6-sol
* 🐳 Expose RustFS ports in devenv
Publish the RustFS S3 API and management console on localhost port 9000
and 9001.
Keep both bindings on loopback so object storage is not exposed to the local
network.
AI-assisted-by: gpt-5.6-sol
* 🐳 Install standalone pnpm in devenv
Install pnpm 12.5.0 from architecture-specific release archives and
verify their published checksums.
Remove the Corepack setup while allowing pnpm to honor the project
packageManager pins.
AI-assisted-by: gpt-5.6-sol
* 🔥 Remove corepack, use system pnpm everywhere
Corepack is gone from Node 25+, so every `corepack enable` call
fails. pnpm now ships as a system binary (devenv, CI runners and
Docker images install it directly) and auto-downloads the version
pinned in `packageManager` on mismatch.
Scripts, workflows and Dockerfiles call `pnpm` straight away; the
three deploy workflows use a single `pnpm/setup@v2` step; and the
new `scripts/sync-pnpm-version` stamps all 35 `packageManager`
fields from the system pnpm, replacing the `corepack use` sweep.
AI-assisted-by: muse-spark-1.3-contributor
* 🐛 Fix exporter watch missing render-wasm build step
The exporter watch compiled CLJS requiring the generated
src/app/wasm/shared.js, which only render-wasm/build export
produces. Without it shadow-cljs failed with a cryptic missing
./shared.js dependency. Run build:wasm before watching, as
the frontend watch:app and exporter scripts/build already do.
AI-assisted-by: muse-spark-1.3-contributor
* 🔧 Add opencode V2 support and adapt plugins
Register the penpot tools for both opencode V1 (server())
and V2 (setup() with JSON Schema inputs) from a single
dependency-free plugin file, sharing the psql and
paren-repair runners between both paths.
Install the opencode2 binary side-by-side with V1 in the
devenv image and document the dual registration in the
paren-repair and psql memories.
AI-assisted-by: muse-spark-1.3-contributor
* ⬆️ Update pnpm and opencode
The frontend build now emits the sha256 hashes of the inline scripts of every page it writes into resources/public, the image moves them out of the document root, and the entrypoint splices them into the default script-src. This removes one of the two reasons why enforcing mode was not usable.
The hashes are computed on the rendered output rather than on the mustache templates, since the digest covers the exact bytes served between the script tags. All four served pages contribute, not just index.html: challenge.html handles the redirect, render.html is loaded by the exporter in a headless browser, and rasterizer.html is initialised by the frontend itself, so leaving any of them out would have broken those paths under enforcing mode. The storybook previews are excluded because that container does not serve them.
A bundle predating this change yields no hashes and the policy stays as it was, so older bundles keep building.
The three external locations were also passing through the security headers of their upstreams. raw.githubusercontent.com returns its own Content-Security-Policy and both it and fonts.googleapis.com return Strict-Transport-Security. Browsers enforce the intersection of every policy they receive, so the upstream one takes precedence on those responses, and the HSTS one lands on our own host, meaning a deployment that deliberately disables HSTS would get it set anyway by a third party. Hide all three at the proxy.
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
Enable Playwright's JSON reporter alongside `list` and publish a
summary of flaky tests to the job step summary. The JSON report is
kept as an artifact for 30 days so flakiness rates can be aggregated
over time.
CI already runs with `retries: 2`, so unstable tests have been passing
silently on retry. This only surfaces what the suite already absorbs;
no test behaviour changes.
The reporter in `frontend/scripts/test-e2e` becomes overridable via
`PLAYWRIGHT_REPORTER` so the local developer default stays untouched.
Enable Playwright's JSON reporter alongside `list` and publish a
summary of flaky tests to the job step summary. The JSON report is
kept as an artifact for 30 days so flakiness rates can be aggregated
over time.
CI already runs with `retries: 2`, so unstable tests have been passing
silently on retry. This only surfaces what the suite already absorbs;
no test behaviour changes.
The reporter in `frontend/scripts/test-e2e` becomes overridable via
`PLAYWRIGHT_REPORTER` so the local developer default stays untouched.
* ✨ Add headless wasm render backend to the exporter
* ♻️ Move render-wasm bridge to common and split wasm builds
* 🔧 Upload builtin font variants in the wasm exporter
* ♻️ Move shared font and resources utils out of render_wasm
* ⚡ Fetch only the exported roots in the wasm exporter
* ⚡ Bound save_layer rects in the vector export path
* 🎉 Install react-aria-components
* 🎉 Create modal component in TS using react-aria-component
* 🎉 Create modal ds component
* 🎉 Separate header content and footer components
* 🐛 Remove mf/html macros when not needed
* 🎉 Solve little problems
* ♻️ Format files
* ♻️ Remove ModalCloseBtn
* 🐛 Fix CI
* ♻️ Remove unused files
* 🎉 Make close button not dependant on the modal header
* 🎉 Add footer with two slots
* 🎉 Improvements on modal
* 🐛 Fix package imports and remove login example code
---------
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
- Remove penpotWorkerURI from index.mustache and rasterizer.mustache templates
- Remove worker_main entry from the build manifest
- Construct worker URI in config.cljs by joining public-uri with worker path
- Fix global variable casing for plugins-list-uri and templates-uri
- Fix alignment in worker.cljs let bindings