diff --git a/.opencode/skills/gh-issue-from-pr/SKILL.md b/.opencode/skills/gh-issue-from-pr/SKILL.md new file mode 100644 index 0000000000..93f17345f2 --- /dev/null +++ b/.opencode/skills/gh-issue-from-pr/SKILL.md @@ -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 --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 --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 + + + +### Steps to reproduce + +1. +2. + +### Expected behavior + + + +### Affected versions + + +``` + +**Enhancement template:** + +```markdown +### Description + + + +### Use case + + + +### Affected versions + + +``` + +### 4. Create the issue + +Write the body to a temp file to avoid shell quoting issues: + +```bash +cat > /tmp/issue-body.md << 'ISSUE_BODY' + +ISSUE_BODY +``` + +Create: + +```bash +gh issue create \ + --repo penpot/penpot \ + --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. diff --git a/.opencode/skills/taiga/SKILL.md b/.opencode/skills/taiga/SKILL.md new file mode 100644 index 0000000000..890c965c03 --- /dev/null +++ b/.opencode/skills/taiga/SKILL.md @@ -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` diff --git a/.opencode/skills/update-changelog/SKILL.md b/.opencode/skills/update-changelog/SKILL.md new file mode 100644 index 0000000000..5752b65aa3 --- /dev/null +++ b/.opencode/skills/update-changelog/SKILL.md @@ -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. diff --git a/CHANGES.md b/CHANGES.md index 570bf5e660..05346225e9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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) diff --git a/backend/src/app/rpc/commands/fonts.clj b/backend/src/app/rpc/commands/fonts.clj index a5ff63c0e1..485d41b571 100644 --- a/backend/src/app/rpc/commands/fonts.clj +++ b/backend/src/app/rpc/commands/fonts.clj @@ -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" diff --git a/backend/test/backend_tests/rpc_font_test.clj b/backend/test/backend_tests/rpc_font_test.clj index 59a58466b9..8733ca3ad9 100644 --- a/backend/test/backend_tests/rpc_font_test.clj +++ b/backend/test/backend_tests/rpc_font_test.clj @@ -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)))))) diff --git a/common/src/app/common/types/font.cljc b/common/src/app/common/types/font.cljc new file mode 100644 index 0000000000..61ee5a6059 --- /dev/null +++ b/common/src/app/common/types/font.cljc @@ -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)))]]) diff --git a/common/test/common_tests/types/font_test.cljc b/common/test/common_tests/types/font_test.cljc new file mode 100644 index 0000000000..ee04e656fb --- /dev/null +++ b/common/test/common_tests/types/font_test.cljc @@ -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")))))) diff --git a/docs/_includes/layouts/base.njk b/docs/_includes/layouts/base.njk index 076acbffec..97fc32e20b 100644 --- a/docs/_includes/layouts/base.njk +++ b/docs/_includes/layouts/base.njk @@ -84,7 +84,7 @@ <path d="M68.318 0L31.941 51.237v28.961L.286 95.255 0 95.121v188.952l123.419 58.309 7.542 3.56 7.542-3.56 123.418-58.309V95.121l-.232.11-31.665-15.062V51.237l-1.107-1.559L193.645 0l-36.38 51.237v.05l-26.47-37.281L104.528 51l-.939-1.322L68.318 0zm6.437 29.762l14.07 19.816H47.811L61.72 29.995l13.036-.233zm125.324 0l14.072 19.816h-41.012l13.903-19.583 13.037-.233zM137.23 43.77l14.07 19.813h-41.008L124.195 44l13.035-.23zM43.923 59.564h19.452v65.497l-19.452-9.19V59.564zm29.438 0h19.354v79.356l-19.354-9.142V59.564zm95.89 0h19.45v70.146l-19.45 9.188V59.564zm29.435 0h19.352v56.285l-19.352 9.142V59.564zM106.4 73.57h19.451v81.004l-19.451-9.19V73.57zm29.438 0h19.352v71.971l-19.352 9.145V73.57zm924.624 7.15l-.096 36.464-19.553-.134v14.857h19.61v78.333c0 5.705.445 10.996 1.348 15.873l.009.042.007.044c1.107 4.982 2.979 9.326 5.65 12.95 2.692 3.654 6.295 6.463 10.645 8.36 4.437 2.11 9.836 3.096 16.17 3.096 2.887 0 6.193-.47 9.981-1.373a90.691 90.691 0 0011.285-3.492l2.202-.84-4.751-14.248-2.492 1.15c-4.84 2.098-9.583 3.667-14.656 3.695-4.087 0-7.299-.587-9.651-1.6-2.245-1.215-3.98-2.738-5.298-4.629-1.375-2.15-2.385-4.622-3.025-7.463-.491-3.143-.748-6.645-.748-10.518v-79.38h36.342v-14.844H1077.1V80.72h-16.637zM230.02 95.096l17.126 7.002-17.126 8.09V95.095zm-198.08.025v15.09l-17.122-8.09 17.122-7zm802.504 18.8l.001.002v.002c-8.243 0-16.244 2.03-23.884 6.031l-.01.007-.012.006c-6.172 3.312-11.324 6.77-16.74 10.856l-.096-13.762H777.61v187.141h16.414V237.31c5.379 3.67 10.692 6.85 15.895 9.163l.026.01.027.014c6.529 2.72 12.96 4.108 19.247 4.108 7.652 0 14.986-1.557 21.917-4.658l.013-.005.015-.008c7.106-3.28 13.324-7.946 18.592-13.941l-.005.002c5.456-6.184 9.708-13.62 12.762-22.242 3.073-8.675 4.585-18.459 4.585-29.319 0-19.865-4.26-35.933-13.01-47.97l-.003-.006c-8.818-12.304-22.34-18.537-39.64-18.537zm-397.499.002v.002c-8.243 0-16.244 2.03-23.884 6.031l-.01.007-.012.006c-6.172 3.312-11.324 6.772-16.74 10.858l-.096-13.764H380.11v187.143h16.414v-66.894c5.379 3.67 10.692 6.848 15.895 9.16l.026.014.028.012c6.528 2.72 12.958 4.11 19.246 4.11 7.652 0 14.986-1.558 21.917-4.659l.013-.007.015-.006c7.106-3.28 13.324-7.946 18.592-13.941l-.005.001c5.456-6.183 9.708-13.618 12.762-22.241 3.073-8.675 4.585-18.459 4.585-29.32 0-19.864-4.26-35.933-13.01-47.97l-.003-.005c-8.817-12.304-22.338-18.537-39.639-18.537zm267.246 0v.002c-9.113 0-17.375 2.306-24.569 6.91-5.873 3.616-11.564 8.222-17.142 13.445l.115-17.217h-15.765v130.406h16.417v-94.62c7.284-7.373 13.843-14.357 19.506-17.917l.02-.014.02-.013c5.715-3.756 11.989-5.61 19.046-5.61 9.77 0 16.07 2.884 19.925 8.59l.022.032.024.033c4.045 5.6 6.332 14.962 6.332 28.107v81.412h16.415v-82.984c0-16.688-3.059-29.35-9.668-37.847-6.622-8.515-17.134-12.715-30.698-12.715zm-134.972.002c-7.331 0-14.503 1.659-21.42 4.93l-.01.006c-6.764 3.11-12.876 7.682-18.298 13.647l-.002.002c-5.287 5.833-9.533 12.995-12.76 21.425l-.01.022-.007.022c-3.075 8.501-4.593 18.028-4.593 28.548s1.518 20.049 4.593 28.55l.009.029.013.03c3.233 8.263 7.572 15.42 13.018 21.412l.015.012.011.015c5.453 5.817 11.854 10.299 19.133 13.392l.009.004.01.004c7.447 3.087 15.367 4.63 23.687 4.63 9.459 0 17.36-1.32 23.714-4.118l.013-.007c6.178-2.648 11.843-5.48 16.994-8.5l2.03-1.188-7.144-13.493L605.93 225c-4.517 3.346-8.972 5.427-14.335 7.44-5.093 1.803-11.373 2.728-17.93 2.728-6.832 0-12.9-.649-18.294-3.095-5.45-2.645-10.123-6.175-14.083-10.62-3.792-4.625-6.783-10.087-8.96-16.446l-.001-.006c-1.93-5.795-2.945-12.072-3.3-18.708h90.5c0-2.558.246-5.015.255-7.163v-4.706c0-18.854-4.374-33.79-13.39-44.463-8.998-10.65-21.63-16.035-37.171-16.035v-.001zm397.808 0c-7.818 0-15.318 1.554-22.418 4.649-7.104 3.096-13.407 7.576-18.85 13.383l-.014.014-.016.015c-5.295 5.843-9.541 13.1-12.768 21.704l-.005.012-.006.015c-3.077 8.506-4.59 18.12-4.59 28.81 0 10.69 1.513 20.305 4.59 28.811l.011.03.013.03c3.23 8.254 7.475 15.324 12.753 21.148l.015.013.014.016c5.444 5.807 11.746 10.285 18.85 13.381 7.1 3.096 14.602 4.65 22.42 4.65 7.819 0 15.32-1.554 22.42-4.65 7.27-3.093 13.585-7.575 18.863-13.396 5.465-5.832 9.724-12.922 12.783-21.196 3.258-8.514 4.863-18.14 4.863-28.837 0-10.692-1.604-20.315-4.86-28.826-3.054-8.614-7.31-15.885-12.787-21.731-5.279-5.82-11.592-10.303-18.862-13.396-7.1-3.095-14.6-4.65-22.418-4.65h-.001zm-951.946 5.004l108.337 51.178v155.59L15.082 274.519v-155.59zm231.755 0v155.59l-108.334 51.178v-155.59l108.334-51.178zm322.641 9.842h.002c10.75 0 18.852 3.647 25.038 11.134l.005.011.008.011c5.748 6.807 8.88 17.511 9.22 32.04h-74.538c.78-6.15 2.074-11.798 4.1-16.723 2.515-5.69 5.586-10.488 9.203-14.438 3.8-3.96 7.955-6.882 12.506-8.833l.033-.016.033-.015c4.566-2.12 9.33-3.171 14.39-3.171zm-134.36.26v.002c6.826 0 12.488 1.325 17.114 3.878l.03.014.029.017c4.685 2.423 8.428 5.844 11.349 10.387l.012.019.011.016c2.936 4.404 5.096 9.763 6.438 16.137l.006.031.009.03c1.52 6.416 2.287 13.372 2.287 20.874 0 8.323-1.104 15.91-3.285 22.787l-.007.019-.006.018c-2.02 6.733-4.856 12.468-8.492 17.261-3.63 4.786-7.952 8.447-13.028 11.067-5.06 2.611-10.479 3.91-16.39 3.91-4.471 0-9.768-1.132-15.842-3.494-5.688-2.212-12.01-7.647-18.827-13.056v-69.463c7.23-6.16 13.974-12.007 20.127-15.245l.009-.004.011-.007c6.52-3.51 12.647-5.198 18.444-5.198zm397.497 0l.002.002c6.827 0 12.489 1.325 17.115 3.878l.03.014.029.017c4.685 2.423 8.428 5.844 11.348 10.387l.013.019.011.016c2.936 4.404 5.096 9.763 6.438 16.137l.006.031.009.03c1.52 6.416 2.287 13.372 2.287 20.874 0 8.323-1.104 15.91-3.285 22.787l-.01.019-.005.018c-2.02 6.733-4.856 12.468-8.492 17.261-3.63 4.786-7.952 8.447-13.028 11.067-5.059 2.611-10.479 3.91-16.39 3.91-4.47 0-9.768-1.132-15.842-3.494-5.687-2.212-12.01-7.647-18.827-13.056v-69.463c7.23-6.16 13.974-12.007 20.127-15.245l.01-.005.01-.006c6.52-3.51 12.647-5.198 18.444-5.198zm134.414 0v.002c5.911 0 11.331 1.3 16.39 3.91l.044.02.04.021c5.223 2.448 9.595 6.004 13.206 10.765l.027.038.03.035c3.798 4.624 6.797 10.183 8.981 16.736 2.17 6.51 3.272 13.824 3.272 21.969 0 7.965-1.1 15.19-3.272 21.707-2.184 6.553-5.183 12.11-8.981 16.734l-.017.022-.014.022c-3.614 4.6-8.012 8.182-13.272 10.814-5.064 2.448-10.505 3.671-16.434 3.671-5.921 0-11.358-1.221-16.417-3.664-5.087-2.63-9.503-6.226-13.304-10.849-3.629-4.624-6.548-10.191-8.734-16.75-2.173-6.517-3.274-13.742-3.274-21.707 0-8.145 1.103-15.458 3.274-21.97 2.188-6.565 5.11-12.138 8.745-16.766l.003-.004.004-.007c3.801-4.79 8.2-8.364 13.255-10.81l.03-.015.027-.013c5.059-2.611 10.48-3.911 16.391-3.911z"></path> </svg> </a> - <span>Design as code</span> + <span>Full-Stack Design</span> </div> <ul class="footer-block"> <li class="footer-block--title">Product</li> diff --git a/docs/mcp/index.md b/docs/mcp/index.md index d473eff10c..e0d31fe6ce 100644 --- a/docs/mcp/index.md +++ b/docs/mcp/index.md @@ -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 diff --git a/frontend/resources/images/penpot-link-preview.png b/frontend/resources/images/penpot-link-preview.png new file mode 100644 index 0000000000..01e3e25272 Binary files /dev/null and b/frontend/resources/images/penpot-link-preview.png differ diff --git a/frontend/resources/images/registration-illustration.png b/frontend/resources/images/registration-illustration.png index 3ccd26deab..21b785ddf0 100644 Binary files a/frontend/resources/images/registration-illustration.png and b/frontend/resources/images/registration-illustration.png differ diff --git a/frontend/resources/templates/index.mustache b/frontend/resources/templates/index.mustache index 60c6119fd0..597ab8db3a 100644 --- a/frontend/resources/templates/index.mustache +++ b/frontend/resources/templates/index.mustache @@ -3,17 +3,17 @@ <head> <meta charset="utf-8" /> <meta http-equiv="x-ua-compatible" content="ie=edge" /> - <title>Penpot - Design Freedom for Teams + Penpot | Full-stack design - + - - - - + + + + - - + + diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 3093a11287..8ee83d7411 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -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))))) diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index 16b8c7dec3..db96f2a901 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -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 diff --git a/frontend/src/app/main/ui/ds/controls/numeric_input.cljs b/frontend/src/app/main/ui/ds/controls/numeric_input.cljs index 4b7caf8622..74e6b9c95a 100644 --- a/frontend/src/app/main/ui/ds/controls/numeric_input.cljs +++ b/frontend/src/app/main/ui/ds/controls/numeric_input.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index c82e2e84d2..b904d5b5e1 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -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)} diff --git a/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs b/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs index ad510bc7d6..4fe6934999 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs @@ -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) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index e68ec16caf..133b282237 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -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 diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 2ce67382b1..1727603670 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -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 "&" "&") + (str/replace "<" "<") + (str/replace ">" ">") + (str/replace "\"" """) + (str/replace "'" "'")))) + (defn set-html! [^js el html] (when (some? el) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index c5fcb05f69..f05975c3c2 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -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" diff --git a/mcp/README.md b/mcp/README.md index d0e2fb49aa..06eca0c45b 100644 --- a/mcp/README.md +++ b/mcp/README.md @@ -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` | diff --git a/opencode.json b/opencode.json deleted file mode 100644 index 6376bc70e5..0000000000 --- a/opencode.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://opencode.ai/config.json", - "instructions": ["AGENTS.md"] -} diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index c5cde152ba..416c13bb0c 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -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)?; } } } diff --git a/render-wasm/src/render/gpu_state.rs b/render-wasm/src/render/gpu_state.rs index 47acd7a26b..07ce266eff 100644 --- a/render-wasm/src/render/gpu_state.rs +++ b/render-wasm/src/render/gpu_state.rs @@ -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 { 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, diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index 6368a8bf9e..6e8736bffc 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -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(()) } diff --git a/render-wasm/src/shapes/text.rs b/render-wasm/src/shapes/text.rs index e3a1037175..c5dec42f4a 100644 --- a/render-wasm/src/shapes/text.rs +++ b/render-wasm/src/shapes/text.rs @@ -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 } diff --git a/tools/gh.py b/tools/gh.py new file mode 100755 index 0000000000..2aa7ac4da8 --- /dev/null +++ b/tools/gh.py @@ -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 (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() diff --git a/tools/taiga.py b/tools/taiga.py new file mode 100755 index 0000000000..1e96364a31 --- /dev/null +++ b/tools/taiga.py @@ -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 + python3 tools/taiga.py + python3 tools/taiga.py [--json] + python3 tools/taiga.py [--json] + +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 " " (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//', + 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()