Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2026-05-15 11:58:06 +02:00
commit d620c86053
29 changed files with 1741 additions and 316 deletions

View File

@ -0,0 +1,230 @@
---
name: gh-issue-from-pr
description: Create a user-facing GitHub issue from a PR, separating the WHAT from the HOW, with correct milestone, project, labels, and issue type.
---
# Skill: gh-issue-from-pr
Create a GitHub issue that captures the **WHAT** (user-facing feature or
bug) from an existing PR that describes the **HOW** (implementation).
Used when the project board needs an issue as the primary changelog/release unit.
## When to Use
- Create a tracking issue from a PR for changelog purposes
- Extract the user-facing problem/feature from a PR's implementation details
- Assign milestone, project, labels, and issue type to a new issue derived from a PR
## Prerequisites
- `gh` CLI authenticated (`gh auth status`)
- Permission to create issues and edit PRs in the target repository
## Workflow
### 1. Understand the PR
```bash
gh pr view <PR_NUMBER> --repo penpot/penpot \
--json title,body,author,labels,baseRefName,mergedAt,state,milestone
```
Identify:
- **WHAT** — user-facing problem or feature. Goes into the issue.
Describe symptoms and impact, not internal mechanisms.
- **HOW** — implementation details. These belong in the PR, not the issue.
### 2. Determine metadata
| Field | Source | Rule |
|-------|--------|------|
| **Title** | PR title | Rewrite from user perspective. Strip leading emoji prefixes (`:bug:`, `:sparkles:`, `:tada:`). Focus on observable behavior. Use imperative mood. |
| **Labels** | PR labels | Copy user-facing labels (`bug`, `enhancement`, `community contribution`). Skip workflow labels (`backport candidate`, `team-qa`). |
| **Milestone** | PR milestone | **Always copy what's on the PR.** Fetch with: `gh pr view <PR_NUMBER> --json milestone --jq '.milestone.title'` If the PR has no milestone, create the issue without one. |
| **Project** | Always `Main` | Penpot uses the `Main` project (number 8) for all issues. |
| **Body** | PR's user-facing section | Extract steps to reproduce or feature description. Omit internal details. Use templates below. |
| **Issue Type** | PR labels / title | Map: `bug` label or `:bug:` title → `Bug`. `enhancement` label or `:sparkles:` title → `Enhancement`. Feature/epic → `Feature`. Default → `Task`. |
### 3. Write the issue body
**Bug template:**
```markdown
### Description
<what breaks, what the user experiences>
### Steps to reproduce
1. <step 1>
2. <step 2>
### Expected behavior
<what should happen instead>
### Affected versions
<version>
```
**Enhancement template:**
```markdown
### Description
<what the user can now do that they couldn't before>
### Use case
<why this is useful, who benefits>
### Affected versions
<version>
```
### 4. Create the issue
Write the body to a temp file to avoid shell quoting issues:
```bash
cat > /tmp/issue-body.md << 'ISSUE_BODY'
<body content here>
ISSUE_BODY
```
Create:
```bash
gh issue create \
--repo penpot/penpot \
--title "<Title>" \
--label "<label1>" \
--label "<label2>" \
--milestone "<milestone>" \
--project "Main" \
--body-file /tmp/issue-body.md
```
Output: `https://github.com/penpot/penpot/issues/<NUMBER>`
### 5. Assign to the PR author
Assign the issue to the PR author so they're responsible for it:
```bash
AUTHOR=$(gh pr view <PR_NUMBER> --repo penpot/penpot --json author --jq '.author.login')
gh issue edit <ISSUE_NUMBER> --repo penpot/penpot --add-assignee "$AUTHOR"
```
### 6. Set the Issue Type
`gh issue create` can't set the Issue Type directly. Use GraphQL.
Get the issue's GraphQL node ID:
```bash
ISSUE_ID=$(gh api graphql -f query='
query { repository(owner: "penpot", name: "penpot") {
issue(number: <ISSUE_NUMBER>) { id }
}}' --jq '.data.repository.issue.id')
```
Issue Type IDs for the Penpot repo:
| Type | ID |
|------|----|
| Bug | `IT_kwDOAcyBPM4AX5Nb` |
| Enhancement | `IT_kwDOAcyBPM4B_IQN` |
| Feature | `IT_kwDOAcyBPM4AX5Nf` |
| Task | `IT_kwDOAcyBPM4AX5NY` |
| Question | `IT_kwDOAcyBPM4B_IQj` |
| Docs | `IT_kwDOAcyBPM4B_IQz` |
Set it:
```bash
gh api graphql -f query='
mutation {
updateIssue(input: {
id: "'"$ISSUE_ID"'"
issueTypeId: "<TYPE_ID>"
}) {
issue { number issueType { name } }
}
}'
```
### 7. Verify
```bash
gh issue view <ISSUE_NUMBER> --repo penpot/penpot \
--json title,milestone,projectItems,labels \
--jq '{title, milestone: .milestone.title, projects: [.projectItems[].title], labels: [.labels[].name]}'
gh api graphql -f query='
query { repository(owner: "penpot", name: "penpot") {
issue(number: <ISSUE_NUMBER>) { issueType { name } }
}}' --jq '.data.repository.issue.issueType.name'
```
### 8. Link the PR to the issue
Append `Fixes #<ISSUE_NUMBER>` to the PR body:
```bash
gh pr view <PR_NUMBER> --repo penpot/penpot --json body --jq '.body' > /tmp/pr-body.md
printf "\n\nFixes #<ISSUE_NUMBER>\n" >> /tmp/pr-body.md
gh pr edit <PR_NUMBER> --repo penpot/penpot --body-file /tmp/pr-body.md
# Verify
gh pr view <PR_NUMBER> --repo penpot/penpot --json body \
--jq '.body | test("Fixes #<ISSUE_NUMBER>")'
```
**Note:** If the PR is already merged, `Fixes` won't auto-close the issue
— it only creates the "Development" sidebar link. This is the desired
behavior since the issue is a tracking artifact.
### 9. Clean up
```bash
rm -f /tmp/issue-body.md /tmp/pr-body.md
```
## Label rules
| PR has | Issue gets |
|--------|-----------|
| `bug` | `bug` |
| `enhancement` | `enhancement` |
| `community contribution` | `community contribution` |
| `backport candidate` | *(skip — workflow label)* |
| `team-qa` | *(skip — workflow label)* |
| No user-facing label | Infer from title: `:bug:``bug`, `:sparkles:``enhancement` |
## Issue Type mapping
| PR label(s) / title prefix | Issue Type |
|----------------------------|-----------|
| `bug` or `:bug:` | Bug |
| `enhancement` or `:sparkles:` or `:tada:` | Enhancement |
| Feature / epic | Feature |
| Documentation | Docs |
| None of the above | Task |
## Key Principles
- **Issue = WHAT, PR = HOW.** Never put implementation details in the
issue body. The issue is for users, QA, and changelog readers.
- **Copy the milestone from the PR.** Don't guess based on branch names.
If the PR has no milestone, create the issue without one.
- **Set Issue Type via GraphQL**`gh issue create` can't set it.
- **Link via PR body**`Fixes #<NUMBER>` creates the "Development"
sidebar link automatically.
- **One issue per PR** — even if a PR fixes multiple things, create a
single issue that summarizes the overall change.
- **Community attribution:** if the PR has the `community contribution`
label or the author is not a core team member, add the label to the issue.

View File

@ -0,0 +1,110 @@
---
name: taiga
description: Fetch information from Taiga public API for the Penpot project (id 345963) — issues, user stories, and tasks, without authentication.
metadata: {"clawdbot":{"requires":{"bins":["python3"]}}}
---
# Taiga API Skill
Fetch information from Taiga public API for the **Penpot** project
(project id: `345963`, slug: `penpot`).
**No authentication required** — only public project data is accessed.
## Prerequisites
- `python3` — the `tools/taiga.py` CLI script is self-contained (stdlib only)
## Quick Start
The easiest way is to use the bundled Python script:
```bash
# Pass a Taiga URL directly
python3 tools/taiga.py https://tree.taiga.io/project/penpot/issue/13714
# Or use "<type> <ref>" syntax
python3 tools/taiga.py us 14128
python3 tools/taiga.py task 13648
# Add --json for raw output
python3 tools/taiga.py --json issue 13714
# See full usage
python3 tools/taiga.py --help
```
## URL Pattern Reference
Taiga web URLs follow these patterns:
| Type | Web URL Pattern |
|------|----------------|
| Issue | `https://tree.taiga.io/project/penpot/issue/<REF>` |
| User Story | `https://tree.taiga.io/project/penpot/us/<REF>` |
| Task | `https://tree.taiga.io/project/penpot/task/<REF>` |
To extract the **type** and **ref** from a URL:
- `issue/13714` → type=`issue`, ref=`13714`
- `us/14128` → type=`us`, ref=`14128`
- `task/13648` → type=`task`, ref=`13648`
## Python Script Reference
The `tools/taiga.py` script wraps the Taiga API into a single convenient CLI
with sensible defaults.
### Usage
```
python3 tools/taiga.py <taiga-url>
python3 tools/taiga.py <type> <ref>
python3 tools/taiga.py [--json] <taiga-url>
python3 tools/taiga.py [--json] <type> <ref>
```
### Examples
```bash
# By URL (recommended — no need to think about type/ref)
python3 tools/taiga.py https://tree.taiga.io/project/penpot/issue/13714
# By type and ref
python3 tools/taiga.py us 14128
python3 tools/taiga.py task 13648
# Raw JSON output
python3 tools/taiga.py --json issue 13714
```
### Output
The script prints a clean, structured summary:
```text
User Story #11964 — 🔴 [DESIGN TOKENS] Typography Composite Input
================================
Status: Defining
Milestone: design-systems-sprint-26
Points: 3 role(s)
Assignee: Natacha Menjibar
Author: Natacha Menjibar
Created: 2025-09-01
Tags: iop-design-tokens
URL: https://tree.taiga.io/project/penpot/us/11964
================================
<full description text, unmodified>
```
The fields section includes type-specific information:
- **Issues:** Status, Type ID, Severity ID, Priority ID
- **User Stories:** Status, Milestone, Points
- **Tasks:** Status, Milestone, Parent US
## Reference
- API docs: https://docs.taiga.io/api.html
- Taiga instance: https://tree.taiga.io
- API base: https://api.taiga.io/api/v1
- Penpot project id: `345963`
- Penpot project slug: `penpot`

View File

@ -0,0 +1,232 @@
---
name: update-changelog
description: Update the project CHANGES.md with issues from a given GitHub milestone, with correct categorization and references.
---
# Skill: update-changelog
Update `CHANGES.md` with entries for all issues and PRs in a given GitHub
milestone. Each entry references the user-facing issue (not the PR) as the
primary link, with the fix PR inline on the same line.
## When to Use
- Before a new release, to populate the changelog with all fixed issues
- When new issues are added to an existing milestone and the changelog needs
to be refreshed
- To ensure every entry follows the correct format for the changelog
## Prerequisites
- `gh` CLI authenticated (`gh auth status`)
- Python 3.8+
- `tools/gh.py` helper script available
## Workflow
### 1. Determine the target version
The version is typically a semver string like `2.15.3`. Confirm with the user
if not specified.
### 2. Fetch all issues in the milestone
Use the helper script. It uses GraphQL for efficient single-pass fetching
(closing PRs are included in the same query — no N+1):
```bash
# All closed issues (default)
python3 tools/gh.py issues "2.16.0"
# Include open issues too
python3 tools/gh.py issues "2.16.0" --state all
# Exclude entries that should not go in the changelog
python3 tools/gh.py issues "2.16.0" --exclude "release blocker,no changelog"
```
**Label exclusion rules:**
- `release blocker` — Internal release-blocking bugs not relevant to end users
- `no changelog` — Chore/refactor work that doesn't need a changelog entry
The script outputs JSON with each entry containing `number`, `title`, `state`,
`labels`, and `closing_prs` (the PRs that fix each issue).
### 3. Identify missing entries (optional)
If updating from an existing `CHANGES.md`, find issues in the milestone that
are NOT yet referenced in the changelog:
```bash
python3 tools/gh.py issues "2.16.0" --exclude "release blocker,no changelog" --compare CHANGES.md
```
This returns a filtered JSON array with only the missing issues.
### 4. Fetch additional PR details when needed
When you need more context for specific PRs (e.g. to find the PR author for
community contribution attribution, or to read the PR body for
"Fixes/Closes #NNN" patterns):
```bash
# One or more PR numbers
python3 tools/gh.py prs 9179 9204 9311
# From a file
python3 tools/gh.py prs --file prs.txt
# From stdin
cat prs.txt | python3 tools/gh.py prs --stdin
```
The `prs` command returns JSON with `number`, `title`, `body`, `state`,
`merged_at`, `author`, `labels`, and `closing_issues`. PRs are fetched in
batches of 50 via GraphQL to stay within API limits.
### 5. Categorize entries
Check the labels on each issue to determine which section it belongs to:
| Label / Title prefix | Changelog section |
|----------------------|-------------------|
| `bug` label or `:bug:` title prefix | `### :bug: Bugs fixed` |
| `enhancement` label or `:sparkles:` prefix | `### :sparkles: New features & Enhancements` |
| No label | Infer from title convention, default to bug fix |
**Community contribution attribution:** If the issue or its fix PR has the
`community contribution` label, add an attribution `(by @<github_username>)`
on the changelog entry line, **before** the GitHub issue/PR references.
The attribution should reference the **PR author**, not the issue author.
The `prs` subcommand includes the `author` field — use that:
```bash
python3 tools/gh.py prs <PR_NUMBER> | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['author'])"
```
Placement in the entry line:
```markdown
- Fix description of the bug (by @username) [#<ISSUE>](...) (PR: [#<PR>](...))
```
**Only closed issues are included.** An issue must have `state: "closed"` to
appear in the changelog. Open/unresolved issues are omitted, even if they are
tracked in the milestone.
**Pairing rules:**
| Pattern | Changelog format |
|---------|-----------------|
| Closed issue + one or more PRs fix it | Primary link = issue, PR inline comma-separated |
| PR exists with no linked issue | If a corresponding closed issue exists in the same milestone, link the issue. Otherwise, skip the entry (the issue must be the changelog unit). |
| Closed issue with no fix PR in milestone | Link the issue directly, without a PR reference. |
### 6. Read the current CHANGES.md
Read the top of `CHANGES.md` to understand the existing format and find the
insertion point (newest version goes at the top, after the `# CHANGELOG`
header).
Key format rules from the existing file:
```markdown
## <VERSION>
### :bug: Bugs fixed
- Fix description of the bug [#<ISSUE>](https://github.com/penpot/penpot/issues/<ISSUE>) (PR: [#<PR>](https://github.com/penpot/penpot/pull/<PR>))
- Fix another bug (by @contributor) [#<ISSUE>](https://github.com/penpot/penpot/issues/<ISSUE>) (PR: [#<PR>](https://github.com/penpot/penpot/pull/<PR>))
### :sparkles: New features & Enhancements
- Add new feature description [#<ISSUE>](https://github.com/penpot/penpot/issues/<ISSUE>) (PR: [#<PR>](https://github.com/penpot/penpot/pull/<PR>))
```
Format details:
- Entries start with `- ` followed by a short description in imperative mood
- Primary link is **always the issue** (user-facing artifact)
- PR references are inline on the same line: `(PR: [#<N>](<url>))`
If an issue has multiple fix PRs, they are comma-separated:
`(PR: [#<N>](<url>), [#<M>](<url>))`
- The description should describe the fix/feature from the user's perspective
- Community contributions get `(by @<username>)` **before** the issue link
- Sections are separated by a blank line between the last entry and the next
section title
- Only include a section if there are entries for it
- When an entry already exists in an earlier version section, it must be removed
from the current version to avoid duplicates
### 7. Build the description text
Derive the description from the issue title, not the PR title. Strip leading
emoji prefixes (`:bug:`, `:sparkles:`, `:tada:`) and focus on the
user-facing behavior.
Examples:
| Issue title | Changelog description |
|-------------|----------------------|
| `Plugin API token methods fail with schema validation error on PRO` | `Fix Plugin API token methods failing with schema validation error on PRO` |
| `Comment content is not sanitized before rendering, enabling stored XSS` | `Sanitize comment content on rendering` |
| `Custom uploaded font family names are not sanitized` | `Sanitize font family names on custom uploaded fonts` |
### 8. Insert the section into CHANGES.md
Insert the new version section right after the `# CHANGELOG` header (before
the previous version entry). Use the `edit` tool with enough context to make
a unique match.
### 9. Verify
Read the top of `CHANGES.md` and confirm:
- The version header is correct
- Every entry has a GitHub link
- Entries with a fix PR have the PR sub-line
- The section ordering is correct (newest first)
- Formatting matches the surrounding entries
## Version section template
```markdown
## <VERSION>
### :bug: Bugs fixed
- <fix description> [#<ISSUE>](https://github.com/penpot/penpot/issues/<ISSUE>) (PR: [#<PR>](https://github.com/penpot/penpot/pull/<PR>))
- <fix description> (by @contributor) [#<ISSUE>](https://github.com/penpot/penpot/issues/<ISSUE>) (PR: [#<PR>](https://github.com/penpot/penpot/pull/<PR>))
```
## Key Principles
- **Issue = changelog unit.** The primary link always points to the
user-facing issue, not the implementation PR.
- **PR = implementation detail.** Reference the PR inline so readers
can find the code changes.
- **Latest version first.** New sections are inserted at the top of the
changelog, below the `# CHANGELOG` header.
- **User-facing descriptions.** Write from the user's perspective — describe
what broke and what was fixed, not internal implementation details.
- **Community attribution.** When the issue or fix PR has the
`community contribution` label, add `(by @<username>)` on the entry line
between the description and the issue link. Use the **PR author** (not the
issue author) for the attribution.
- **Only closed issues.** An issue must have `state: "closed"` to appear in
the changelog. Open unresolved issues are omitted.
- **Excluded labels.** Issues with `release blocker` or `no changelog` labels
must be excluded from the changelog.
- **Multiple PRs per issue.** If multiple PRs fix the same issue, list them
comma-separated inline: `(PR: [#A](url), [#B](url))`.
- **Duplicate removal.** If an entry already exists in a prior version section,
remove it from the current version. Check for text-level duplicates (after
stripping links and attributions) across version sections.
- **Taiga references.** If a changelog entry references a Taiga URL
(`tree.taiga.io`), attempt to find a corresponding GitHub issue via the
Taiga description text or by searching GitHub PRs that reference the Taiga
URL. Replace the Taiga reference with the GitHub issue link and add the PR
reference if applicable.
- **Re-fetch before editing.** Milestones can change — always re-fetch issues
before making edits, don't rely on cached data.
- **Use `tools/gh.py`.** Prefer the helper script over raw `gh api` calls for
milestone issue listing and PR detail fetching. It handles GraphQL
pagination, batching, and label filtering automatically.

View File

