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
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
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
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
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>
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>
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
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
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
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
* ✨ 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
* 🔥 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
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
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
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
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
* 🐛 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
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
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>
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>
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
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
Added a new section for Penpot Enterprise detailing its features and benefits for organizations.
Signed-off-by: Elenzakaleidos <elena.scilinguo@kaleidos.net>
* ✨ 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>
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.
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
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
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
* 📚 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>
* 🐛 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.
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