@ -35,224 +35,239 @@
### :sparkles: New features & Enhancements
- Enhance readability of applied tokens in plugins API [Taiga #13714](https://tree.taiga.io/project/penpot/issue/13714)
- Add "Delete group" option to the assets panel context menu for components, colors and typographies (by @FairyPigDev) [Github #9141](https://github.com/penpot/penpot/issues/9141)
- Add `Alt+click` on a layer's disclosure arrow to recursively expand the entire subtree rooted at that layer in the Layers sidebar; symmetric with the existing `Shift+click` collapse-all gesture, and removes the O(siblings × depth) click cost of unfolding a deep subtree one level at a time [Github #7736](https://github.com/penpot/penpot/issues/7736)
- Show relative timestamp and short identifier for each entry in the workspace Actions history sidebar (by @FairyPigDev) [Github #7660](https://github.com/penpot/penpot/issues/7660)
- Add `Alt+click` on a layer's disclosure arrow to recursively expand the entire subtree in the Layers sidebar (by @MilosM348) [Github #9179](https://github.com/penpot/penpot/pull/9179)
- Show alpha percentage next to library color values to distinguish colors that differ only in opacity (by @rockchris099) [Github #6328](https://github.com/penpot/penpot/issues/6328)
- Add "Clear artboard guides" option to right-click context menu for frames (by @eureka0928) [Github #6987](https://github.com/penpot/penpot/issues/6987)
- Add loader feedback while importing and exporting files (by @moorsecopers99) [Github #9024](https://github.com/penpot/penpot/pull/9024)
- Allow duplicating color and typography styles (by @MkDev11) [Github #2912](https://github.com/penpot/penpot/issues/2912)
- Add woff2 support on user uploaded fonts (by @Nivl) [Github #8248](https://github.com/penpot/penpot/pull/8248)
- Import Tokens from linked library (by @dfelinto) [Github #8391](https://github.com/penpot/penpot/pull/8391)
- Option to download custom fonts (by @dfelinto) [Github #8320](https://github.com/penpot/penpot/issues/8320)
- Add copy as image to clipboard option to workspace context menu (by @dfelinto) [Github #8313](https://github.com/penpot/penpot/pull/8313)
- Add Tab/Shift+Tab navigation to rename layers sequentially (by @bittoby) [Github #8474](https://github.com/penpot/penpot/pull/8474)
- Copy and paste entire rows in existing table (by @bittoby) [Github #8498](https://github.com/penpot/penpot/pull/8498)
- Rename token group [Taiga #13137](https://tree.taiga.io/project/penpot/us/13137)
- Duplicate token group [Taiga #10653](https://tree.taiga.io/project/penpot/us/10653)
- Copy token name from contextual menu [Taiga #13568](https://tree.taiga.io/project/penpot/issue/13568)
- Add natural sorting on token names [Taiga #13713](https://tree.taiga.io/project/penpot/issue/13713)
- Add drag-to-change for numeric inputs in workspace sidebar (by @RenzoMXD) [Github #8536](https://github.com/penpot/penpot/pull/8536)
- Add CSS linter [Taiga #13790](https://tree.taiga.io/project/penpot/us/13790)
- Save and restore selection state in undo/redo (by @eureka0928) [Github #6007](https://github.com/penpot/penpot/issues/6007)
- Fix warnings for unsupported token $type (by @Dexterity104) [Github #8790](https://github.com/penpot/penpot/issues/8790)
- Add per-group add button for typographies (by @eureka0928) [Github #5275](https://github.com/penpot/penpot/issues/5275)
- Add Find & Replace for text content and layer names (by @statxc) [Github #7108](https://github.com/penpot/penpot/issues/7108)
- Use page name for multi-export ZIP/PDF downloads (by @Dexterity104) [Github #8773](https://github.com/penpot/penpot/issues/8773)
- Make links in comments clickable (by @eureka0928) [Github #1602](https://github.com/penpot/penpot/issues/1602)
- Add visibility toggle for strokes (by @eureka0928) [Github #7438](https://github.com/penpot/penpot/issues/7438)
- Sort asset library subfolders alphabetically at every nesting level (by @eureka0928) [Github #2572](https://github.com/penpot/penpot/issues/2572)
- Add Paste to replace (Cmd+Shift+V) to replace the selected shape with clipboard contents (by @eureka0928) [Github #4240](https://github.com/penpot/penpot/issues/4240)
- Differentiate incoming and outgoing interaction link colors (by @claytonlin1110) [Github #7794](https://github.com/penpot/penpot/issues/7794)
- Add guide locking and fix locked elements not selectable in viewer (by @Dexterity104) [Github #8358](https://github.com/penpot/penpot/issues/8358)
- Apply styles to selection (by @AzazelN28) [Taiga #13647](https://tree.taiga.io/project/penpot/task/13647)
- Reorder prototyping overlay options to show Position before Relative to (by @rockchris099) [Github #2910](https://github.com/penpot/penpot/issues/2910)
- Add customizable colors for ruler guides (by @Dexterity104) [Github #5199](https://github.com/penpot/penpot/issues/5199)
- Persist asset search query and section filter when switching sidebar tabs (by @eureka0928) [Github #2913](https://github.com/penpot/penpot/issues/2913)
- Add delete and duplicate buttons to typography dialog (by @eureka0928) [Github #5270](https://github.com/penpot/penpot/issues/5270)
- Edit ruler guide position by double-clicking the guide pill (by @eureka0928) [Github #2311](https://github.com/penpot/penpot/issues/2311)
- Add a search bar to filter colors in the color palette toolbar (by @eureka0928) [Github #7653](https://github.com/penpot/penpot/issues/7653)
- Add a search bar to filter board size presets (by @eureka0928) [Github #4658](https://github.com/penpot/penpot/issues/4658)
- Allow customising the OIDC login button label (by @wdeveloper16) [Github #7027](https://github.com/penpot/penpot/issues/7027)
- Add page separators in Workspace [Taiga #13611](https://tree.taiga.io/project/penpot/us/13611?milestone=262806)
- Preserve vector content when pasting SVG from external tools such as Inkscape (by @RenzoMXD) [Github #9182](https://github.com/penpot/penpot/pull/9182)
- Add Shift+Numpad0/1/2 as aliases to Shift+0/1/2 for zoom shortcuts (by @RenzoMXD) [Github #9063](https://github.com/penpot/penpot/pull/9063)
- Add pixel grid color picker in viewport settings (by @Yakehira) [Github #7750](https://github.com/penpot/penpot/issues/7750)
- Add HEX, HSB and HSL support to the color picker with a model switcher that persists across sessions (by @edwin-rivera-dev) [Github #9133](https://github.com/penpot/penpot/issues/9133)
- Show specific invitation-link error messages for expired, email-mismatch and invalid token cases [Github #9220](https://github.com/penpot/penpot/issues/9220)
- Show detailed messages on file import errors to help diagnose why a file could not be imported (by @jsdevninja) [Github #9004](https://github.com/penpot/penpot/issues/9004)
- Add read-only preview mode for saved versions — click a version name to open a dedicated preview view (by @wdeveloper16) [Github #8976](https://github.com/penpot/penpot/issues/8976)
- Add clipboard read/write permissions to the plugin system (by @wdeveloper16) [Github #9053](https://github.com/penpot/penpot/issues/9053)
- Add new numeric inputs for token management on the right sidebar [Taiga #12109](https://tree.taiga.io/project/penpot/us/12109?milestone=513226)
- Restore deleted team files in bulk instead of per file (by @Dexterity104) [Github #9248](https://github.com/penpot/penpot/pull/9248)
- Preserve Inkscape labels when pasting SVGs (by @jeffrey701) [Github #9252](https://github.com/penpot/penpot/pull/9252)
- Add delete group to assets panel context menu (by @FairyPigDev) [#9141](https://github.com/penpot/penpot/issues/9141) (PR: [#9151](https://github.com/penpot/penpot/pull/9151), [#9211](https://github.com/penpot/penpot/pull/9211))
- Show alpha percentage on library color values (by @rockchris099) [#6328](https://github.com/penpot/penpot/issues/6328)
- Add clear artboard guides to frame context menu (by @eureka0928) [#6987](https://github.com/penpot/penpot/issues/6987) (PR: [#8936](https://github.com/penpot/penpot/pull/8936))
- Add loader feedback while importing and exporting files (by @moorsecopers99) [#9020](https://github.com/penpot/penpot/issues/9020) (PR: [#9024](https://github.com/penpot/penpot/pull/9024))
- Allow duplicating color and typography styles (by @MkDev11) [#2912](https://github.com/penpot/penpot/issues/2912) (PR: [#8449](https://github.com/penpot/penpot/pull/8449))
- Add woff2 support on user uploaded fonts (by @Nivl) [#3521](https://github.com/penpot/penpot/issues/3521) (PR: [#8248](https://github.com/penpot/penpot/pull/8248))
- Import Tokens from linked library (by @dfelinto) [#9635](https://github.com/penpot/penpot/issues/9635) (PR: [#8391](https://github.com/penpot/penpot/pull/8391))
- Option to download custom fonts (by @dfelinto) [#9672](https://github.com/penpot/penpot/issues/9672) (PR: [#8320](https://github.com/penpot/penpot/pull/8320))
- Add copy as image to workspace context menu (by @dfelinto) [#9607](https://github.com/penpot/penpot/issues/9607) (PR: [#8313](https://github.com/penpot/penpot/pull/8313))
- Add Tab/Shift+Tab navigation to rename layers sequentially (by @bittoby) [#2569](https://github.com/penpot/penpot/issues/2569) (PR: [#8474](https://github.com/penpot/penpot/pull/8474))
- Copy and paste entire rows in existing table (by @bittoby) [#5969](https://github.com/penpot/penpot/issues/5969) (PR: [#8498](https://github.com/penpot/penpot/pull/8498))
- Rename token group [#9637](https://github.com/penpot/penpot/issues/9637) (PR: [#8275](https://github.com/penpot/penpot/pull/8275))
- Duplicate token group [#9638](https://github.com/penpot/penpot/issues/9638) (PR: [#8886](https://github.com/penpot/penpot/pull/8886))
- Copy token name from contextual menu [#9639](https://github.com/penpot/penpot/issues/9639) (PR: [#8566](https://github.com/penpot/penpot/pull/8566))
- Add natural sorting on token names [#8635](https://github.com/penpot/penpot/issues/8635) (PR: [#8672](https://github.com/penpot/penpot/pull/8672))
- Add drag-to-change for numeric inputs in workspace sidebar (by @RenzoMXD) [#2466](https://github.com/penpot/penpot/issues/2466) (PR: [#8536](https://github.com/penpot/penpot/pull/8536))
- Add CSS linter [#9636](https://github.com/penpot/penpot/issues/9636) (PR: [#8592](https://github.com/penpot/penpot/pull/8592))
- Save and restore selection state in undo/redo (by @eureka0928) [#6007](https://github.com/penpot/penpot/issues/6007) (PR: [#8652](https://github.com/penpot/penpot/pull/8652))
- Fix warnings for unsupported token $type (by @Dexterity104) [#8790](https://github.com/penpot/penpot/issues/8790) (PR: [#8873](https://github.com/penpot/penpot/pull/8873))
- Add per-group add button for typographies (by @eureka0928) [#5275](https://github.com/penpot/penpot/issues/5275) (PR: [#8895](https://github.com/penpot/penpot/pull/8895))
- Add Find & Replace for text content and layer names (by @statxc) [#7108](https://github.com/penpot/penpot/issues/7108) (PR: [#8899](https://github.com/penpot/penpot/pull/8899))
- Use page name for multi-export ZIP/PDF downloads (by @Dexterity104) [#8773](https://github.com/penpot/penpot/issues/8773) (PR: [#8874](https://github.com/penpot/penpot/pull/8874))
- Make links in comments clickable (by @eureka0928) [#1602](https://github.com/penpot/penpot/issues/1602) (PR: [#8894](https://github.com/penpot/penpot/pull/8894))
- Add visibility toggle for strokes (by @eureka0928) [#7438](https://github.com/penpot/penpot/issues/7438) (PR: [#8913](https://github.com/penpot/penpot/pull/8913))
- Sort asset library subfolders alphabetically at every nesting level (by @eureka0928) [#2572](https://github.com/penpot/penpot/issues/2572) (PR: [#8952](https://github.com/penpot/penpot/pull/8952))
- Add Paste to replace (Cmd+Shift+V) for selected shapes (by @eureka0928) [#4240](https://github.com/penpot/penpot/issues/4240) (PR: [#9033](https://github.com/penpot/penpot/pull/9033))
- Differentiate incoming and outgoing interaction link colors (by @claytonlin1110) [#7794](https://github.com/penpot/penpot/issues/7794) (PR: [#8923](https://github.com/penpot/penpot/pull/8923))
- Add guide locking and fix locked element selection in viewer (by @Dexterity104) [#8358](https://github.com/penpot/penpot/issues/8358) (PR: [#8949](https://github.com/penpot/penpot/pull/8949))
- Apply styles to selection (by @AzazelN28) [#9661](https://github.com/penpot/penpot/issues/9661) (PR: [#8625](https://github.com/penpot/penpot/pull/8625))
- Reorder prototyping overlay options to show Position before Relative to (by @rockchris099) [#2910](https://github.com/penpot/penpot/issues/2910)
- Add customizable colors for ruler guides (by @Dexterity104) [#5199](https://github.com/penpot/penpot/issues/5199) (PR: [#8986](https://github.com/penpot/penpot/pull/8986))
- Persist asset search and section filter across sidebar tabs (by @eureka0928) [#2913](https://github.com/penpot/penpot/issues/2913) (PR: [#8985](https://github.com/penpot/penpot/pull/8985))
- Add delete and duplicate buttons to typography dialog (by @eureka0928) [#5270](https://github.com/penpot/penpot/issues/5270) (PR: [#8983](https://github.com/penpot/penpot/pull/8983))
- Edit ruler guide position by double-clicking the guide pill (by @eureka0928) [#2311](https://github.com/penpot/penpot/issues/2311) (PR: [#8987](https://github.com/penpot/penpot/pull/8987))
- Add search bar to color palette (by @eureka0928) [#7653](https://github.com/penpot/penpot/issues/7653) (PR: [#8994](https://github.com/penpot/penpot/pull/8994))
- Add search bar to board size presets (by @eureka0928) [#4658](https://github.com/penpot/penpot/issues/4658) (PR: [#9117](https://github.com/penpot/penpot/pull/9117))
- Allow customising the OIDC login button label (by @wdeveloper16) [#7027](https://github.com/penpot/penpot/issues/7027) (PR: [#9026](https://github.com/penpot/penpot/pull/9026))
- Add page separators in Workspace [#9180](https://github.com/penpot/penpot/issues/9180) (PR: [#8561](https://github.com/penpot/penpot/pull/8561))
- Preserve vector content when pasting SVG from external tools (by @RenzoMXD) [#546](https://github.com/penpot/penpot/issues/546) (PR: [#9182](https://github.com/penpot/penpot/pull/9182))
- Add Shift+Numpad aliases for zoom shortcuts (by @RenzoMXD) [#2457](https://github.com/penpot/penpot/issues/2457) (PR: [#9063](https://github.com/penpot/penpot/pull/9063))
- Add pixel grid color picker in viewport settings (by @jack-stormentswe) [#7750](https://github.com/penpot/penpot/issues/7750) (PR: [#9155](https://github.com/penpot/penpot/pull/9155))
- Add HEX/HSB/HSL support to color picker with persistent model switcher (by @edwin-rivera-dev) [#9133](https://github.com/penpot/penpot/issues/9133) (PR: [#9134](https://github.com/penpot/penpot/pull/9134))
- Show specific invitation-link error messages (by @niwinz) [#9220](https://github.com/penpot/penpot/issues/9220) (PR: [#9223](https://github.com/penpot/penpot/pull/9223))
- Show detailed file import error messages (by @jsdevninja) [#8212](https://github.com/penpot/penpot/issues/8212) (PR: [#9004](https://github.com/penpot/penpot/pull/9004))
- Add read-only preview mode for saved versions (by @wdeveloper16) [#7622](https://github.com/penpot/penpot/issues/7622) (PR: [#8976](https://github.com/penpot/penpot/pull/8976))
- Add clipboard read/write permissions to the plugin system (by @wdeveloper16) [#6980](https://github.com/penpot/penpot/issues/6980) (PR: [#9053](https://github.com/penpot/penpot/pull/9053))
- Update auth hero illustration on login screen [#9532](https://github.com/penpot/penpot/issues/9532) (PR: [#9552](https://github.com/penpot/penpot/pull/9552))
- Update Open Graph link preview metadata [#9555](https://github.com/penpot/penpot/issues/9555) (PR: [#9557](https://github.com/penpot/penpot/pull/9557))
- Fix library update button freezing [#9330](https://github.com/penpot/penpot/issues/9330) (PR: [#9513](https://github.com/penpot/penpot/pull/9513))
- Add new numeric inputs for token management on the right sidebar [#9358](https://github.com/penpot/penpot/issues/9358)
- Restore deleted team files in bulk instead of per file (by @Dexterity104) [#9246](https://github.com/penpot/penpot/issues/9246) (PR: [#9248](https://github.com/penpot/penpot/pull/9248))
- Preserve Inkscape labels when pasting SVGs (by @jeffrey701) [#7869](https://github.com/penpot/penpot/issues/7869) (PR: [#9252](https://github.com/penpot/penpot/pull/9252))
- Add Alt+click to expand layer subtree (by @MilosM348) [#7736](https://github.com/penpot/penpot/issues/7736) (PR: [#9179](https://github.com/penpot/penpot/pull/9179))
- Fix typo in subscription settings success key (by @jack-stormentswe) [#9203](https://github.com/penpot/penpot/issues/9203) (PR: [#9204](https://github.com/penpot/penpot/pull/9204))
### :bug: Bugs fixed
- Fix Alt/Option to draw shapes from center point (by @offreal) [#8360](https://github.com/penpot/penpot/issues/8360) (PR: [#8361](https://github.com/penpot/penpot/pull/8361))
- Add token name on broken token pill on sidebar [#9534](https://github.com/penpot/penpot/issues/9534) (PR: [#8527](https://github.com/penpot/penpot/pull/8527))
- Fix tooltip activated when tab change [#9539](https://github.com/penpot/penpot/issues/9539) (PR: [#8719](https://github.com/penpot/penpot/pull/8719))
- Fix title on shared button [#9541](https://github.com/penpot/penpot/issues/9541) (PR: [#8696](https://github.com/penpot/penpot/pull/8696))
- Fix hover on layers [#9542](https://github.com/penpot/penpot/issues/9542) (PR: [#8885](https://github.com/penpot/penpot/pull/8885))
- Fix highlight after name edition [#9537](https://github.com/penpot/penpot/issues/9537) (PR: [#8890](https://github.com/penpot/penpot/pull/8890))
- Fix multiple small UI bugs — id prop, update copy, library modal scroll [#9536](https://github.com/penpot/penpot/issues/9536) (PR: [#8604](https://github.com/penpot/penpot/pull/8604))
- Fix themes modal height [#9535](https://github.com/penpot/penpot/issues/9535) (PR: [#9105](https://github.com/penpot/penpot/pull/9105))
- Fix layers panel rename showing default type name (by @jack-stormentswe) [#9230](https://github.com/penpot/penpot/issues/9230) (PR: [#9231](https://github.com/penpot/penpot/pull/9231))
- Suppress browser context menu on workspace sidebar right-click (by @sujyotraut) [#5127](https://github.com/penpot/penpot/issues/5127) (PR: [#9196](https://github.com/penpot/penpot/pull/9196))
- Fix plugin API fileVersion.restore() hanging on failure (by @thomascolden585-svg) [#9092](https://github.com/penpot/penpot/issues/9092) (PR: [#9111](https://github.com/penpot/penpot/pull/9111))
- Fix stroke-only SVG paths losing rounded join on split (by @Chrissi2812) [#5283](https://github.com/penpot/penpot/issues/5283) (PR: [#9156](https://github.com/penpot/penpot/pull/9156))
- Fix plugin API library.connectLibrary() not returning Promise (by @boskodev790) [#9646](https://github.com/penpot/penpot/issues/9646) (PR: [#9158](https://github.com/penpot/penpot/pull/9158))
- Fix LDAP provider schema typo in malli migration (by @boskodev790) [#9531](https://github.com/penpot/penpot/issues/9531) (PR: [#9165](https://github.com/penpot/penpot/pull/9165))
- Fix login-with-ldap dropping error on uninitialized LDAP (by @boskodev790) [#9533](https://github.com/penpot/penpot/issues/9533) (PR: [#9159](https://github.com/penpot/penpot/pull/9159))
- Fix OIDC_USER_INFO_SOURCE flag being ignored (by @GeekClassy) [#9108](https://github.com/penpot/penpot/issues/9108) (PR: [#9114](https://github.com/penpot/penpot/pull/9114))
- Fix share-link viewer crash on malformed email (by @boskodev790) [#9530](https://github.com/penpot/penpot/issues/9530) (PR: [#9120](https://github.com/penpot/penpot/pull/9120))
- Fix crash pasting component variants from external library (by @FairyPigDev) [#8144](https://github.com/penpot/penpot/issues/8144) (PR: [#9136](https://github.com/penpot/penpot/pull/9136))
- Remove corepack from MCP launcher for Node.js 25+ (by @TheAifam5) [#8877](https://github.com/penpot/penpot/issues/8877) (PR: [#9119](https://github.com/penpot/penpot/pull/9119))
- Fix Copy as SVG for multi-shape selections (by @RenzoMXD) [#9088](https://github.com/penpot/penpot/issues/9088) (PR: [#9066](https://github.com/penpot/penpot/pull/9066))
- Preserve OpenType variant name table for custom fonts in the dashboard (by @rutherfordcraze) [#8924](https://github.com/penpot/penpot/issues/8924) (PR: [#9193](https://github.com/penpot/penpot/pull/9193))
- Add export panel to inspect styles tab [#9660](https://github.com/penpot/penpot/issues/9660) (PR: [#8645](https://github.com/penpot/penpot/pull/8645))
- Fix styles between grid layout inputs [#9656](https://github.com/penpot/penpot/issues/9656) (PR: [#8673](https://github.com/penpot/penpot/pull/8673))
- Fix dates to avoid show them in english when browser is in auto [#8709](https://github.com/penpot/penpot/issues/8709) (PR: [#8775](https://github.com/penpot/penpot/pull/8775))
- Fix focus radio button [#9657](https://github.com/penpot/penpot/issues/9657) (PR: [#8774](https://github.com/penpot/penpot/pull/8774))
- Token tree should be expanded by default [#9662](https://github.com/penpot/penpot/issues/9662) (PR: [#8799](https://github.com/penpot/penpot/pull/8799))
- Fix opacity incorrectly disabled for visible shapes [#9658](https://github.com/penpot/penpot/issues/9658) (PR: [#8854](https://github.com/penpot/penpot/pull/8854))
- Fix plugin modal drag over iframe and close button (by @marekhrabe) [#9529](https://github.com/penpot/penpot/issues/9529) (PR: [#8871](https://github.com/penpot/penpot/pull/8871))
- Fix hot update on color-row on texts [#9664](https://github.com/penpot/penpot/issues/9664) (PR: [#8880](https://github.com/penpot/penpot/pull/8880))
- Fix selected color tokens [#9655](https://github.com/penpot/penpot/issues/9655) (PR: [#8889](https://github.com/penpot/penpot/pull/8889))
- Fix dashboard Recent/Deleted titles overlapped by scrolling content (by @rockchris099) [#8577](https://github.com/penpot/penpot/issues/8577)
- Display resolved values of inactive tokens [#9665](https://github.com/penpot/penpot/issues/9665) (PR: [#8589](https://github.com/penpot/penpot/pull/8589))
- Fix hyphens stripped from export filenames (by @jamesrayammons) [#8901](https://github.com/penpot/penpot/issues/8901) (PR: [#8944](https://github.com/penpot/penpot/pull/8944))
- Fix app crash on multiselection with hidden shapes and opacity mixed value [#9666](https://github.com/penpot/penpot/issues/9666) (PR: [#8932](https://github.com/penpot/penpot/pull/8932))
- Fix gap input throwing an error [#9667](https://github.com/penpot/penpot/issues/9667) (PR: [#8984](https://github.com/penpot/penpot/pull/8984))
- Fix copy to be more specific [#9668](https://github.com/penpot/penpot/issues/9668) (PR: [#9028](https://github.com/penpot/penpot/pull/9028))
- Allow deleting the profile avatar after uploading (by @moorsecopers99) [#9067](https://github.com/penpot/penpot/issues/9067) (PR: [#9068](https://github.com/penpot/penpot/pull/9068))
- Fix incorrect rendering when exporting text as SVG, PNG and JPG (by @edwin-rivera-dev) [#8516](https://github.com/penpot/penpot/issues/8516) (PR: [#9094](https://github.com/penpot/penpot/pull/9094))
- Fix typography style creation with tokenized line-height (by @juan-flores077) [#8479](https://github.com/penpot/penpot/issues/8479) (PR: [#9121](https://github.com/penpot/penpot/pull/9121))
- Fix colorpicker layout hiding eyedropper button [#9669](https://github.com/penpot/penpot/issues/9669) (PR: [#9125](https://github.com/penpot/penpot/pull/9125))
- Fix restore-deleted-team-files reduce typo (by @Dexterity104) [#9240](https://github.com/penpot/penpot/issues/9240) (PR: [#9241](https://github.com/penpot/penpot/pull/9241))
- Fix internal error on layer prev/next sibling selection (by @jsdevninja) [#7064](https://github.com/penpot/penpot/issues/7064) (PR: [#9003](https://github.com/penpot/penpot/pull/9003))
- Fix tooltip appearing two times when nested elements [#9674](https://github.com/penpot/penpot/issues/9674) (PR: [#9031](https://github.com/penpot/penpot/pull/9031))
- Fix broken update library notification link in the UI [#9673](https://github.com/penpot/penpot/issues/9673) (PR: [#9070](https://github.com/penpot/penpot/pull/9070))
- Fix plugin API ShapeBase.component() returning outermost instead of immediate component [#9183](https://github.com/penpot/penpot/issues/9183) (PR: [#9298](https://github.com/penpot/penpot/pull/9298))
- Fix content attribute sync group resolution by shape type [#9527](https://github.com/penpot/penpot/issues/9527) (PR: [#8724](https://github.com/penpot/penpot/pull/8724))
- Fix plugin parse-point returning plain map instead of Point record (by @FairyPigDev) [#8409](https://github.com/penpot/penpot/issues/8409) (PR: [#9129](https://github.com/penpot/penpot/pull/9129))
- Fix `:heigth` typo in clipboard frame-same-size? (by @iot2edge) [#9249](https://github.com/penpot/penpot/issues/9249) (PR: [#9250](https://github.com/penpot/penpot/pull/9250))
- Fix Settings Update button enabled state (by @moorsecopers99) [#9090](https://github.com/penpot/penpot/issues/9090) (PR: [#9091](https://github.com/penpot/penpot/pull/9091))
- Fix library updates reappearing after reload [#9326](https://github.com/penpot/penpot/issues/9326) (PR: [#9563](https://github.com/penpot/penpot/pull/9563))
- Fix dependency libraries visible after unlinking main library [#9331](https://github.com/penpot/penpot/issues/9331) (PR: [#9511](https://github.com/penpot/penpot/pull/9511))
- Fix internal error on margins [#9309](https://github.com/penpot/penpot/issues/9309) (PR: [#9311](https://github.com/penpot/penpot/pull/9311))
- Remove drag-to-change when token applied on numeric input [#9313](https://github.com/penpot/penpot/issues/9313) (PR: [#9314](https://github.com/penpot/penpot/pull/9314))
- Fix extra input on canvas background [#9359](https://github.com/penpot/penpot/issues/9359) (PR: [#9360](https://github.com/penpot/penpot/pull/9360))
- Fix frame selection highlight persists after rename [#9538](https://github.com/penpot/penpot/issues/9538) (PR: [#8938](https://github.com/penpot/penpot/pull/8938))
- Fix several color picker issues [#9556](https://github.com/penpot/penpot/issues/9556) (PR: [#9558](https://github.com/penpot/penpot/pull/9558))
- Fix asset icon broken on Asset tab [#9587](https://github.com/penpot/penpot/issues/9587) (PR: [#9612](https://github.com/penpot/penpot/pull/9612))
- Fix text fill color stops updating in multiselect with texts [#9608](https://github.com/penpot/penpot/issues/9608) (PR: [#9549](https://github.com/penpot/penpot/pull/9549))
## 2.15.4 (Unreleased)
### :bug: Bugs fixed
- Fix render-wasm atlas corruption when dragging large shapes after a zoom or pan change (stale multi-zoom-level pixels no longer appear at the old shape position).
- Fix Alt/Option to draw shapes from center point (by @offreal) [Github #8361](https://github.com/penpot/penpot/pull/8361)
- Add token name on broken token pill on sidebar [Taiga #13527](https://tree.taiga.io/project/penpot/issue/13527)
- Fix tooltip activated when tab change [Taiga #13627](https://tree.taiga.io/project/penpot/issue/13627)
- Fix title on shared button [Taiga #13730](https://tree.taiga.io/project/penpot/issue/13730)
- Fix hover on layers [Taiga #13799](https://tree.taiga.io/project/penpot/issue/13799)
- Fix highlight after name edition [Taiga #13783](https://tree.taiga.io/project/penpot/issue/13783)
- Fix id prop on switch component [Taiga #13534](https://tree.taiga.io/project/penpot/issue/13534)
- Fix dashboard navigation tabs overlap with projects content when scrolling [Taiga #13962](https://tree.taiga.io/project/penpot/issue/13962)
- Fix text editor v1 focus [Taiga #13961](https://tree.taiga.io/project/penpot/issue/13961)
- Fix color dropdown option update [Taiga #14035](https://tree.taiga.io/project/penpot/issue/14035)
- Fix themes modal height [Taiga #14046](https://tree.taiga.io/project/penpot/issue/14046)
- Fix layers panel rename input showing the default type name instead of the saved layer name (by @jack-stormentswe) [Github #9231](https://github.com/penpot/penpot/pull/9231)
- Suppress browser context menu on right-click in workspace sidebars while preserving it on text inputs (by @sujyotraut) [Github #5127](https://github.com/penpot/penpot/issues/5127)
- Fix release notes modal appearing behind the dashboard sidebar (by @ciaokitty) [Github #8296](https://github.com/penpot/penpot/issues/8296)
- Fix plugin API `fileVersion.restore()` promise hanging indefinitely on restore failure (by @thomascolden585-svg) [Github #9092](https://github.com/penpot/penpot/issues/9092)
- Fix imported stroke-only SVG paths losing their rounded join when split into adjacent subpaths (by @Chrissi2812) [Github #5283](https://github.com/penpot/penpot/issues/5283)
- Fix plugin API `library.connectLibrary()` not returning a Promise when the plugin lacks `library:write` permission (by @boskodev790) [Github #9158](https://github.com/penpot/penpot/pull/9158)
- Fix LDAP provider schema typo (`bind-passwor``bind-password`) introduced during the `clojure.spec``malli` migration (by @boskodev790) [Github #9165](https://github.com/penpot/penpot/pull/9165)
- Fix `login-with-ldap` silently dropping the error message when LDAP is not initialized (typo `:hide``:hint`) (by @boskodev790) [Github #9159](https://github.com/penpot/penpot/pull/9159)
- Fix `PENPOT_OIDC_USER_INFO_SOURCE` flag being silently ignored in the OIDC callback (by @GeekClassy) [Github #9108](https://github.com/penpot/penpot/issues/9108)
- Fix crash in share-link viewer when a team member's email is missing `@` or has no domain TLD (by @boskodev790) [Github #9120](https://github.com/penpot/penpot/pull/9120)
- Fix crash when pasting a component with variants from an external shared library into a file that uses that library (by @FairyPigDev) [Github #8144](https://github.com/penpot/penpot/issues/8144)
- Remove `corepack` from the MCP local launcher so it runs on Node.js 25+, where corepack is no longer bundled (by @TheAifam5) [Github #8877](https://github.com/penpot/penpot/issues/8877)
- Fix Copy as SVG to produce a valid document for multi-shape selections and use `image/svg+xml` MIME type (by @RenzoMXD) [Github #9066](https://github.com/penpot/penpot/pull/9066)
- Preserve OpenType variant name table for custom fonts in the dashboard (by @rutherfordcraze) [Github #8924](https://github.com/penpot/penpot/issues/8924)
- Add export panel to inspect styles tab [Taiga #13582](https://tree.taiga.io/project/penpot/issue/13582)
- Fix styles between grid layout inputs [Taiga #13526](https://tree.taiga.io/project/penpot/issue/13526)
- Fix id prop on switch component [Taiga #13534](https://tree.taiga.io/project/penpot/issue/13534)
- Update copy on penpot update message [Taiga #12924](https://tree.taiga.io/project/penpot/issue/12924)
- Fix scroll on library modal [Taiga #13639](https://tree.taiga.io/project/penpot/issue/13639)
- Fix dates to avoid show them in english when browser is in auto [Taiga #13786](https://tree.taiga.io/project/penpot/issue/13786)
- Fix focus radio button [Taiga #13841](https://tree.taiga.io/project/penpot/issue/13841)
- Token tree should be expanded by default [Taiga #13631](https://tree.taiga.io/project/penpot/issue/13631)
- Fix opacity incorrectly disabled for visible shapes [Taiga #13906](https://tree.taiga.io/project/penpot/issue/13906)
- Update onboarding image [Taiga #13864](https://tree.taiga.io/project/penpot/issue/13864)
- Fix plugin modal drag interactions over iframe and close-button behavior (by @marekhrabe) [Github #8871](https://github.com/penpot/penpot/pull/8871)
- Fix hot update on color-row on texts [Taiga #13923](https://tree.taiga.io/project/penpot/issue/13923)
- Fix selected color tokens [Taiga #13930](https://tree.taiga.io/project/penpot/issue/13930)
- Fix dashboard Recent/Deleted titles overlapped by scrolling content (by @rockchris099) [Github #8577](https://github.com/penpot/penpot/issues/8577)
- Display resolved values of inactive tokens [Taiga #13628](https://tree.taiga.io/project/penpot/issue/13628)
- Fix hyphens stripped from export filenames (by @jamesrayammons) [Github #8901](https://github.com/penpot/penpot/issues/8901)
- Fix app crash when selecting shapes with one hidden [Taiga #13959](https://tree.taiga.io/project/penpot/issue/13959)
- Fix opacity mixed value [Taiga #13960](https://tree.taiga.io/project/penpot/issue/13960)
- Fix gap input throwing an error [Github #8984](https://github.com/penpot/penpot/pull/8984)
- Fix copy to be more specific [Taiga #13990](https://tree.taiga.io/project/penpot/issue/13990)
- Allow deleting the profile avatar after uploading (by @moorsecopers99) [Github #9067](https://github.com/penpot/penpot/issues/9067)
- Fix incorrect rendering when exporting text as SVG, PNG and JPG (by @edwin-rivera-dev) [Github #8516](https://github.com/penpot/penpot/issues/8516)
- Fix "Help & Learning" submenu vertical alignment in account menu (by @juan-flores077) [Github #9137](https://github.com/penpot/penpot/issues/9137)
- Fix plugin `addInteraction` silently rejecting `open-overlay` actions with `manualPositionLocation` (by @axelseis) [Github #8409](https://github.com/penpot/penpot/issues/8409)
- Fix typography style creation with tokenized line-height (by @juan-flores077) [Github #8479](https://github.com/penpot/penpot/issues/8479)
- Fix colorpicker layout so the eyedropper button is visible again [Taiga #14057](https://tree.taiga.io/project/penpot/issue/14057)
- Fix restore-deleted-team-files failing due to a typo in the reduce accumulator (by @Dexterity104) [Github #9241](https://github.com/penpot/penpot/issues/9241)
- Fix internal error on layer prev/next sibling selection (by @jsdevninja) [Github #9003](https://github.com/penpot/penpot/issues/9003)
- Fix tooltip appearing two times when nested elements [Github #9031](https://github.com/penpot/penpot/issues/9031)
- Fix broken update library notification link in the UI [Github #9070](https://github.com/penpot/penpot/issues/9070)
- Fix plugin API `ShapeBase.component()` returning the outermost component instead of the immediate component in case of nested component instances [Github #9183](https://github.com/penpot/penpot/issues/9183)
- Fix content attribute sync group resolution by shape type [Github #8724](https://github.com/penpot/penpot/pull/8724)
- Fix highlight on shape after rename [Github #8890](https://github.com/penpot/penpot/pull/8890)
- Fix plugin parse-point returning plain map instead of Point record (by @FairyPigDev) [Github #9129](https://github.com/penpot/penpot/pull/9129)
- Fix `:heigth` typo in clipboard frame-same-size? (by @iot2edge) [Github #9250](https://github.com/penpot/penpot/pull/9250)
- Fix Settings and Notifications "Update Settings" button enabled state when form has no changes (by @moorsecopers99) [Github #9090](https://github.com/penpot/penpot/issues/9090)
- Fix library updates reappear after being applied and the file is reloaded [Taiga #14040](https://tree.taiga.io/project/penpot/issue/14040)
- Fix dependency libraries remaining visible in UI after unlinking main library [Taiga #14020](https://tree.taiga.io/project/penpot/issue/14020)
- Emit `create-shape-layout` for flex/grid layout creation from plugins and MCP (same event as workspace) [Github #9652](https://github.com/penpot/penpot/issues/9652)
## 2.15.3
### :bug: Bugs fixed
- Fix Plugin API token methods failing with schema validation error on PRO [GH #9641](https://github.com/penpot/penpot/issues/9641)
(PR: [#9632](https://github.com/penpot/penpot/pull/9632))
- Sanitize comment content on rendering [GH #9642](https://github.com/penpot/penpot/issues/9642)
(PR: [#9605](https://github.com/penpot/penpot/pull/9605))
- Sanitize font family names on custom uploaded fonts [GH #9643](https://github.com/penpot/penpot/issues/9643)
(PR: [#9601](https://github.com/penpot/penpot/pull/9601))
## 2.15.2
### :bug: Bugs fixed
- Fix mcp related internal config for docker images [Github #9565](https://github.com/penpot/penpot/pull/9565)
- Fix mcp related internal config for docker images [GH #9565](https://github.com/penpot/penpot/pull/9565)
## 2.15.1
### :sparkles: New features & Enhancements
- Add support for chunked uploading of fonts [Github #9560](https://github.com/penpot/penpot/issues/9560)
- Add support for chunked uploading of fonts [GH #9560](https://github.com/penpot/penpot/issues/9560)
### :bug: Bugs fixed
- Fix "Help & Learning" submenu vertical alignment in account menu (by @juan-flores077) [#9137](https://github.com/penpot/penpot/issues/9137) (PR: [#9138](https://github.com/penpot/penpot/pull/9138))
## 2.15.0
### :sparkles: New features & Enhancements
- Add MCP server integration [Github #9174](https://github.com/penpot/penpot/issues/9174)
- Add MCP server integration [GH #9174](https://github.com/penpot/penpot/issues/9174)
(PR: [#9032](https://github.com/penpot/penpot/pull/9032), [#9321](https://github.com/penpot/penpot/pull/9321))
- Add chunked upload API for large media and binary files (removes previous upload size limits) [Github #9516](https://github.com/penpot/penpot/issues/9516)
- Add chunked upload API for large media and binary files (removes previous upload size limits) [GH #9516](https://github.com/penpot/penpot/issues/9516)
(PR: [#8909](https://github.com/penpot/penpot/pull/8909))
- Add anonymous telemetry event collection [Github #9467](https://github.com/penpot/penpot/issues/9467)
- Add anonymous telemetry event collection [GH #9467](https://github.com/penpot/penpot/issues/9467)
(PR: [#9065](https://github.com/penpot/penpot/pull/9065), [#9483](https://github.com/penpot/penpot/pull/9483))
- Improve team name validation [Github #9517](https://github.com/penpot/penpot/issues/9517)
- Improve team name validation [GH #9517](https://github.com/penpot/penpot/issues/9517)
(PR: [#9176](https://github.com/penpot/penpot/pull/9176))
- Enhance readability of applied tokens in plugins API [Github #9175](https://github.com/penpot/penpot/issues/9175)
- Enhance readability of applied tokens in plugins API [GH #9175](https://github.com/penpot/penpot/issues/9175)
(PR: [#8607](https://github.com/penpot/penpot/pull/8607))
- Encourage use of flex/grid layouts in designs generated via MCP [Github #9081](https://github.com/penpot/penpot/issues/9081)
- Encourage use of flex/grid layouts in designs generated via MCP [GH #9081](https://github.com/penpot/penpot/issues/9081)
(PR: [#9084](https://github.com/penpot/penpot/pull/9084))
- Improve MCP server logging, adding Loki support [Github #9415](https://github.com/penpot/penpot/issues/9415)
- Improve MCP server logging, adding Loki support [GH #9415](https://github.com/penpot/penpot/issues/9415)
(PR: [#9425](https://github.com/penpot/penpot/pull/9425))
- Add security headers to Nginx on Docker images [Github #9519](https://github.com/penpot/penpot/issues/9519)
- Add security headers to Nginx on Docker images [GH #9519](https://github.com/penpot/penpot/issues/9519)
(PR: [#9473](https://github.com/penpot/penpot/pull/9473))
### :bug: Bugs fixed
- Fix text edition mode not exited when changing selection, blocking token application [Github #9346](https://github.com/penpot/penpot/issues/9346)
- Fix text edition mode not exited when changing selection, blocking token application [GH #9346](https://github.com/penpot/penpot/issues/9346)
(PR: [#9355](https://github.com/penpot/penpot/pull/9355))
- Reduce memory usage of MCP server when handling images (by @opcode81) [Github #9420](https://github.com/penpot/penpot/issues/9420)
- Reduce memory usage of MCP server when handling images (by @opcode81) [GH #9420](https://github.com/penpot/penpot/issues/9420)
(PR: [#9431](https://github.com/penpot/penpot/pull/9431))
- Fix Plugin API token methods rejecting JS array of strings (by @boskodev790) [Github #9162](https://github.com/penpot/penpot/issues/9162)
- Fix Plugin API token methods rejecting JS array of strings (by @boskodev790) [GH #9162](https://github.com/penpot/penpot/issues/9162)
(PR: [#9166](https://github.com/penpot/penpot/pull/9166))
- Fix release notes modal appearing behind the dashboard sidebar (by @RenzoMXD) [Github #8296](https://github.com/penpot/penpot/issues/8296)
- Fix release notes modal appearing behind the dashboard sidebar (by @RenzoMXD) [GH #8296](https://github.com/penpot/penpot/issues/8296)
(PR: [#9126](https://github.com/penpot/penpot/pull/9126), [#9233](https://github.com/penpot/penpot/pull/9233))
- Fix empty warning on login [Github #9520](https://github.com/penpot/penpot/issues/9520)
- Fix empty warning on login [GH #9520](https://github.com/penpot/penpot/issues/9520)
(PR: [#9056](https://github.com/penpot/penpot/pull/9056))
- Fix maximum call stack size exceeded in SSE read-stream [Github #9470](https://github.com/penpot/penpot/issues/9470)
- Fix maximum call stack size exceeded in SSE read-stream [GH #9470](https://github.com/penpot/penpot/issues/9470)
(PR: [#9484](https://github.com/penpot/penpot/pull/9484))
- Fix incorrect handling of version restore operation [Github #9515](https://github.com/penpot/penpot/issues/9515)
- Fix incorrect handling of version restore operation [GH #9515](https://github.com/penpot/penpot/issues/9515)
(PR: [#9041](https://github.com/penpot/penpot/pull/9041))
- Fix MCP ReplServer binding to all interfaces (0.0.0.0) instead of localhost, allowing unauthenticated RCE [Github #9518](https://github.com/penpot/penpot/issues/9518)
- Fix MCP ReplServer binding to all interfaces (0.0.0.0) instead of localhost, allowing unauthenticated RCE [GH #9518](https://github.com/penpot/penpot/issues/9518)
(PR: [#9400](https://github.com/penpot/penpot/pull/9400))
- Fix MCP integrations URL copy action to match the URL displayed in settings [Github #9238](https://github.com/penpot/penpot/issues/9238)
- Fix MCP integrations URL copy action to match the URL displayed in settings [GH #9238](https://github.com/penpot/penpot/issues/9238)
(PR: [#9239](https://github.com/penpot/penpot/pull/9239))
- Fix swapped analytics event names on MCP tab-switch dialog (by @Dexterity104) [Github #9496](https://github.com/penpot/penpot/issues/9496)
- Fix swapped analytics event names on MCP tab-switch dialog (by @Dexterity104) [GH #9496](https://github.com/penpot/penpot/issues/9496)
(PR: [#9322](https://github.com/penpot/penpot/pull/9322))
- Fix multiple selection on shapes with token applied to stroke color [Github #9522](https://github.com/penpot/penpot/issues/9522)
- Fix multiple selection on shapes with token applied to stroke color [GH #9522](https://github.com/penpot/penpot/issues/9522)
(PR: [#9110](https://github.com/penpot/penpot/pull/9110))
- Fix onboarding modals appearing behind libraries and templates panel [Github #9521](https://github.com/penpot/penpot/issues/9521)
- Fix onboarding modals appearing behind libraries and templates panel [GH #9521](https://github.com/penpot/penpot/issues/9521)
(PR: [#9178](https://github.com/penpot/penpot/pull/9178))
- Fix keep-alive interval leak in PluginBridge (by @opcode81) [Github #9430](https://github.com/penpot/penpot/issues/9430)
- Fix keep-alive interval leak in PluginBridge (by @opcode81) [GH #9430](https://github.com/penpot/penpot/issues/9430)
(PR: [#9435](https://github.com/penpot/penpot/pull/9435))
## 2.14.5
### :bug: Bugs fixed
- Fix incorrect invitation token handling on register process [Github #9380](https://github.com/penpot/penpot/pull/9380)
- Fix incorrect invitation token handling on register process [GH #9380](https://github.com/penpot/penpot/pull/9380)
## 2.14.4
### :bug: Bugs fixed
- Fix email validation [Taiga #14006](https://tree.taiga.io/project/penpot/issue/14006)
- Fix email blacklisting [Github #9122](https://github.com/penpot/penpot/pull/9122)
- Fix removeChild errors from unmount race conditions [Github #8927](https://github.com/penpot/penpot/pull/8927)
- Fix email blacklisting [GH #9122](https://github.com/penpot/penpot/pull/9122)
- Fix removeChild errors from unmount race conditions [GH #8927](https://github.com/penpot/penpot/pull/8927)
## 2.14.3
### :sparkles: New features & Enhancements
- Add webp export format to plugin types [Github #8870](https://github.com/penpot/penpot/pull/8870)
- Use shared singleton containers for React portals to reduce DOM growth [Github #8957](https://github.com/penpot/penpot/pull/8957)
- Add webp export format to plugin types [GH #8870](https://github.com/penpot/penpot/pull/8870)
- Use shared singleton containers for React portals to reduce DOM growth [GH #8957](https://github.com/penpot/penpot/pull/8957)
### :bug: Bugs fixed
- Fix variants corner cases with selrect and points [Github #8882](https://github.com/penpot/penpot/pull/8882)
- Fix variants corner cases with selrect and points [GH #8882](https://github.com/penpot/penpot/pull/8882)
- Fix dashboard navigation tabs overlap with projects content when scrolling [Taiga #13962](https://tree.taiga.io/project/penpot/issue/13962)
- Fix text editor v1 focus [Taiga #13961](https://tree.taiga.io/project/penpot/issue/13961)
- Fix highlight on frames after rename [Github #8938](https://github.com/penpot/penpot/pull/8938)
- Fix TypeError in sd-token-uuid when resolving tokens interactively [Github #8929](https://github.com/penpot/penpot/pull/8929)
- Fix highlight on frames after rename [GH #8938](https://github.com/penpot/penpot/pull/8938)
- Fix TypeError in sd-token-uuid when resolving tokens interactively [GH #8929](https://github.com/penpot/penpot/pull/8929)
- Fix path drawing preview passing shape instead of content to next-node
- Fix swapped arguments in CLJS PathData `-nth` with default
- Normalize PathData coordinates to safe integer bounds on read
- Fix RangeError from re-entrant error handling causing stack overflow [Github #8962](https://github.com/penpot/penpot/pull/8962)
- Fix builder bool styles and media validation [Github #8963](https://github.com/penpot/penpot/pull/8963)
- Fix RangeError from re-entrant error handling causing stack overflow [GH #8962](https://github.com/penpot/penpot/pull/8962)
- Fix builder bool styles and media validation [GH #8963](https://github.com/penpot/penpot/pull/8963)
- Fix "Move to" menu allowing same project as target when multiple files are selected
- Fix crash when index query param is duplicated in URL
- Fix wrong extremity point in path `calculate-extremities` for line-to segments
@ -267,42 +282,42 @@
### :sparkles: New features & Enhancements
- Add protection for stale JS asset cache to force reload on version mismatch [Github #8638](https://github.com/penpot/penpot/pull/8638)
- Normalize newsletter opt-in checkbox across different register flows [Github #8839](https://github.com/penpot/penpot/pull/8839)
- Add protection for stale JS asset cache to force reload on version mismatch [GH #8638](https://github.com/penpot/penpot/pull/8638)
- Normalize newsletter opt-in checkbox across different register flows [GH #8839](https://github.com/penpot/penpot/pull/8839)
### :bug: Bugs fixed
- Fix PathData corruption root causes across WASM and CLJS (unsafe transmute and byteOffset handling)
- Handle corrupted PathData segments gracefully instead of crashing
- Fix swapped move-to/line-to type codes in PathData binary readers
- Fix non-integer row/column values in grid cell position inputs [Github #8869](https://github.com/penpot/penpot/pull/8869)
- Fix nil path content crash by exposing safe public API [Github #8806](https://github.com/penpot/penpot/pull/8806)
- Fix infinite recursion in get-frame-ids for thumbnail extraction [Github #8807](https://github.com/penpot/penpot/pull/8807)
- Fix non-integer row/column values in grid cell position inputs [GH #8869](https://github.com/penpot/penpot/pull/8869)
- Fix nil path content crash by exposing safe public API [GH #8806](https://github.com/penpot/penpot/pull/8806)
- Fix infinite recursion in get-frame-ids for thumbnail extraction [GH #8807](https://github.com/penpot/penpot/pull/8807)
- Fix stale-asset detector missing protocol-dispatch errors
- Ignore Zone.js toString TypeError in uncaught error handler [Github #8804](https://github.com/penpot/penpot/pull/8804)
- Prevent thumbnail frame recursion overflow [Github #8763](https://github.com/penpot/penpot/pull/8763)
- Fix vector index out of bounds in viewer zoom-to-fit/fill [Github #8834](https://github.com/penpot/penpot/pull/8834)
- Guard delete undo against missing sibling order [Github #8858](https://github.com/penpot/penpot/pull/8858)
- Fix ICounted error on numeric-input token dropdown keyboard nav [Github #8803](https://github.com/penpot/penpot/pull/8803)
- Ignore Zone.js toString TypeError in uncaught error handler [GH #8804](https://github.com/penpot/penpot/pull/8804)
- Prevent thumbnail frame recursion overflow [GH #8763](https://github.com/penpot/penpot/pull/8763)
- Fix vector index out of bounds in viewer zoom-to-fit/fill [GH #8834](https://github.com/penpot/penpot/pull/8834)
- Guard delete undo against missing sibling order [GH #8858](https://github.com/penpot/penpot/pull/8858)
- Fix ICounted error on numeric-input token dropdown keyboard nav [GH #8803](https://github.com/penpot/penpot/pull/8803)
## 2.14.1
### :sparkles: New features & Enhancements
- Add automatic retry with backoff for idempotent RPC requests on network failures [Github #8792](https://github.com/penpot/penpot/pull/8792)
- Add scroll and zoom throttling to one state update per animation frame [Github #8812](https://github.com/penpot/penpot/pull/8812)
- Improve error handling and exception formatting [Github #8757](https://github.com/penpot/penpot/pull/8757)
- Add automatic retry with backoff for idempotent RPC requests on network failures [GH #8792](https://github.com/penpot/penpot/pull/8792)
- Add scroll and zoom throttling to one state update per animation frame [GH #8812](https://github.com/penpot/penpot/pull/8812)
- Improve error handling and exception formatting [GH #8757](https://github.com/penpot/penpot/pull/8757)
### :bug: Bugs fixed
- Fix crash in apply-text-modifier with nil selrect or modifier [Github #8762](https://github.com/penpot/penpot/pull/8762)
- Fix incorrect attrs references on generate-sync-shape [Github #8776](https://github.com/penpot/penpot/pull/8776)
- Fix regression on subpath support [Github #8793](https://github.com/penpot/penpot/pull/8793)
- Improve error reporting on request parsing failures [Github #8805](https://github.com/penpot/penpot/pull/8805)
- Fix fetch abort errors escaping the unhandled exception handler [Github #8801](https://github.com/penpot/penpot/pull/8801)
- Fix nil deref on missing bounds in layout modifier propagation [Github #8735](https://github.com/penpot/penpot/pull/8735)
- Fix TypeError when token error map lacks :error/fn key [Github #8767](https://github.com/penpot/penpot/pull/8767)
- Fix dissoc error when detaching stroke color from library [Github #8738](https://github.com/penpot/penpot/pull/8738)
- Fix crash in apply-text-modifier with nil selrect or modifier [GH #8762](https://github.com/penpot/penpot/pull/8762)
- Fix incorrect attrs references on generate-sync-shape [GH #8776](https://github.com/penpot/penpot/pull/8776)
- Fix regression on subpath support [GH #8793](https://github.com/penpot/penpot/pull/8793)
- Improve error reporting on request parsing failures [GH #8805](https://github.com/penpot/penpot/pull/8805)
- Fix fetch abort errors escaping the unhandled exception handler [GH #8801](https://github.com/penpot/penpot/pull/8801)
- Fix nil deref on missing bounds in layout modifier propagation [GH #8735](https://github.com/penpot/penpot/pull/8735)
- Fix TypeError when token error map lacks :error/fn key [GH #8767](https://github.com/penpot/penpot/pull/8767)
- Fix dissoc error when detaching stroke color from library [GH #8738](https://github.com/penpot/penpot/pull/8738)
- Fix crash when pasting image into text editor
- Fix null text crash on paste in text editor
- Ensure path content is always PathData when saving
@ -326,28 +341,28 @@
### :bug: Bugs fixed
- Remove whitespaces from asset export filename [Github #8133](https://github.com/penpot/penpot/pull/8133)
- Remove whitespaces from asset export filename [GH #8133](https://github.com/penpot/penpot/pull/8133)
- Fix prototype connections lost when switching between variants [Taiga #12812](https://tree.taiga.io/project/penpot/issue/12812)
- Fix wrong image in the onboarding invitation block [Taiga #13040](https://tree.taiga.io/project/penpot/issue/13040)
- Fix wrong register image [Taiga #12955](https://tree.taiga.io/project/penpot/task/12955)
- Fix error message on components doesn't close automatically [Taiga #12012](https://tree.taiga.io/project/penpot/issue/12012)
- Fix incorrect default option on tokens import dialog [Github #8051](https://github.com/penpot/penpot/pull/8051)
- Fix unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
- Fix incorrect default option on tokens import dialog [GH #8051](https://github.com/penpot/penpot/pull/8051)
- Fix unhandled exception tokens creation dialog [GH #8110](https://github.com/penpot/penpot/issues/8110)
- Fix displaying a hidden user avatar when there is only one more [Taiga #13058](https://tree.taiga.io/project/penpot/issue/13058)
- Fix exception on uploading large fonts [Github #8135](https://github.com/penpot/penpot/pull/8135)
- Fix exception on uploading large fonts [GH #8135](https://github.com/penpot/penpot/pull/8135)
- Fix boolean operators in menu for boards [Taiga #13174](https://tree.taiga.io/project/penpot/issue/13174)
- Fix viewer can update library [Taiga #13186](https://tree.taiga.io/project/penpot/issue/13186)
- Fix remove fill affects different element than selected [Taiga #13128](https://tree.taiga.io/project/penpot/issue/13128)
- Fix cannot apply second token after creation while shape is selected [Taiga #13513](https://tree.taiga.io/project/penpot/issue/13513)
- Fix error activating a set with invalid shadow token applied [Taiga #13528](https://tree.taiga.io/project/penpot/issue/13528)
- Fix component "broken" after variant switch [Taiga #12984](https://tree.taiga.io/project/penpot/issue/12984)
- Fix incorrect query for file versions [Github #8463](https://github.com/penpot/penpot/pull/8463)
- Fix incorrect query for file versions [GH #8463](https://github.com/penpot/penpot/pull/8463)
- Fix warning when clicking on number token pills [Taiga #13661](https://tree.taiga.io/project/penpot/issue/13661)
- Fix 'not ISeqable' error when entering float values in layout item and opacity inputs [Github #8569](https://github.com/penpot/penpot/pull/8569)
- Fix crash in select component when options vector is empty [Github #8578](https://github.com/penpot/penpot/pull/8578)
- Fix 'not ISeqable' error when entering float values in layout item and opacity inputs [GH #8569](https://github.com/penpot/penpot/pull/8569)
- Fix crash in select component when options vector is empty [GH #8578](https://github.com/penpot/penpot/pull/8578)
- Fix scroll on colorpicker [Taiga #13623](https://tree.taiga.io/project/penpot/issue/13623)
- Fix crash when pasting non-map transit clipboard data [Github #8580](https://github.com/penpot/penpot/pull/8580)
- Fix `penpot.openPage()` plugin API not navigating in the same tab; change default to same-tab navigation and allow passing a UUID string instead of a Page object [Github #8520](https://github.com/penpot/penpot/issues/8520)
- Fix crash when pasting non-map transit clipboard data [GH #8580](https://github.com/penpot/penpot/pull/8580)
- Fix `penpot.openPage()` plugin API not navigating in the same tab; change default to same-tab navigation and allow passing a UUID string instead of a Page object [GH #8520](https://github.com/penpot/penpot/issues/8520)
## 2.13.3
@ -372,7 +387,7 @@
### :heart: Community contributions (Thank you!)
- Fix mask issues with component swap (by @dfelinto) [Github #7675](https://github.com/penpot/penpot/issues/7675)
- Fix mask issues with component swap (by @dfelinto) [GH #7675](https://github.com/penpot/penpot/issues/7675)
### :sparkles: New features & Enhancements
@ -385,7 +400,7 @@
- Fix problem when drag+duplicate a full grid [Taiga #12565](https://tree.taiga.io/project/penpot/issue/12565)
- Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
- Fix problem with grid layout components and auto sizing [Github #7797](https://github.com/penpot/penpot/issues/7797)
- Fix problem with grid layout components and auto sizing [GH #7797](https://github.com/penpot/penpot/issues/7797)
- Fix some alignments on inspect tab [Taiga #12915](https://tree.taiga.io/project/penpot/issue/12915)
- Fix problem with text editor maintaining previous styles [Taiga #12835](https://tree.taiga.io/project/penpot/issue/12835)
- Fix color assets from shared libraries not appearing as assets in Selected colors panel [Taiga #12957](https://tree.taiga.io/project/penpot/issue/12957)
@ -396,9 +411,9 @@
- Fix typos on download modal [Taiga #12865](https://tree.taiga.io/project/penpot/issue/12865)
- Fix allow negative spread values on shadow token creation [Taiga #13167](https://tree.taiga.io/project/penpot/issue/13167)
- Fix spanish translations on import export token modal [Taiga #13171](https://tree.taiga.io/project/penpot/issue/13171)
- Fix unhandled exception on open-new-window helper [Github #7787](https://github.com/penpot/penpot/issues/7787)
- Fix incorrect handling of input values on layout gap and padding inputs [Github #8113](https://github.com/penpot/penpot/issues/8113)
- Fix several race conditions on path editor [Github #8187](https://github.com/penpot/penpot/pull/8187)
- Fix unhandled exception on open-new-window helper [GH #7787](https://github.com/penpot/penpot/issues/7787)
- Fix incorrect handling of input values on layout gap and padding inputs [GH #8113](https://github.com/penpot/penpot/issues/8113)
- Fix several race conditions on path editor [GH #8187](https://github.com/penpot/penpot/pull/8187)
- Fix app freeze when introducing an error on a very long token name [Taiga #13214](https://tree.taiga.io/project/penpot/issue/13214)
- Fix import a file with shadow tokens [Taiga #13229](https://tree.taiga.io/project/penpot/issue/13229)
- Fix allow spaces on token description [Taiga #13184](https://tree.taiga.io/project/penpot/issue/13184)
@ -408,9 +423,9 @@
### :bug: Bugs fixed
- Fix setting a portion of text as bold or underline messes things up [Github #7980](https://github.com/penpot/penpot/issues/7980)
- Fix setting a portion of text as bold or underline messes things up [GH #7980](https://github.com/penpot/penpot/issues/7980)
- Fix problem with style in fonts input [Taiga #12935](https://tree.taiga.io/project/penpot/issue/12935)
- Fix problem with path editor and right click [Github #7917](https://github.com/penpot/penpot/issues/7917)
- Fix problem with path editor and right click [GH #7917](https://github.com/penpot/penpot/issues/7917)
## 2.12.0
@ -470,8 +485,8 @@ example. It's still usable as before, we just removed the example.
### :heart: Community contributions (Thank you!)
- Ensure consistent snap behavior across all zoom levels [Github #7774](https://github.com/penpot/penpot/pull/7774) by [@Tokytome](https://github.com/Tokytome)
- Fix crash in token grid view due to tooltip validation (by @dfelinto) [Github #7887](https://github.com/penpot/penpot/pull/7887)
- Ensure consistent snap behavior across all zoom levels [GH #7774](https://github.com/penpot/penpot/pull/7774) by [@Tokytome](https://github.com/Tokytome)
- Fix crash in token grid view due to tooltip validation (by @dfelinto) [GH #7887](https://github.com/penpot/penpot/pull/7887)
- Enable Hindi translations on the application
### :sparkles: New features & Enhancements
@ -480,7 +495,7 @@ example. It's still usable as before, we just removed the example.
- Add toggle for switching boolean property values [Taiga #12341](https://tree.taiga.io/project/penpot/us/12341)
- Make the file export process more reliable [Taiga #12555](https://tree.taiga.io/project/penpot/us/12555)
- Add auth flow changes [Taiga #12333](https://tree.taiga.io/project/penpot/us/12333)
- Add new shape validation mechanism for shapes [Github #7696](https://github.com/penpot/penpot/pull/7696)
- Add new shape validation mechanism for shapes [GH #7696](https://github.com/penpot/penpot/pull/7696)
- Apply color tokens from sidebar [Taiga #11353](https://tree.taiga.io/project/penpot/us/11353)
- Display tokens in the inspect tab [Taiga #9313](https://tree.taiga.io/project/penpot/us/9313)
- Refactor clipboard behavior to assess some minor inconsistencies and make pasting binary data faster. [Taiga #12571](https://tree.taiga.io/project/penpot/task/12571)
@ -488,10 +503,10 @@ example. It's still usable as before, we just removed the example.
### :bug: Bugs fixed
- Fix text line-height values are wrong [Taiga #12252](https://tree.taiga.io/project/penpot/issue/12252)
- Fix pan cursor not disabling viewport guides [Github #6985](https://github.com/penpot/penpot/issues/6985)
- Fix pan cursor not disabling viewport guides [GH #6985](https://github.com/penpot/penpot/issues/6985)
- Fix viewport resize on locked shapes [Taiga #11974](https://tree.taiga.io/project/penpot/issue/11974)
- Fix on copy instance inside a components chain touched are missing [Taiga #12371](https://tree.taiga.io/project/penpot/issue/12371)
- Fix problem with multiple selection and shadows [Github #7437](https://github.com/penpot/penpot/issues/7437)
- Fix problem with multiple selection and shadows [GH #7437](https://github.com/penpot/penpot/issues/7437)
- Fix search shortcut [Taiga #10265](https://tree.taiga.io/project/penpot/issue/10265)
- Fix shortcut conflict in text editor (increase/decrease font size vs word selection)
- Fix problem with plugins generating code for pages different than current one [Taiga #12312](https://tree.taiga.io/project/penpot/issue/12312)
@ -504,7 +519,7 @@ example. It's still usable as before, we just removed the example.
- Fix switch variants with paths [Taiga #12841](https://tree.taiga.io/project/penpot/issue/12841)
- Fix referencing typography tokens on font-family tokens [Taiga #12492](https://tree.taiga.io/project/penpot/issue/12492)
- Fix horizontal scroll on layer panel [Taiga #12843](https://tree.taiga.io/project/penpot/issue/12843)
- Fix unicode handling on email template abbreviation filter [Github #7966](https://github.com/penpot/penpot/pull/7966)
- Fix unicode handling on email template abbreviation filter [GH #7966](https://github.com/penpot/penpot/pull/7966)
## 2.11.1
@ -545,15 +560,15 @@ example. It's still usable as before, we just removed the example.
- Invitations management improvements [Taiga #3479](https://tree.taiga.io/project/penpot/us/3479)
- Alternative ways of creating variants - Button Viewport [Taiga #11931](https://tree.taiga.io/project/penpot/us/11931)
- Reorder properties for a component [Taiga #10225](https://tree.taiga.io/project/penpot/us/10225)
- File Data storage layout refactor [Github #7345](https://github.com/penpot/penpot/pull/7345)
- Make several queries optimization on comment threads [Github #7506](https://github.com/penpot/penpot/pull/7506)
- File Data storage layout refactor [GH #7345](https://github.com/penpot/penpot/pull/7345)
- Make several queries optimization on comment threads [GH #7506](https://github.com/penpot/penpot/pull/7506)
### :bug: Bugs fixed
- Fix selection problems when devtools open [Taiga #11950](https://tree.taiga.io/project/penpot/issue/11950)
- Fix long font names overlap [Taiga #11844](https://tree.taiga.io/project/penpot/issue/11844)
- Fix paste behavior according to the selected element [Taiga #11979](https://tree.taiga.io/project/penpot/issue/11979)
- Fix problem with export size [Github #7160](https://github.com/penpot/penpot/issues/7160)
- Fix problem with export size [GH #7160](https://github.com/penpot/penpot/issues/7160)
- Fix multi level library dependencies [Taiga #12155](https://tree.taiga.io/project/penpot/issue/12155)
- Fix component context menu options order in assets tab [Taiga #11941](https://tree.taiga.io/project/penpot/issue/11941)
- Fix error updating library [Taiga #12218](https://tree.taiga.io/project/penpot/issue/12218)
@ -581,8 +596,8 @@ example. It's still usable as before, we just removed the example.
- Fix options button does not work for comments created in the lower part of the screen [Taiga #12422](https://tree.taiga.io/project/penpot/issue/12422)
- Fix problem when checking usage with removed teams [Taiga #12442](https://tree.taiga.io/project/penpot/issue/12442)
- Fix focus mode persisting across page/file navigation [Taiga #12469](https://tree.taiga.io/project/penpot/issue/12469)
- Fix shadow color validation [Github #7705](https://github.com/penpot/penpot/pull/7705)
- Fix exception on selection blend-mode using keyboard [Github #7710](https://github.com/penpot/penpot/pull/7710)
- Fix shadow color validation [GH #7705](https://github.com/penpot/penpot/pull/7705)
- Fix exception on selection blend-mode using keyboard [GH #7710](https://github.com/penpot/penpot/pull/7710)
- Fix crash when using decimal (floating-point) values for X/Y or width/height [Taiga #12543](https://tree.taiga.io/project/penpot/issue/12543)
## 2.10.1
@ -607,7 +622,7 @@ example. It's still usable as before, we just removed the example.
### :sparkles: New features & Enhancements
- Add efficiency enhancements to right sidebar [Github #7182](https://github.com/penpot/penpot/pull/7182)
- Add efficiency enhancements to right sidebar [GH #7182](https://github.com/penpot/penpot/pull/7182)
- Add defaults for artboard drawing [Taiga #494](https://tree.taiga.io/project/penpot/us/494?milestone=465047)
- Continuous display of distances between elements when moving a layer with the keyboard [Taiga #1780](https://tree.taiga.io/project/penpot/us/1780)
- New Number token - unitless values [Taiga #10936](https://tree.taiga.io/project/penpot/us/10936)
@ -616,8 +631,8 @@ example. It's still usable as before, we just removed the example.
- New text-decoration token [Taiga #10941](https://tree.taiga.io/project/penpot/us/10941)
- New letter spacing token [Taiga #10940](https://tree.taiga.io/project/penpot/us/10940)
- New font weight token [Taiga #10939](https://tree.taiga.io/project/penpot/us/10939)
- Upgrade Node to v22.18.0 [Github #7283](https://github.com/penpot/penpot/pull/7283)
- Upgrade the base docker image for penpot frontend to v1.29.1 [Github #7283](https://github.com/penpot/penpot/pull/7283)
- Upgrade Node to v22.18.0 [GH #7283](https://github.com/penpot/penpot/pull/7283)
- Upgrade the base docker image for penpot frontend to v1.29.1 [GH #7283](https://github.com/penpot/penpot/pull/7283)
- Create variant from an existing component [Taiga #2088](https://tree.taiga.io/project/penpot/us/2088)
- Create variant from an existing variant [Taiga #8282](https://tree.taiga.io/project/penpot/us/8282)
- Actions over a component with variants [Taiga #10503](https://tree.taiga.io/project/penpot/us/10503)
@ -686,7 +701,7 @@ example. It's still usable as before, we just removed the example.
- Hide bounding box while editing visual effects [Taiga #11576](https://tree.taiga.io/project/penpot/issue/11576)
- Improved text layer resizing: Allow double-click on text bounding box to set auto-width/auto-height [Taiga #11577](https://tree.taiga.io/project/penpot/issue/11577)
- Improve text layer auto-resize: auto-width switches to auto-height on horizontal resize, and only switches to fixed on vertical resize [Taiga #11578](https://tree.taiga.io/project/penpot/issue/11578)
- Add the ability to show login dialog on profile settings [Github #6871](https://github.com/penpot/penpot/pull/6871)
- Add the ability to show login dialog on profile settings [GH #6871](https://github.com/penpot/penpot/pull/6871)
- Improve the application of tokens with object specific tokens [Taiga #10209](https://tree.taiga.io/project/penpot/us/10209)
- Add info to apply-token event [Taiga #11710](https://tree.taiga.io/project/penpot/task/11710)
- Fix double click on set name input [Taiga #11747](https://tree.taiga.io/project/penpot/issue/11747)
@ -726,7 +741,7 @@ example. It's still usable as before, we just removed the example.
### :bug: Bugs fixed
- Fix unexpected exception on processing old texts [Github #6889](https://github.com/penpot/penpot/pull/6889)
- Fix unexpected exception on processing old texts [GH #6889](https://github.com/penpot/penpot/pull/6889)
- Fix error on inspect tab when selecting multiple shapes [Taiga #11655](https://tree.taiga.io/project/penpot/issue/11655)
- Fix missing package for the penport_exporter Docker image [GitHub #7205](https://github.com/penpot/penpot/issues/7025)
@ -759,13 +774,13 @@ on-premises instances** that want to keep up to date.
- Rewrite path shape data PathData encoding [Taiga #8542](https://tree.taiga.io/project/penpot/us/8542?milestone=441308)
- Update base image for Docker Backend and Exporter to Ubuntu 24.04
- Update base image for Docker Frontend to Nginx 1.28.0
- Allow multi file token import [Github #27](https://github.com/tokens-studio/penpot/issues/27)
- Allow multi file token import [GH #27](https://github.com/tokens-studio/penpot/issues/27)
- Create `input*` wrapper component, and `label*`, `input-field*` and `hint-message*` components [Taiga #10713](https://tree.taiga.io/project/penpot/us/10713)
- Deselect layers (and path nodes) with Ctrl+Shift+Drag [Github #2509](https://github.com/penpot/penpot/issues/2509)
- Copy to SVG from contextual menu [Github #838](https://github.com/penpot/penpot/issues/838)
- Deselect layers (and path nodes) with Ctrl+Shift+Drag [GH #2509](https://github.com/penpot/penpot/issues/2509)
- Copy to SVG from contextual menu [GH #838](https://github.com/penpot/penpot/issues/838)
- Add styles for Inkeep Chat at workspace [Taiga #10708](https://tree.taiga.io/project/penpot/us/10708)
- Add configuration for air gapped installations with Docker
- Support system color scheme [Github #5030](https://github.com/penpot/penpot/issues/5030)
- Support system color scheme [GH #5030](https://github.com/penpot/penpot/issues/5030)
- Persist ruler visibility across files and reloads [GitHub #4586](https://github.com/penpot/penpot/issues/4586)
- Update google fonts (at 2025/05/19) [Taiga 10792](https://tree.taiga.io/project/penpot/us/10792)
- Add tooltip component to DS [Taiga 9220](https://tree.taiga.io/project/penpot/us/9220)
@ -776,7 +791,7 @@ on-premises instances** that want to keep up to date.
- Fix getCurrentUser for plugins api [Taiga #11057](https://tree.taiga.io/project/penpot/issue/11057)
- Fix spacing / sizes of different elements in the measurements section of the design tab [Taiga #11076](https://tree.taiga.io/project/penpot/issue/11076)
- Fix selection of short paths [Github #4472](https://github.com/penpot/penpot/issues/4472)
- Fix selection of short paths [GH #4472](https://github.com/penpot/penpot/issues/4472)
- Fix element positioning on the right side to adjust to grid [#11073](https://tree.taiga.io/project/penpot/issue/11073)
- Fix palette is over sidebar [#11160](https://tree.taiga.io/project/penpot/issue/11160)
- Fix font size input not displaying "mixed" when multiple texts are selected [Taiga #11177](https://tree.taiga.io/project/penpot/issue/11177)
@ -794,15 +809,15 @@ on-premises instances** that want to keep up to date.
- Fix entering long project name [Taiga #11417](https://tree.taiga.io/project/penpot/issue/11417)
- Fix slow color picker [Taiga #11019](https://tree.taiga.io/project/penpot/issue/11019)
- Fix tooltip position after click [Taiga #11405](https://tree.taiga.io/project/penpot/issue/11405)
- Fix incorrect media translation on paste text with fill images [Github #6845](https://github.com/penpot/penpot/pull/6845)
- Fix incorrect media translation on paste text with fill images [GH #6845](https://github.com/penpot/penpot/pull/6845)
## 2.7.2
### :bug: Bugs fixed
- Update plugins runtime [Github #6604](https://github.com/penpot/penpot/pull/6604)
- Update plugins runtime [GH #6604](https://github.com/penpot/penpot/pull/6604)
- Backport from develop a minor fix that enables import of files
generated by penpot library [Github #6614](https://github.com/penpot/penpot/pull/6614)
generated by penpot library [GH #6614](https://github.com/penpot/penpot/pull/6614)
- Fix copy in error message [GitHub #6615](https://github.com/penpot/penpot/pull/6615)
- Fix url on invitation link [Taiga #11284](https://tree.taiga.io/project/penpot/issue/11284)
@ -853,13 +868,13 @@ on-premises instances** that want to keep up to date.
- Fix team info settings alignment [Taiga #10869](https://tree.taiga.io/project/penpot/issue/10869)
- Fix left sidebar horizontal scroll on nested layers [Taiga #10791](https://tree.taiga.io/project/penpot/issue/10791)
- Improve error message details importing tokens [Taiga Issue #10772](https://tree.taiga.io/project/penpot/issue/10772)
- Fix no selected set after Drag & Drop [Github #71](https://github.com/tokens-studio/penpot/issues/71)
- Styledictionary v5 Update [Github #6283](https://github.com/penpot/penpot/pull/6283)
- Fix Rename a set throws an internal error [Github #78](https://github.com/tokens-studio/penpot/issues/78)
- Fix Out of Sync Token Value & Color Picker [Github #102](https://github.com/tokens-studio/penpot/issues/102)
- Fix Color should preserve color space [Github #69](https://github.com/tokens-studio/penpot/issues/69)
- Fix no selected set after Drag & Drop [GH #71](https://github.com/tokens-studio/penpot/issues/71)
- Styledictionary v5 Update [GH #6283](https://github.com/penpot/penpot/pull/6283)
- Fix Rename a set throws an internal error [GH #78](https://github.com/tokens-studio/penpot/issues/78)
- Fix Out of Sync Token Value & Color Picker [GH #102](https://github.com/tokens-studio/penpot/issues/102)
- Fix Color should preserve color space [GH #69](https://github.com/tokens-studio/penpot/issues/69)
- Fix cannot rename Design Token Sets when group of same name exists [Taiga Issue #10773](https://tree.taiga.io/project/penpot/issue/10773)
- Fix problem when duplicating grid layout [Github #6391](https://github.com/penpot/penpot/issues/6391)
- Fix problem when duplicating grid layout [GH #6391](https://github.com/penpot/penpot/issues/6391)
- Fix issue that makes workspace shortcuts stop working [Taiga #11062](https://tree.taiga.io/project/penpot/issue/11062)
- Fix problem while syncing library colors and typographies [Taiga #11068](https://tree.taiga.io/project/penpot/issue/11068)
- Fix problem with path edition of shapes [Taiga #9496](https://tree.taiga.io/project/penpot/issue/9496)
@ -883,7 +898,7 @@ on-premises instances** that want to keep up to date.
- Fix unexpected exception on template import from libraries
- Fix incorrect uuid parsing from different parts of code
- Fix update layout on component restore [Taiga #10637](https://tree.taiga.io/project/penpot/issue/10637)
- Fix horizontal scroll in viewer [Github #6290](https://github.com/penpot/penpot/issues/6290)
- Fix horizontal scroll in viewer [GH #6290](https://github.com/penpot/penpot/issues/6290)
- Fix detach component in a particular case [Taiga #10837](https://tree.taiga.io/project/penpot/issue/10837)
## 2.6.1
@ -923,7 +938,7 @@ on-premises instances** that want to keep up to date.
### :bug: Bugs fixed
- Fix opacity in frame containers [Github #5858](https://github.com/penpot/penpot/pull/5858)
- Fix opacity in frame containers [GH #5858](https://github.com/penpot/penpot/pull/5858)
- Avoid resizing on click [Taiga #10213](https://tree.taiga.io/project/penpot/issue/10213)
- Hide horizontal scroll from dashboard sidebar [Taiga #10422](https://tree.taiga.io/project/penpot/issue/10422)
- Fix cut and paste a copy a cmponent inside its parent [Taiga #10365](https://tree.taiga.io/project/penpot/us/10365)
@ -948,7 +963,7 @@ on-premises instances** that want to keep up to date.
### :heart: Community contributions (Thank you!)
- Add support for WEBP format on shape export [Github #6053](https://github.com/penpot/penpot/pull/6053) and [Github #6074](https://github.com/penpot/penpot/pull/6074)
- Add support for WEBP format on shape export [GH #6053](https://github.com/penpot/penpot/pull/6053) and [GH #6074](https://github.com/penpot/penpot/pull/6074)
### :bug: Bugs fixed
@ -1171,20 +1186,20 @@ is a number of cores)
- Fix problem with go back button on error page [Taiga #8887](https://tree.taiga.io/project/penpot/issue/8887)
- Fix problem with shadows in text for Safari [Taiga #8770](https://tree.taiga.io/project/penpot/issue/8770)
- Fix a regression with feedback form subject and content limits [Taiga #8908](https://tree.taiga.io/project/penpot/issue/8908)
- Fix problem with stroke and filter ordering in frames [Github #5058](https://github.com/penpot/penpot/issues/5058)
- Fix problem with hover layers when hidden/blocked [Github #5074](https://github.com/penpot/penpot/issues/5074)
- Fix problem with stroke and filter ordering in frames [GH #5058](https://github.com/penpot/penpot/issues/5058)
- Fix problem with hover layers when hidden/blocked [GH #5074](https://github.com/penpot/penpot/issues/5074)
- Fix problem with precision on boolean calculation [Taiga #8482](https://tree.taiga.io/project/penpot/issue/8482)
- Fix problem when translating multiple path points [Github #4459](https://github.com/penpot/penpot/issues/4459)
- Fix problem when translating multiple path points [GH #4459](https://github.com/penpot/penpot/issues/4459)
- Fix problem on importing (and exporting) files with flows [Taiga #8914](https://tree.taiga.io/project/penpot/issue/8914)
- Fix Internal Error page: "go to your penpot" wrong design [Taiga #8922](https://tree.taiga.io/project/penpot/issue/8922)
- Fix problem updating layout when toggle visibility in component copy [Github #5143](https://github.com/penpot/penpot/issues/5143)
- Fix problem updating layout when toggle visibility in component copy [GH #5143](https://github.com/penpot/penpot/issues/5143)
- Fix "Done" button on toolbar on inspect mode should go to design mode [Taiga #8933](https://tree.taiga.io/project/penpot/issue/8933)
- Fix problem with shortcuts in text editor [Github #5078](https://github.com/penpot/penpot/issues/5078)
- Fix problems with show in viewer and interactions [Github #4868](https://github.com/penpot/penpot/issues/4868)
- Add visual feedback when moving an element into a board [Github #3210](https://github.com/penpot/penpot/issues/3210)
- Fix percent calculation on grid layout tracks [Github #4688](https://github.com/penpot/penpot/issues/4688)
- Fix problem with caps and inner shadows [Github #4517](https://github.com/penpot/penpot/issues/4517)
- Fix problem with horizontal/vertical lines and shadows [Github #4516](https://github.com/penpot/penpot/issues/4516)
- Fix problem with shortcuts in text editor [GH #5078](https://github.com/penpot/penpot/issues/5078)
- Fix problems with show in viewer and interactions [GH #4868](https://github.com/penpot/penpot/issues/4868)
- Add visual feedback when moving an element into a board [GH #3210](https://github.com/penpot/penpot/issues/3210)
- Fix percent calculation on grid layout tracks [GH #4688](https://github.com/penpot/penpot/issues/4688)
- Fix problem with caps and inner shadows [GH #4517](https://github.com/penpot/penpot/issues/4517)
- Fix problem with horizontal/vertical lines and shadows [GH #4516](https://github.com/penpot/penpot/issues/4516)
- Fix problem with layers overflowing panel [Taiga #9021](https://tree.taiga.io/project/penpot/issue/9021)
- Fix in workspace you can manage rulers on view mode [Taiga #8966](https://tree.taiga.io/project/penpot/issue/8966)
- Fix problem with swap components in grid layout [Taiga #9066](https://tree.taiga.io/project/penpot/issue/9066)
@ -1277,10 +1292,10 @@ is a number of cores)
- Fix fill collapsed options [Taiga #8351](https://tree.taiga.io/project/penpot/issue/8351)
- Fix scroll on color picker modal [Taiga #8353](https://tree.taiga.io/project/penpot/issue/8353)
- Fix components are not dragged from the group to the assets tab [Taiga #8273](https://tree.taiga.io/project/penpot/issue/8273)
- Fix problem with SVG import [Github #4888](https://github.com/penpot/penpot/issues/4888)
- Fix problem with SVG import [GH #4888](https://github.com/penpot/penpot/issues/4888)
- Fix problem with overlay positions in viewer [Taiga #8464](https://tree.taiga.io/project/penpot/issue/8464)
- Fix layer panel overflowing [Taiga #8665](https://tree.taiga.io/project/penpot/issue/8665)
- Fix problem when creating a component instance from grid layout [Github #4881](https://github.com/penpot/penpot/issues/4881)
- Fix problem when creating a component instance from grid layout [GH #4881](https://github.com/penpot/penpot/issues/4881)
- Fix problem when dismissing shared library update [Taiga #8669](https://tree.taiga.io/project/penpot/issue/8669)
- Fix visual problem with stroke cap menu [Taiga #8730](https://tree.taiga.io/project/penpot/issue/8730)
- Fix issue when exporting libraries when merging libraries [Taiga #8758](https://tree.taiga.io/project/penpot/issue/8758)
@ -1305,15 +1320,15 @@ is a number of cores)
## 2.1.3
- Don't allow registration when registration is disabled and invitation token is used [Github #4975](https://github.com/penpot/penpot/issues/4975)
- Don't allow registration when registration is disabled and invitation token is used [GH #4975](https://github.com/penpot/penpot/issues/4975)
## 2.1.2
### :bug: Bugs fixed
- User switch language to "zh_hant" will get 400 [Github #4884](https://github.com/penpot/penpot/issues/4884)
- Smtp config ignoring port if ssl is set [Github #4872](https://github.com/penpot/penpot/issues/4872)
- Ability to let users to authenticate with a private oidc provider only [Github #4963](https://github.com/penpot/penpot/issues/4963)
- User switch language to "zh_hant" will get 400 [GH #4884](https://github.com/penpot/penpot/issues/4884)
- Smtp config ignoring port if ssl is set [GH #4872](https://github.com/penpot/penpot/issues/4872)
- Ability to let users to authenticate with a private oidc provider only [GH #4963](https://github.com/penpot/penpot/issues/4963)
## 2.1.1
@ -1356,7 +1371,7 @@ is a number of cores)
- Layout and scrollign fixes for the bottom palette [Taiga #7559](https://tree.taiga.io/project/penpot/issue/7559)
- Fix expand libraries when search results are present [Taiga #7876](https://tree.taiga.io/project/penpot/issue/7876)
- Fix color palette default library [Taiga #8029](https://tree.taiga.io/project/penpot/issue/8029)
- Component Library is lost after exporting/importing in .zip format [Github #4672](https://github.com/penpot/penpot/issues/4672)
- Component Library is lost after exporting/importing in .zip format [GH #4672](https://github.com/penpot/penpot/issues/4672)
- Fix problem with moving+selection not working properly [Taiga #7943](https://tree.taiga.io/project/penpot/issue/7943)
- Fix problem with flex layout fit to content not positioning correctly children [Taiga #7537](https://tree.taiga.io/project/penpot/issue/7537)
- Fix black line is displaying after show main [Taiga #7653](https://tree.taiga.io/project/penpot/issue/7653)
@ -1389,7 +1404,7 @@ is a number of cores)
### :bug: Bugs fixed
- Fix chrome scrollbar styling [Taiga #7852](https://tree.taiga.io/project/penpot/issue/7852)
- Fix incorrect password encoding on create-profile manage scritp [Github #3651](https://github.com/penpot/penpot/issues/3651)
- Fix incorrect password encoding on create-profile manage scritp [GH #3651](https://github.com/penpot/penpot/issues/3651)
## 2.0.2
@ -1407,7 +1422,7 @@ is a number of cores)
### :bug: Bugs fixed
- Fix different issues related to components v2 migrations including [Github #4443](https://github.com/penpot/penpot/issues/4443)
- Fix different issues related to components v2 migrations including [GH #4443](https://github.com/penpot/penpot/issues/4443)
## 2.0.0 - I Just Can't Get Enough
@ -1506,9 +1521,9 @@ is a number of cores)
### :bug: Bugs fixed
- Fix pixelated thumbnails [Github #3681](https://github.com/penpot/penpot/issues/3681), [Github #3661](https://github.com/penpot/penpot/issues/3661)
- Fix problem with not applying colors to boards [Github #3941](https://github.com/penpot/penpot/issues/3941)
- Fix problem with path editor undoing changes [Github #3998](https://github.com/penpot/penpot/issues/3998)
- Fix pixelated thumbnails [GH #3681](https://github.com/penpot/penpot/issues/3681), [GH #3661](https://github.com/penpot/penpot/issues/3661)
- Fix problem with not applying colors to boards [GH #3941](https://github.com/penpot/penpot/issues/3941)
- Fix problem with path editor undoing changes [GH #3998](https://github.com/penpot/penpot/issues/3998)
- [View mode] Open overlay places frame in the wrong position when paired with a fixed element [Taiga #6385](https://tree.taiga.io/project/penpot/issue/6385)
- Flex Layout: Fit-content not recalculated after deleting an element [Taiga #5968](https://tree.taiga.io/project/penpot/issue/5968)
- Selecting from Color Palette does not work for board when there is no existing fill [Taiga #6464](https://tree.taiga.io/project/penpot/issue/6464)
@ -1539,11 +1554,11 @@ is a number of cores)
- [VIEWER] Cannot scroll down in code </> mode [Taiga #4655](https://tree.taiga.io/project/penpot/issue/4655)
- Strange cursor behavior after clicking viewport with text tool [Taiga #4363](https://tree.taiga.io/project/penpot/issue/4363)
- Selected color affects all of them [Taiga #5285](https://tree.taiga.io/project/penpot/issue/5285)
- Fix problem with shadow negative spread [Github #3421](https://github.com/penpot/penpot/issues/3421)
- Fix problem with linked colors to strokes [Github #3522](https://github.com/penpot/penpot/issues/3522)
- Fix problem with hand tool stuck [Github #3318](https://github.com/penpot/penpot/issues/3318)
- Fix problem with fix scrolling on nested elements [Github #3508](https://github.com/penpot/penpot/issues/3508)
- Fix problem when changing typography assets [Github #3683](https://github.com/penpot/penpot/issues/3683)
- Fix problem with shadow negative spread [GH #3421](https://github.com/penpot/penpot/issues/3421)
- Fix problem with linked colors to strokes [GH #3522](https://github.com/penpot/penpot/issues/3522)
- Fix problem with hand tool stuck [GH #3318](https://github.com/penpot/penpot/issues/3318)
- Fix problem with fix scrolling on nested elements [GH #3508](https://github.com/penpot/penpot/issues/3508)
- Fix problem when changing typography assets [GH #3683](https://github.com/penpot/penpot/issues/3683)
- Internal error when you copy and paste some main components between files [Taiga #7397](https://tree.taiga.io/project/penpot/issue/7397)
- Fix toolbar disappearing [Taiga #7411](https://tree.taiga.io/project/penpot/issue/7411)
- Fix long text on tab breaks UI [Taiga #7421](https://tree.taiga.io/project/penpot/issue/7421)
@ -1569,12 +1584,12 @@ is a number of cores)
### :sparkles: New features
- Remember last color mode in colorpicker [Taiga #5508](https://tree.taiga.io/project/penpot/issue/5508)
- Improve layers multiselection behaviour [Github #5741](https://github.com/penpot/penpot/issues/5741)
- Remember last active team across logouts / sessions [Github #3325](https://github.com/penpot/penpot/issues/3325)
- Improve layers multiselection behaviour [GH #5741](https://github.com/penpot/penpot/issues/5741)
- Remember last active team across logouts / sessions [GH #3325](https://github.com/penpot/penpot/issues/3325)
### :bug: Bugs fixed
- List view is discarded on tab change on Workspace Assets Sidebar tab [Github #3547](https://github.com/penpot/penpot/issues/3547)
- List view is discarded on tab change on Workspace Assets Sidebar tab [GH #3547](https://github.com/penpot/penpot/issues/3547)
- Fix message popup remains open when exiting workspace with browser back button [Taiga #5747](https://tree.taiga.io/project/penpot/issue/5747)
- When editing text if font is changed, the proportions of the rendered shape are wrong [Taiga #5786](https://tree.taiga.io/project/penpot/issue/5786)
@ -1588,7 +1603,7 @@ is a number of cores)
### :bug: Bugs fixed
- Fix unexpected output on get-page rpc method when invalid object-id is provided [Github #3546](https://github.com/penpot/penpot/issues/3546)
- Fix unexpected output on get-page rpc method when invalid object-id is provided [GH #3546](https://github.com/penpot/penpot/issues/3546)
- Fix Invalid files amount after moving file from Project to Drafts [Taiga #5638](https://tree.taiga.io/project/penpot/us/5638)
- Fix deleted pages comments shown in right sidebar [Taiga #5648](https://tree.taiga.io/project/penpot/us/5648)
- Fix tooltip on toggle visibility and toggle lock buttons [Taiga #5141](https://tree.taiga.io/project/penpot/issue/5141)
@ -1614,7 +1629,7 @@ is a number of cores)
rendered as bitmap images.
- Add the ability to disable google fonts provider with the `disable-google-fonts-provider` flag
- Add the ability to disable dashboard templates section with the `disable-dashboard-templates-section` flag
- Add the ability to use the registration whitelist with OICD [Github #3348](https://github.com/penpot/penpot/issues/3348)
- Add the ability to use the registration whitelist with OICD [GH #3348](https://github.com/penpot/penpot/issues/3348)
- Add support for local caching of google fonts (this avoids exposing the final user IP to
goolge and reduces the amount of request sent to google)
- Set smooth/instant autoscroll depending on distance [GitHub #3377](https://github.com/penpot/penpot/issues/3377)
@ -1708,20 +1723,20 @@ is a number of cores)
### :heart: Community contributions by (Thank you!)
- Update Typography palette order (by @akshay-gupta7) [Github #3156](https://github.com/penpot/penpot/pull/3156)
- Palettes (color, typographies) empty state (by @akshay-gupta7) [Github #3160](https://github.com/penpot/penpot/pull/3160)
- Duplicate objects via drag + alt (by @akshay-gupta7) [Github #3147](https://github.com/penpot/penpot/pull/3147)
- Set line-height to auto as 1.2 (by @akshay-gupta7) [Github #3185](https://github.com/penpot/penpot/pull/3185)
- Click to select full values at the design sidebar (by @akshay-gupta7) [Github #3179](https://github.com/penpot/penpot/pull/3179)
- Fix rect filter bounds math (by @ryanbreen) [Github #3180](https://github.com/penpot/penpot/pull/3180)
- Removed sizing variables from radius (by @ondrejkonec) [Github #3184](https://github.com/penpot/penpot/pull/3184)
- Dashboard search, set focus after shortcut (by @akshay-gupta7) [Github #3196](https://github.com/penpot/penpot/pull/3196)
- Update Typography palette order (by @akshay-gupta7) [GH #3156](https://github.com/penpot/penpot/pull/3156)
- Palettes (color, typographies) empty state (by @akshay-gupta7) [GH #3160](https://github.com/penpot/penpot/pull/3160)
- Duplicate objects via drag + alt (by @akshay-gupta7) [GH #3147](https://github.com/penpot/penpot/pull/3147)
- Set line-height to auto as 1.2 (by @akshay-gupta7) [GH #3185](https://github.com/penpot/penpot/pull/3185)
- Click to select full values at the design sidebar (by @akshay-gupta7) [GH #3179](https://github.com/penpot/penpot/pull/3179)
- Fix rect filter bounds math (by @ryanbreen) [GH #3180](https://github.com/penpot/penpot/pull/3180)
- Removed sizing variables from radius (by @ondrejkonec) [GH #3184](https://github.com/penpot/penpot/pull/3184)
- Dashboard search, set focus after shortcut (by @akshay-gupta7) [GH #3196](https://github.com/penpot/penpot/pull/3196)
- Library name dropdown arrow is overlapped by library name (by @ondrejkonec) [Taiga #5200](https://tree.taiga.io/project/penpot/issue/5200)
- Reorder shadows (by @akshay-gupta7) [Github #3236](https://github.com/penpot/penpot/pull/3236)
- Open project in new tab from workspace (by @akshay-gupta7) [Github #3246](https://github.com/penpot/penpot/pull/3246)
- Distribute fix enabled when two elements were selected (by @dfelinto) [Github #3266](https://github.com/penpot/penpot/pull/3266)
- Distribute vertical spacing failing for overlapped text (by @dfelinto) [Github #3267](https://github.com/penpot/penpot/pull/3267)
- bug Change independent corner radius input tooltips #3332 (by @astudentinearth) [Github #3332](https://github.com/penpot/penpot/pull/3332)
- Reorder shadows (by @akshay-gupta7) [GH #3236](https://github.com/penpot/penpot/pull/3236)
- Open project in new tab from workspace (by @akshay-gupta7) [GH #3246](https://github.com/penpot/penpot/pull/3246)
- Distribute fix enabled when two elements were selected (by @dfelinto) [GH #3266](https://github.com/penpot/penpot/pull/3266)
- Distribute vertical spacing failing for overlapped text (by @dfelinto) [GH #3267](https://github.com/penpot/penpot/pull/3267)
- bug Change independent corner radius input tooltips #3332 (by @astudentinearth) [GH #3332](https://github.com/penpot/penpot/pull/3332)
## 1.18.6
@ -1751,7 +1766,7 @@ is a number of cores)
- Fix problem with layout not reflowing on shape deletion [Taiga #5289](https://tree.taiga.io/project/penpot/issue/5289)
- Fix extra long typography names on assets and palette [Taiga #5199](https://tree.taiga.io/project/penpot/issue/5199)
- Fix background-color property on inspect code [Taiga #5300](https://tree.taiga.io/project/penpot/issue/5300)
- Preview layer blend modes (by @akshay-gupta7) [Github #3235](https://github.com/penpot/penpot/pull/3235)
- Preview layer blend modes (by @akshay-gupta7) [GH #3235](https://github.com/penpot/penpot/pull/3235)
## 1.18.3
@ -1803,7 +1818,7 @@ is a number of cores)
- Improve deeps selection of nested arboards [Taiga #4913](https://tree.taiga.io/project/penpot/issue/4913)
- Fix problem on selection numeric inputs on Firefox [#2991](https://github.com/penpot/penpot/issues/2991)
- Changed the text dominant-baseline to use ideographic [Taiga #4791](https://tree.taiga.io/project/penpot/issue/4791)
- Viewer wrong translations [Github #3035](https://github.com/penpot/penpot/issues/3035)
- Viewer wrong translations [GH #3035](https://github.com/penpot/penpot/issues/3035)
- Fix problem with text editor in Safari
- Fix unlink library color when blur color picker input [#3026](https://github.com/penpot/penpot/issues/3026)
- Fix snap pixel when moving path points on high zoom [#2930](https://github.com/penpot/penpot/issues/2930)
@ -1855,13 +1870,13 @@ is a number of cores)
- Fix view mode header buttons overlapping in small resolutions [Taiga #5058](https://tree.taiga.io/project/penpot/issue/5058)
- Fix precision for wrap in flex [Taiga #5072](https://tree.taiga.io/project/penpot/issue/5072)
- Fix relative position overlay positioning [Taiga #5092](https://tree.taiga.io/project/penpot/issue/5092)
- Fix hide grid keyboard shortcut [Github #3071](https://github.com/penpot/penpot/pull/3071)
- Fix hide grid keyboard shortcut [GH #3071](https://github.com/penpot/penpot/pull/3071)
- Fix problem with opacity in imported SVG's [Taiga #4923](https://tree.taiga.io/project/penpot/issue/4923)
### :heart: Community contributions by (Thank you!)
- To @ondrejkonec: for contributing to the code with:
- Refactor CSS variables [Github #2948](https://github.com/penpot/penpot/pull/2948)
- Refactor CSS variables [GH #2948](https://github.com/penpot/penpot/pull/2948)
## 1.17.3
@ -1878,7 +1893,7 @@ is a number of cores)
### :sparkles: Enhancements
- Adds environment variables for specifying the export and backend URI for the frontend docker image, thanks to @Supernova3339 for the initial PR and suggestion [Github #2984](https://github.com/penpot/penpot/issues/2984)
- Adds environment variables for specifying the export and backend URI for the frontend docker image, thanks to @Supernova3339 for the initial PR and suggestion [GH #2984](https://github.com/penpot/penpot/issues/2984)
## 1.17.2
@ -1946,13 +1961,13 @@ is a number of cores)
- Fix problem with text edition in Safari [Taiga #4046](https://tree.taiga.io/project/penpot/issue/4046)
- Fix show outline with rounded corners on rects [Taiga #4053](https://tree.taiga.io/project/penpot/issue/4053)
- Fix wrong interaction between comments and panning modes [Taiga #4297](https://tree.taiga.io/project/penpot/issue/4297)
- Fix bad element positioning on interaction with fixed scroll [Github #2660](https://github.com/penpot/penpot/issues/2660)
- Fix bad element positioning on interaction with fixed scroll [GH #2660](https://github.com/penpot/penpot/issues/2660)
- Fix display type of component library not persistent [Taiga #4512](https://tree.taiga.io/project/penpot/issue/4512)
- Fix problem when moving texts with keyboard [#2690](https://github.com/penpot/penpot/issues/2690)
- Fix problem when drawing boxes won't detect mouse-up [Taiga #4618](https://tree.taiga.io/project/penpot/issue/4618)
- Fix missing loading icon on shared libraries [Taiga #4148](https://tree.taiga.io/project/penpot/issue/4148)
- Fix selection stroke missing in properties of multiple texts [Taiga #4048](https://tree.taiga.io/project/penpot/issue/4048)
- Fix missing create component menu for frames [Github #2670](https://github.com/penpot/penpot/issues/2670)
- Fix missing create component menu for frames [GH #2670](https://github.com/penpot/penpot/issues/2670)
- Fix "currentColor" is not converted when importing SVG [Github 2276](https://github.com/penpot/penpot/issues/2276)
- Fix incorrect color in properties of multiple bool shapes [Taiga #4355](https://tree.taiga.io/project/penpot/issue/4355)
- Fix pressing the enter key gives you an internal error [Github 2675](https://github.com/penpot/penpot/issues/2675) [Github 2577](https://github.com/penpot/penpot/issues/2577)
@ -1961,10 +1976,10 @@ is a number of cores)
- Fix wrong update of text in components [Taiga #4646](https://tree.taiga.io/project/penpot/issue/4646)
- Fix problem with SVG imports with style [#2605](https://github.com/penpot/penpot/issues/2605)
- Fix ghost shapes after sync groups in components [Taiga #4649](https://tree.taiga.io/project/penpot/issue/4649)
- Fix layer orders messed up on move, group, reparent and undo [Github #2672](https://github.com/penpot/penpot/issues/2672)
- Fix max height in library dialog [Github #2335](https://github.com/penpot/penpot/issues/2335)
- Fix layer orders messed up on move, group, reparent and undo [GH #2672](https://github.com/penpot/penpot/issues/2672)
- Fix max height in library dialog [GH #2335](https://github.com/penpot/penpot/issues/2335)
- Fix undo ungroup (shift+g) scrambles positions [Taiga #4674](https://tree.taiga.io/project/penpot/issue/4674)
- Fix justified text is stretched [Github #2539](https://github.com/penpot/penpot/issues/2539)
- Fix justified text is stretched [GH #2539](https://github.com/penpot/penpot/issues/2539)
- Fix mousewheel on viewer inspector [Taiga #4221](https://tree.taiga.io/project/penpot/issue/4221)
- Fix path edition activated on boards [Taiga #4105](https://tree.taiga.io/project/penpot/issue/4105)
- Fix hidden layers inside groups become visible after the group visibility is changed[Taiga #4710](https://tree.taiga.io/project/penpot/issue/4710)
@ -1987,7 +2002,7 @@ is a number of cores)
### :bug: Bugs fixed
- Fix strage cursor behaviour after clicking viewport with text pool [Github #2447](https://github.com/penpot/penpot/issues/2447)
- Fix strage cursor behaviour after clicking viewport with text pool [GH #2447](https://github.com/penpot/penpot/issues/2447)
## 1.16.1-beta
@ -1998,7 +2013,7 @@ is a number of cores)
- Fix justify alignes text left [Taiga #4322](https://tree.taiga.io/project/penpot/issue/4322)
- Fix text out of borders with "auto width" and center align [Taiga #4308](https://tree.taiga.io/project/penpot/issue/4308)
- Fix wrong validation text after interaction with 2 and more files [Taiga #4276](https://tree.taiga.io/project/penpot/issue/4276)
- Fix auto-width for texts can make text appear stretched [Github #2482](https://github.com/penpot/penpot/issues/2482)
- Fix auto-width for texts can make text appear stretched [GH #2482](https://github.com/penpot/penpot/issues/2482)
- Fix boards name do not disappear in focus mode [#4272](https://tree.taiga.io/project/penpot/issue/4272)
- Fix wrong email in the info message at change email [Taiga #4274](https://tree.taiga.io/project/penpot/issue/4274)
- Fix transform to path RMB menu item is not relevant if shape is already path [Taiga #4302](https://tree.taiga.io/project/penpot/issue/4302)
@ -2137,7 +2152,7 @@ is a number of cores)
- Fix problems with double-click and selection [Taiga #4005](https://tree.taiga.io/project/penpot/issue/4005)
- Fix mismatch between editor and displayed text in workspace [Taiga #3975](https://tree.taiga.io/project/penpot/issue/3975)
- Fix validation error on text position [Taiga #4010](https://tree.taiga.io/project/penpot/issue/4010)
- Fix objects jitter while scrolling [Github #2167](https://github.com/penpot/penpot/issues/2167)
- Fix objects jitter while scrolling [GH #2167](https://github.com/penpot/penpot/issues/2167)
- Fix on color-picker, click+drag adds lots of recent colors [Taiga #4013](https://tree.taiga.io/project/penpot/issue/4013)
- Fix opening profile URL while signed out takes to "your account" section[Taiga #3976](https://tree.taiga.io/project/penpot/issue/3976)
@ -2447,7 +2462,7 @@ is a number of cores)
- Scroll bars [Taiga #2550](https://tree.taiga.io/project/penpot/task/2550)
- Add select layer option to context menu [Taiga #2474](https://tree.taiga.io/project/penpot/us/2474)
- Guides [Taiga #290](https://tree.taiga.io/project/penpot/us/290)
- Improve file menu by adding semantically groups [Github #1203](https://github.com/penpot/penpot/issues/1203)
- Improve file menu by adding semantically groups [GH #1203](https://github.com/penpot/penpot/issues/1203)
- Add update components in bulk option in context menu [Taiga #1975](https://tree.taiga.io/project/penpot/us/1975)
- Create first E2E tests [Taiga #2608](https://tree.taiga.io/project/penpot/task/2608), [Taiga #2608](https://tree.taiga.io/project/penpot/task/2608)
- Redesign of workspace toolbars [Taiga #2319](https://tree.taiga.io/project/penpot/us/2319)
@ -2515,7 +2530,7 @@ is a number of cores)
- Add shortcut to create artboard from selected objects [Taiga #2412](https://tree.taiga.io/project/penpot/us/2412)
- Add shortcut for opacity [Taiga #2442](https://tree.taiga.io/project/penpot/us/2442)
- Setting fill automatically for new texts [Taiga #2441](https://tree.taiga.io/project/penpot/us/2441)
- Add shortcut to move action [Github #1213](https://github.com/penpot/penpot/issues/1213)
- Add shortcut to move action [GH #1213](https://github.com/penpot/penpot/issues/1213)
- Add alt as mod key to add stroke color from library menu [Taiga #2207](https://tree.taiga.io/project/penpot/us/2207)
- Add detach in bulk option to context menu [Taiga #2210](https://tree.taiga.io/project/penpot/us/2210)
- Add penpot look and feel to multiuser cursors [Taiga #1387](https://tree.taiga.io/project/penpot/us/1387)

View File

@ -13,6 +13,7 @@
[app.common.media :as cm]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.types.font :as types.font]
[app.common.uuid :as uuid]
[app.db :as db]
[app.db.sql :as-alias sql]
@ -99,7 +100,7 @@
[:map {:title "create-font-variant"}
[:team-id ::sm/uuid]
[:font-id ::sm/uuid]
[:font-family ::sm/text]
[:font-family types.font/schema:font-family]
[:font-weight [::sm/one-of {:format "number"} valid-weight]]
[:font-style [::sm/one-of {:format "string"} valid-style]]
[:data {:optional true} [:map-of ::sm/text [:or ::sm/bytes [::sm/vec ::sm/bytes]]]]
@ -277,7 +278,7 @@
[:map {:title "update-font"}
[:team-id ::sm/uuid]
[:id ::sm/uuid]
[:name :string]])
[:name types.font/schema:font-family]])
(sv/defmethod ::update-font
{::doc/added "1.18"

View File

@ -825,3 +825,100 @@
(t/is (some? (:error out)))
(t/is (= :validation (-> out :error ex-data :type)))
(t/is (= :media-type-not-allowed (-> out :error ex-data :code))))))
;; --- Font family name validation / XSS prevention
(t/deftest create-font-variant-with-invalid-family
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
(let [prof (th/create-profile* 1 {:is-active true})
team-id (:default-team-id prof)
font-id (uuid/custom 10 100)
data (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*))]
;; name with < should fail
(let [params {::th/type :create-font-variant
::rpc/profile-id (:id prof)
:team-id team-id :font-id font-id
:font-family "evil<script>alert(1)</script>"
:font-weight 400 :font-style "normal"
:data {"font/ttf" data}}
out (th/command! params)]
(t/is (not (th/success? out)))
(t/is (th/ex-of-type? (:error out) :validation))
(t/is (th/ex-of-code? (:error out) :params-validation)))
;; name with ' should fail
(let [params {::th/type :create-font-variant
::rpc/profile-id (:id prof)
:team-id team-id :font-id font-id
:font-family "evil'name"
:font-weight 400 :font-style "normal"
:data {"font/ttf" data}}
out (th/command! params)]
(t/is (not (th/success? out)))
(t/is (th/ex-of-type? (:error out) :validation)))
;; name with } should fail
(let [params {::th/type :create-font-variant
::rpc/profile-id (:id prof)
:team-id team-id :font-id font-id
:font-family "evil}name"
:font-weight 400 :font-style "normal"
:data {"font/ttf" data}}
out (th/command! params)]
(t/is (not (th/success? out)))
(t/is (th/ex-of-type? (:error out) :validation)))
;; valid name should succeed
(let [params {::th/type :create-font-variant
::rpc/profile-id (:id prof)
:team-id team-id :font-id (uuid/custom 10 101)
:font-family "Source Sans Pro"
:font-weight 400 :font-style "normal"
:data {"font/ttf" data}}
out (th/command! params)]
(t/is (th/success? out))))))
(t/deftest update-font-with-invalid-family
(with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}]
(let [prof (th/create-profile* 1 {:is-active true})
team-id (:default-team-id prof)
font-id (uuid/custom 10 102)
data (-> (io/resource "backend_tests/test_files/font-1.ttf") (io/read*))]
;; Create a valid font first
(let [params {::th/type :create-font-variant
::rpc/profile-id (:id prof)
:team-id team-id :font-id font-id
:font-family "ValidFont"
:font-weight 400 :font-style "normal"
:data {"font/ttf" data}}
out (th/command! params)]
(t/is (th/success? out)))
;; rename with < should fail
(let [params {::th/type :update-font
::rpc/profile-id (:id prof)
:team-id team-id :id font-id
:name "evil<script>x</script>"}
out (th/command! params)]
(t/is (not (th/success? out)))
(t/is (th/ex-of-type? (:error out) :validation))
(t/is (th/ex-of-code? (:error out) :params-validation)))
;; rename with ' should fail
(let [params {::th/type :update-font
::rpc/profile-id (:id prof)
:team-id team-id :id font-id
:name "evil'name"}
out (th/command! params)]
(t/is (not (th/success? out)))
(t/is (th/ex-of-type? (:error out) :validation)))
;; valid rename should succeed
(let [params {::th/type :update-font
::rpc/profile-id (:id prof)
:team-id team-id :id font-id
:name "Valid Font Name"}
out (th/command! params)]
(t/is (th/success? out))))))

View File

@ -0,0 +1,21 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.types.font
(:require
[app.common.schema :as sm]))
(def ^:private font-family-re
;; \p{L} (Unicode letter) works in Java regex natively, but in JavaScript it
;; requires the "u" flag which ClojureScript regex literals don't support.
#?(:clj #"[\p{L}\d _.-]+"
:cljs (js/RegExp. "[\\p{L}\\d _.-]+" "u")))
(def schema:font-family
[:and
[::sm/text {:max 250}]
[:fn {:error/code "errors.font-family-invalid-chars"}
(fn [s] (boolean (re-matches font-family-re s)))]])

View File

@ -0,0 +1,41 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns common-tests.types.font-test
(:require
[app.common.schema :as sm]
[app.common.types.font :as ctf]
[clojure.test :as t]))
(t/deftest font-family-schema-valid
(t/is (sm/validate ctf/schema:font-family "Source Sans Pro"))
(t/is (sm/validate ctf/schema:font-family "Roboto"))
(t/is (sm/validate ctf/schema:font-family "Open Sans 300"))
(t/is (sm/validate ctf/schema:font-family "Font-Name_v2"))
(t/is (sm/validate ctf/schema:font-family "Noto Sans CJK SC"))
(t/is (sm/validate ctf/schema:font-family "A"))
;; hyphens, underscores and dots are allowed
(t/is (sm/validate ctf/schema:font-family "Fira-Code"))
(t/is (sm/validate ctf/schema:font-family "font_name"))
(t/is (sm/validate ctf/schema:font-family "Soucre Sans Pro 3.0"))
;; Unicode letters are allowed
(t/is (sm/validate ctf/schema:font-family "思源黑体"))
(t/is (sm/validate ctf/schema:font-family "العربية")))
(t/deftest font-family-schema-invalid
;; HTML injection characters
(t/is (not (sm/validate ctf/schema:font-family "evil<script>")))
(t/is (not (sm/validate ctf/schema:font-family "<test>name")))
;; CSS injection characters
(t/is (not (sm/validate ctf/schema:font-family "evil'name")))
(t/is (not (sm/validate ctf/schema:font-family "evil\"name")))
(t/is (not (sm/validate ctf/schema:font-family "evil}name")))
(t/is (not (sm/validate ctf/schema:font-family "evil;name")))
(t/is (not (sm/validate ctf/schema:font-family "evil\\name")))
;; empty string
(t/is (not (sm/validate ctf/schema:font-family "")))
;; too long
(t/is (not (sm/validate ctf/schema:font-family (apply str (repeat 251 "a"))))))

File diff suppressed because one or more lines are too long

View File

@ -109,7 +109,7 @@ Because **remote MCP** does not expose local file-system access:
## Quick start
If you just want to try Penpot MCP quickly, follow this path for the **hosted (remote) MCP server**.
If you just want to try Penpot AI workflows quickly through the MCP, follow this path for the **hosted (remote) MCP server**.
### Remote MCP in 5 steps

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

After

Width:  |  Height:  |  Size: 321 KiB

View File

@ -3,17 +3,17 @@
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Penpot - Design Freedom for Teams</title>
<title>Penpot | Full-stack design</title>
<meta name="description" content="The open-source solution for design and prototyping.">
<meta name="description" content="Penpot is the open-source design platform for teams that build digital products at scale.">
<meta property="og:locale" content="en_US">
<meta property="og:title" content="Penpot | Design Freedom for Teams">
<meta property="og:description" content="The open-source solution for design and prototyping">
<meta property="og:image" content="https://penpot.app/images/workspace-ui.jpg">
<meta name="twitter:title" content="Penpot | Design Freedom for Teams">
<meta property="og:title" content="Penpot | Full-stack design">
<meta property="og:description" content="Penpot is the open-source design platform for teams that build digital products at scale.">
<meta property="og:image" content="/images/penpot-link-preview.png">
<meta name="twitter:title" content="Penpot | Full-stack design">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:description" content="The open-source solution for design and prototyping">
<meta name="twitter:image" content="https://penpot.app/images/workspace-ui.jpg">
<meta name="twitter:description" content="Penpot is the open-source design platform for teams that build digital products at scale.">
<meta name="twitter:image" content="/images/penpot-link-preview.png">
<meta name="twitter:site" content="@penpotapp">
<meta name="twitter:creator" content="@penpotapp">
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)">

View File

@ -95,7 +95,7 @@
([text]
(-> (dom/create-element "span")
(dom/set-data! "type" "text")
(dom/set-html! (if (empty? text) zero-width-space text)))))
(dom/set-html! (if (empty? text) zero-width-space (dom/escape-html text))))))
(defn- create-mention-node
"Creates a mention node"
@ -334,7 +334,7 @@
after-span (create-text-node (dm/str " " suffix))
sel (wapi/get-selection)]
(dom/set-html! span-node (if (empty? prefix) zero-width-space prefix))
(dom/set-html! span-node (if (empty? prefix) zero-width-space (dom/escape-html prefix)))
(dom/insert-after! node span-node mention-span)
(dom/insert-after! node mention-span after-span)
(wapi/set-cursor-after! after-span)
@ -351,7 +351,7 @@
(let [node-text (dom/get-text span-node)
at-symbol (if (blank-content? node-text) "@" " @")]
(dom/set-html! span-node (str/concat node-text at-symbol))
(dom/set-html! span-node (str/concat (dom/escape-html node-text) at-symbol))
(wapi/set-cursor-after! span-node))))))
handle-key-down
@ -399,7 +399,7 @@
(when span-node
(let [txt (.-textContent span-node)]
(dom/set-html! span-node (dm/str (subs txt 0 offset) "\n" zero-width-space (subs txt offset)))
(dom/set-html! span-node (dm/str (dom/escape-html (subs txt 0 offset)) "\n" zero-width-space (dom/escape-html (subs txt offset))))
(wapi/set-cursor! span-node (inc offset))
(handle-input)))))

View File

@ -11,6 +11,8 @@
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.media :as cm]
[app.common.schema :as sm]
[app.common.types.font :as ctf]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.fonts :as df]
@ -139,7 +141,8 @@
(dom/get-data "id")
(uuid/parse))
name (dom/get-value target)]
(when-not (str/blank? name)
(when (and (not (str/blank? name))
(sm/validate ctf/schema:font-family name))
(swap! fonts* df/rename-and-regroup id name installed-fonts)))))
on-change-name
@ -320,7 +323,9 @@
(fn [_]
(reset! edition* false)
(when-not (str/blank? font-family)
(st/emit! (df/update-font {:id font-id :name font-family})))))
(if (sm/validate ctf/schema:font-family font-family)
(st/emit! (df/update-font {:id font-id :name font-family}))
(st/emit! (ntf/error (tr "errors.font-family-invalid-chars")))))))
on-key-down
(mf/use-fn

View File

@ -19,6 +19,7 @@
[app.main.ui.ds.controls.utilities.token-field :refer [token-field*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list] :as i]
[app.main.ui.formats :as fmt]
[app.main.ui.hooks :as h]
[app.main.ui.workspace.tokens.management.forms.controls.utils :as csu]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
@ -393,6 +394,8 @@
(on-blur event))
(dom/blur! (mf/ref-val ref))))
handle-unmount (h/use-ref-callback handle-blur)
on-key-down
(mf/use-fn
(mf/deps is-open apply-value update-input is-open focused-id handle-focus-change)
@ -768,6 +771,8 @@
(mf/with-effect [dropdown-options]
(mf/set-ref-val! options-ref dropdown-options))
(mf/with-effect [handle-unmount] handle-unmount)
[:div {:class [class (stl/css-case :input-wrapper true
:resizable (not is-token-applied?))]
:ref wrapper-ref

View File

@ -510,12 +510,13 @@
(mf/deps file-id)
(fn [event]
(when-not updating?
(let [library-id (some-> (dom/get-target event)
(let [library-id (some-> (dom/get-current-target event)
(dom/get-data "library-id")
(uuid/parse))]
(st/emit!
(dwl/set-updating-library true)
(dwl/sync-file file-id library-id))))))]
(when library-id
(st/emit!
(dwl/set-updating-library true)
(dwl/sync-file file-id library-id)))))))]
[:div {:class (stl/css :updates-content)}
[:div {:class (stl/css :update-section)}

View File

@ -240,7 +240,7 @@
on-checkbox-click
(mf/use-fn
(mf/deps id on-toggle)
(mf/deps set on-toggle)
(fn [event]
(dom/stop-propagation event)
(when (fn? on-toggle)

View File

@ -1026,7 +1026,7 @@
:else
(do (st/emit!
(dwsl/create-layout-from-id id :flex :from-frame? true :calculate-params? false)
(se/event plugin-id "create-layout" :layout "flex"))
(se/event plugin-id "create-shape-layout" :layout "flex"))
(flex/flex-layout-proxy plugin-id file-id page-id id)))))
:addGridLayout
@ -1041,7 +1041,7 @@
:else
(do (st/emit! (dwsl/create-layout-from-id id :grid :from-frame? true :calculate-params? false))
(se/event plugin-id "create-layout" :layout "grid")
(se/event plugin-id "create-shape-layout" :layout "grid")
(grid/grid-layout-proxy plugin-id file-id page-id id)))))
;; Make masks for groups

View File

@ -331,6 +331,18 @@
([document ^js text]
(.createTextNode document text)))
(defn escape-html
"Escapes special HTML characters in a string so that it can be safely used
as innerHTML without risk of XSS."
[^js text]
(when (some? text)
(-> text
(str/replace "&" "&amp;")
(str/replace "<" "&lt;")
(str/replace ">" "&gt;")
(str/replace "\"" "&quot;")
(str/replace "'" "&#39;"))))
(defn set-html!
[^js el html]
(when (some? el)

View File

@ -169,7 +169,7 @@ msgstr "Create an account"
#: src/app/main/ui/auth.cljs
#, unused
msgid "auth.sidebar-tagline"
msgstr "The open-source solution for design and prototyping."
msgstr "Penpot is the open-source design platform for teams that build digital products at scale."
#: src/app/main/ui/auth/register.cljs:51
#, markdown
@ -1548,6 +1548,10 @@ msgstr "Invalid text"
msgid "errors.team-name-invalid-chars"
msgstr "The team name can't contain any of the following characters:'.', ':' or '/'"
#: common/src/app/common/types/font.cljc
msgid "errors.font-family-invalid-chars"
msgstr "The font family name can only contain letters, numbers, spaces, hyphens, underscores, and dots."
#: src/app/main/ui/static.cljs:74
msgid "errors.invite-invalid"
msgstr "Invite invalid"
@ -5458,7 +5462,7 @@ msgstr "Shared Libraries - %s - Penpot"
#: src/app/main/ui/auth/verify_token.cljs:70, src/app/main/ui/auth.cljs:34
msgid "title.default"
msgstr "Penpot - Design Freedom for Teams"
msgstr "Penpot | Full-stack design"
#: src/app/main/ui/settings/feedback.cljs:161
msgid "title.settings.feedback"

View File

@ -270,7 +270,6 @@ The Penpot MCP server can be configured using environment variables.
| `PENPOT_MCP_SERVER_PORT` | Port for the HTTP/SSE server | `4401` |
| `PENPOT_MCP_WEBSOCKET_PORT` | Port for the WebSocket server (plugin connection) | `4402` |
| `PENPOT_MCP_REPL_PORT` | Port for the REPL server (development/debugging) | `4403` |
| `PENPOT_MCP_SERVER_ADDRESS` | Hostname or IP address via which clients can reach the MCP server | `localhost` |
| `PENPOT_MCP_REMOTE_MODE` | Enable remote mode (disables file system access). Set to `true` to enable. | `false` |
| `PENPOT_MCP_DEVENV` | Enable Penpot development environment tools. Set to `true` to enable. | `false` |

View File

@ -1,4 +0,0 @@
{
"$schema": "https://opencode.ai/config.json",
"instructions": ["AGENTS.md"]
}

View File

@ -3457,6 +3457,9 @@ impl RenderState {
tile_rect,
);
}
} else {
// Tile is uncached and has no shapes to render
self.apply_render_to_final_canvas(tile_rect)?;
}
}
}

View File

@ -47,7 +47,14 @@ impl GpuState {
})
}
fn create_webgl_texture(&mut self, width: i32, height: i32) -> gl::types::GLuint {
fn delete_gl_texture(&mut self, texture_id: gl::types::GLuint) -> bool {
unsafe {
gl::DeleteTextures(1, &texture_id);
gl::GetError() == 0
}
}
fn create_gl_texture(&mut self, width: i32, height: i32) -> gl::types::GLuint {
let mut texture_id: gl::types::GLuint = 0;
unsafe {
@ -75,6 +82,19 @@ impl GpuState {
texture_id
}
pub fn delete_surface(&mut self, surface: &mut skia::Surface) -> bool {
let Some(texture) = skia::gpu::surfaces::get_backend_texture(
surface,
skia_safe::surface::BackendHandleAccess::FlushRead,
) else {
return false;
};
let Some(texture_info) = gpu::backend_textures::get_gl_texture_info(&texture) else {
return false;
};
self.delete_gl_texture(texture_info.id)
}
pub fn create_surface_with_isize(
&mut self,
label: String,
@ -90,7 +110,7 @@ impl GpuState {
height: i32,
) -> Result<skia::Surface> {
let backend_texture = unsafe {
let texture_id = self.create_webgl_texture(width, height);
let texture_id = self.create_gl_texture(width, height);
let texture_info = TextureInfo {
target: gl::TEXTURE_2D,
id: texture_id,

View File

@ -314,6 +314,7 @@ impl Surfaces {
self.atlas_origin = skia::Point::new(new_left, new_top);
self.atlas_size = skia::ISize::new(new_w, new_h);
self.atlas_scale = new_scale;
gpu_state.delete_surface(&mut self.atlas);
self.atlas = new_atlas;
Ok(())
}

View File

@ -929,14 +929,17 @@ impl TextContent {
match self.grow_type() {
GrowType::AutoHeight => {
let result = self.text_layout_auto_height();
self.layout_width = Some(result.2.width);
self.set_layout_from_result(result, selrect.width(), selrect.height());
}
GrowType::AutoWidth => {
let result = self.text_layout_auto_width();
self.layout_width = Some(result.2.width);
self.set_layout_from_result(result, selrect.width(), selrect.height());
}
GrowType::Fixed => {
let result = self.text_layout_fixed();
self.layout_width = Some(result.2.width);
self.set_layout_from_result(result, selrect.width(), selrect.height());
}
}
@ -949,8 +952,6 @@ impl TextContent {
}
self.layout_version = self.content_version;
self.layout_width = Some(selrect.width());
self.size
}

370
tools/gh.py Executable file
View File

@ -0,0 +1,370 @@
#!/usr/bin/env python3
"""
gh.py Multi-purpose CLI helper for penpot/penpot GitHub operations.
Uses GitHub GraphQL and REST APIs via the authenticated ``gh`` CLI.
Subcommands:
issues List issues in a milestone
prs Fetch details for one or more PRs
Usage:
python3 tools/gh.py issues <milestone-title> (default: state=closed)
python3 tools/gh.py issues "2.16.0" --state all
python3 tools/gh.py issues "2.16.0" --exclude "release blocker,no changelog"
python3 tools/gh.py issues "2.16.0" --compare CHANGES.md
python3 tools/gh.py prs 9179 9204 9311
python3 tools/gh.py prs --file prs.txt
cat prs.txt | python3 tools/gh.py prs --stdin
Prerequisites:
- gh CLI authenticated (gh auth status)
- Python 3.8+
"""
import argparse
import json
import re
import subprocess
import sys
from typing import Any
REPO = "penpot/penpot"
OWNER = "penpot"
REPO_NAME = "penpot"
# ─────────────────────────────────────────────
# Shared helpers
# ─────────────────────────────────────────────
def run_gh(method: str, endpoint: str, **kwargs: Any) -> Any:
"""Run a ``gh api`` call and return parsed JSON."""
cmd = ["gh", "api", endpoint, "--method", method]
for key, val in kwargs.items():
if val is not None:
cmd.extend(["-f", f"{key}={val}"])
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"gh error: {result.stderr}", file=sys.stderr)
sys.exit(1)
return json.loads(result.stdout)
def run_gh_graphql(query: str, variables: dict) -> Any:
"""Run a GraphQL query via ``gh api graphql --input -``."""
payload = json.dumps({"query": query, "variables": variables})
cmd = ["gh", "api", "graphql", "--input", "-"]
result = subprocess.run(cmd, input=payload, capture_output=True, text=True)
if result.returncode != 0:
print(f"gh error: {result.stderr}", file=sys.stderr)
sys.exit(1)
body = json.loads(result.stdout)
if "errors" in body:
for err in body["errors"]:
print(f"GraphQL error: {err.get('message')}", file=sys.stderr)
sys.exit(1)
return body["data"]
# ─────────────────────────────────────────────
# Subcommand: issues
# ─────────────────────────────────────────────
GQL_ISSUES_QUERY = """\
query($owner: String!, $repo: String!, $milestone: Int!, $cursor: String) {
repository(owner: $owner, name: $repo) {
milestone(number: $milestone) {
issues(first: 100, after: $cursor, states: __STATES__) {
totalCount
pageInfo { hasNextPage endCursor }
nodes {
... on Issue {
number
title
state
labels(first: 20) { nodes { name } }
closedByPullRequestsReferences(first: 5) { nodes { number } }
}
}
}
}
}
}
"""
def find_milestone(title: str) -> dict:
"""Look up milestone by title, return {number, title, open_issues, closed_issues}."""
data = run_gh("GET", f"repos/{OWNER}/{REPO_NAME}/milestones?per_page=100&state=all")
for ms in data:
if ms["title"] == title:
return {
"number": ms["number"],
"title": ms["title"],
"open_issues": ms["open_issues"],
"closed_issues": ms["closed_issues"],
}
print(f"ERROR: Milestone \"{title}\" not found in {REPO}", file=sys.stderr)
sys.exit(1)
def fetch_milestone_issues(milestone_num: int, states: str) -> list[dict]:
"""
Fetch all issues in a milestone via paginated GraphQL.
Args:
milestone_num: milestone number
states: GraphQL states enum array literal, e.g. ``"[CLOSED]"`` or ``"[OPEN CLOSED]"``
Returns:
List of {number, title, state, labels: [str], closing_prs: [int]}
"""
query = GQL_ISSUES_QUERY.replace("__STATES__", states)
all_nodes: list[dict] = []
cursor: str | None = None
while True:
variables: dict[str, Any] = {
"owner": OWNER,
"repo": REPO_NAME,
"milestone": milestone_num,
"cursor": cursor,
}
data = run_gh_graphql(query, variables)
issues = data["repository"]["milestone"]["issues"]
page_info = issues["pageInfo"]
for node in issues["nodes"]:
if node is None:
continue
all_nodes.append({
"number": node["number"],
"title": node["title"],
"state": node["state"],
"labels": [lbl["name"] for lbl in node["labels"]["nodes"]],
"closing_prs": [pr["number"] for pr in node["closedByPullRequestsReferences"]["nodes"]],
})
total = len(all_nodes)
print(f" ... fetched {total} issues so far", file=sys.stderr)
if not page_info["hasNextPage"]:
break
cursor = page_info["endCursor"]
return all_nodes
def load_existing_issue_numbers(filepath: str) -> set[int]:
"""Parse all ``#NNNN`` references from a file (e.g. CHANGES.md)."""
pattern = re.compile(r"#(\d{3,5})\b")
nums: set[int] = set()
with open(filepath) as f:
for line in f:
for m in pattern.finditer(line):
nums.add(int(m.group(1)))
return nums
def cmd_issues(args: argparse.Namespace) -> None:
"""Handle the ``issues`` subcommand."""
# Resolve milestone
print(f"Looking up milestone \"{args.milestone}\"...", file=sys.stderr)
ms = find_milestone(args.milestone)
print(f"Milestone #{ms['number']}: {ms['open_issues']} open, {ms['closed_issues']} closed",
file=sys.stderr)
# Map state to GraphQL enum array literal
state_map = {"open": "[OPEN]", "closed": "[CLOSED]", "all": "[OPEN CLOSED]"}
gql_states = state_map[args.state]
# Fetch issues
print(f"Fetching {args.state} issues via GraphQL...", file=sys.stderr)
issues = fetch_milestone_issues(ms["number"], gql_states)
print(f"Fetched {len(issues)} issues total", file=sys.stderr)
# Filter by excluded labels
if args.exclude:
exclusions = set(label.strip() for label in args.exclude.split(","))
filtered = [issue for issue in issues
if not any(lbl in exclusions for lbl in issue["labels"])]
print(f"After excluding labels: {len(filtered)} issues", file=sys.stderr)
issues = filtered
# Filter to issues NOT yet in the comparison file (if --compare given)
if args.compare:
existing_nums = load_existing_issue_numbers(args.compare)
missing = [iss for iss in issues if iss["number"] not in existing_nums]
missing.sort(key=lambda x: x["number"])
print(f"Issues not yet in changelog: {len(missing)}", file=sys.stderr)
issues = missing
print(json.dumps(issues, indent=2))
# ─────────────────────────────────────────────
# Subcommand: prs
# ─────────────────────────────────────────────
PRS_BATCH_SIZE = 50
GQL_PRS_QUERY_ITEM = """\
pr_{num}: pullRequest(number: {num}) {{
number
title
body
state
mergedAt
createdAt
author {{ login }}
labels(first: 20) {{ nodes {{ name }} }}
closingIssuesReferences(first: 5) {{ nodes {{ number }} }}
}}
"""
GQL_PRS_QUERY_WRAPPER = """\
query($owner: String!, $repo: String!) {{
repository(owner: $owner, name: $repo) {{
{items}
}}
}}
"""
def fetch_prs_batch(pr_numbers: list[int]) -> list[dict]:
"""
Fetch details for a list of PR numbers in a single GraphQL query.
Uses numbered aliases (pr_1234, pr_5678, ) so each PR is looked up by
number in one round-trip. Returns entries in the same order as the input.
"""
items = "\n".join(
GQL_PRS_QUERY_ITEM.format(num=n) for n in pr_numbers
)
query = GQL_PRS_QUERY_WRAPPER.format(items=items)
variables = {"owner": OWNER, "repo": REPO_NAME}
data = run_gh_graphql(query, variables)
repo = data["repository"]
results: list[dict] = []
for num in pr_numbers:
pr = repo.get(f"pr_{num}")
if pr is None:
results.append({
"number": num,
"error": "not_found",
})
continue
results.append({
"number": pr["number"],
"title": pr["title"],
"body": pr.get("body"),
"state": pr["state"],
"merged_at": pr.get("mergedAt"),
"created_at": pr.get("createdAt"),
"author": pr["author"]["login"] if pr["author"] else None,
"labels": [lbl["name"] for lbl in pr["labels"]["nodes"]],
"closing_issues": [iss["number"] for iss in pr["closingIssuesReferences"]["nodes"]],
})
return results
def cmd_prs(args: argparse.Namespace) -> None:
"""Handle the ``prs`` subcommand."""
# Collect PR numbers from args / file / stdin
pr_numbers: list[int] = []
if args.numbers:
pr_numbers.extend(args.numbers)
if args.file:
with open(args.file) as f:
for line in f:
line = line.strip()
if line:
pr_numbers.append(int(line))
if args.stdin:
for line in sys.stdin:
line = line.strip()
if line:
pr_numbers.append(int(line))
if not pr_numbers:
print("ERROR: no PR numbers provided (pass numbers, --file, or --stdin)",
file=sys.stderr)
sys.exit(1)
# Deduplicate while preserving order
seen: set[int] = set()
pr_numbers = [n for n in pr_numbers if not (n in seen or seen.add(n))]
print(f"Fetching {len(pr_numbers)} PRs in batches of {PRS_BATCH_SIZE}...",
file=sys.stderr)
all_results: list[dict] = []
for i in range(0, len(pr_numbers), PRS_BATCH_SIZE):
batch = pr_numbers[i : i + PRS_BATCH_SIZE]
print(f" batch {i // PRS_BATCH_SIZE + 1}: PRs {batch[0]}..{batch[-1]}",
file=sys.stderr)
all_results.extend(fetch_prs_batch(batch))
print(json.dumps(all_results, indent=2))
# ─────────────────────────────────────────────
# CLI entrypoint
# ─────────────────────────────────────────────
def main() -> None:
parser = argparse.ArgumentParser(
description="Multi-purpose CLI helper for penpot/penpot GitHub operations"
)
sub = parser.add_subparsers(dest="command", required=True, title="subcommands")
# --- issues ---
p_issues = sub.add_parser("issues", help="List issues in a milestone")
p_issues.add_argument("milestone", help="Milestone title, e.g. '2.16.0'")
p_issues.add_argument(
"--state", choices=["open", "closed", "all"], default="closed",
help="Issue state filter (default: closed)"
)
p_issues.add_argument(
"--exclude", "--exclude-labels",
help="Comma-separated labels to exclude, e.g. 'release blocker,no changelog'"
)
p_issues.add_argument(
"--compare",
help="Path to CHANGES.md; only show issues NOT yet referenced in that file"
)
p_issues.set_defaults(func=cmd_issues)
# --- prs ---
p_prs = sub.add_parser("prs", help="Fetch details for one or more PRs")
p_prs.add_argument(
"numbers", type=int, nargs="*",
help="PR numbers to fetch (space-separated)"
)
p_prs.add_argument(
"--file", type=str,
help="File with one PR number per line"
)
p_prs.add_argument(
"--stdin", action="store_true",
help="Read PR numbers from stdin (one per line)"
)
p_prs.set_defaults(func=cmd_prs)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()

261
tools/taiga.py Executable file
View File

@ -0,0 +1,261 @@
#!/usr/bin/env python3
"""
Taiga API client fetch public issues, user stories, and tasks from the
Penpot project (id 345963) without authentication.
Usage:
python3 tools/taiga.py <taiga-url>
python3 tools/taiga.py <type> <ref>
python3 tools/taiga.py [--json] <taiga-url>
python3 tools/taiga.py [--json] <type> <ref>
Examples:
python3 tools/taiga.py https://tree.taiga.io/project/penpot/issue/13714
python3 tools/taiga.py --json https://tree.taiga.io/project/penpot/us/14128
python3 tools/taiga.py task 13648
"""
import argparse
import json
import re
import sys
import urllib.error
import urllib.request
API_BASE = "https://api.taiga.io/api/v1"
PROJECT_ID = 345963
ENDPOINT_MAP = {
"issue": "issues",
"us": "userstories",
"task": "tasks",
}
TYPE_LABELS = {
"issue": "Issue",
"us": "User Story",
"task": "Task",
}
# ── URL Parsing ──────────────────────────────────────────────────────────────
def parse_taiga_url(url: str) -> tuple[str, int] | None:
"""Extract (type, ref) from a tree.taiga.io URL.
Supported patterns:
.../project/penpot/issue/13714
.../project/penpot/us/14128
.../project/penpot/task/13648
"""
m = re.search(r"/project/penpot/(issue|us|task)/(\d+)", url)
if not m:
return None
return m.group(1), int(m.group(2))
# ── API call ─────────────────────────────────────────────────────────────────
def fetch_item(endpoint: str, ref: int) -> dict | None:
"""Fetch a single item by ref using the 'by_ref' endpoint."""
url = f"{API_BASE}/{endpoint}/by_ref?ref={ref}&project={PROJECT_ID}"
try:
with urllib.request.urlopen(url, timeout=15) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
print(f"Error: HTTP {e.code}{e.reason}", file=sys.stderr)
if e.code == 404:
print(
f" Item (ref={ref}) not found in project {PROJECT_ID}.",
file=sys.stderr,
)
return None
except urllib.error.URLError as e:
print(f"Error: {e.reason}", file=sys.stderr)
return None
except json.JSONDecodeError as e:
print(f"Error: invalid JSON response — {e}", file=sys.stderr)
return None
# ── Output formatting ────────────────────────────────────────────────────────
def _val(value, default=""):
return value if value is not None else default
def _tag_list(tags):
"""Pretty-print tag list. Tags are arrays of [name, color] pairs."""
if not tags:
return ""
names = [t[0] if isinstance(t, list) else str(t) for t in tags]
return ", ".join(names)
def _extra_name(extra_info):
"""Extract a display name from an *_extra_info dict."""
if not extra_info:
return ""
return extra_info.get("full_name_display") or extra_info.get("username") or ""
def _status_name(status_info):
"""Extract status name from status_extra_info."""
if not status_info:
return ""
return status_info.get("name", "")
def _project_name(proj_info):
"""Extract project name from project_extra_info."""
if not proj_info:
return ""
return proj_info.get("name", "")
def _assignee(item):
"""Return the assignee display name."""
return _extra_name(item.get("assigned_to_extra_info"))
def _owner(item):
return _extra_name(item.get("owner_extra_info"))
def format_summary(item: dict, item_type: str) -> str:
"""Build a printable summary matching the requested format."""
label = TYPE_LABELS.get(item_type, item_type.capitalize())
subject = item.get("subject", "(no subject)")
ref = item.get("ref", "?")
status = _status_name(item.get("status_extra_info"))
assignee = _assignee(item)
owner = _owner(item)
created = item.get("created_date", "")[:10] if item.get("created_date") else ""
tags = _tag_list(item.get("tags", []))
# Title line
title = f"{label} #{ref}{subject}"
# Fields section (no indent)
fields = []
fields.append(f"Status: {status}")
if item_type == "us":
milestone = item.get("milestone_slug") or ""
points = item.get("points") or {}
point_count = len(points)
fields.append(f"Milestone: {milestone}")
fields.append(f"Points: {point_count} role(s)")
elif item_type == "task":
milestone = item.get("milestone_slug") or ""
parent = item.get("user_story")
fields.append(f"Milestone: {milestone}")
fields.append(f"Parent US: {parent if parent else ''}")
elif item_type == "issue":
issue_type_id = item.get("type", "")
severity_id = item.get("severity", "")
priority_id = item.get("priority", "")
fields.append(f"Type ID: {issue_type_id}")
fields.append(f"Severity ID: {severity_id}")
fields.append(f"Priority ID: {priority_id}")
fields.append(f"Assignee: {assignee}")
fields.append(f"Author: {owner}")
fields.append(f"Created: {created}")
fields.append(f"Tags: {tags}")
url = f"https://tree.taiga.io/project/penpot/{item_type}/{ref}"
fields.append(f"URL: {url}")
# Assemble output
sep = "================================"
parts = [title, sep]
parts.extend(fields)
# Full description after second separator
desc = item.get("description") or ""
if desc.strip():
parts.append(sep)
parts.append(desc)
return "\n".join(parts)
# ── CLI ───────────────────────────────────────────────────────────────────────
def build_parser():
parser = argparse.ArgumentParser(
description="Fetch public items from the Penpot Taiga project.",
epilog=(
"Examples:\n"
" %(prog)s https://tree.taiga.io/project/penpot/issue/13714\n"
" %(prog)s --json us 14128\n"
" %(prog)s task 13648"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--json",
action="store_true",
dest="raw_json",
help="Output raw JSON instead of formatted summary.",
)
parser.add_argument(
"args",
nargs="+",
help='Either a Taiga URL, or "<type> <ref>" (e.g. issue 13714).',
)
return parser
def main():
parser = build_parser()
opts = parser.parse_args()
# Determine (type, ref) from arguments
item_type: str | None = None
ref: int | None = None
if len(opts.args) == 1:
# Single argument — must be a Taiga URL
parsed = parse_taiga_url(opts.args[0])
if parsed is None:
print(
"Error: could not parse Taiga URL. "
'Expected format: https://tree.taiga.io/project/penpot/<type>/<ref>',
file=sys.stderr,
)
sys.exit(1)
item_type, ref = parsed
elif len(opts.args) == 2:
item_type, ref_str = opts.args
if item_type not in ENDPOINT_MAP:
print(
f"Error: unknown type '{item_type}'. "
f"Expected one of: {', '.join(ENDPOINT_MAP)}",
file=sys.stderr,
)
sys.exit(1)
try:
ref = int(ref_str)
except ValueError:
print(f"Error: ref must be a number, got '{ref_str}'", file=sys.stderr)
sys.exit(1)
else:
parser.print_help()
sys.exit(1)
endpoint = ENDPOINT_MAP[item_type]
item = fetch_item(endpoint, ref)
if item is None:
sys.exit(1)
if opts.raw_json:
print(json.dumps(item, indent=2, ensure_ascii=False))
else:
print(format_summary(item, item_type))
if __name__ == "__main__":
main()