diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 67ba1d4fe1..794fec01c2 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,7 +1,10 @@ description: Create a report to help us improve -labels: ["bug"] name: Bug report title: "bug: " +type: Bug +labels: ["triage"] +projects: ["penpot/8"] + body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..0086358db1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: true diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index a49d6a57c9..0fe9c3757d 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -1,7 +1,10 @@ description: Suggest an idea for this project. -labels: ["needs triage", "enhancement"] +labels: ["needs triage"] name: "Feature request" title: "feature: " +type: Enhancement +projects: ["penpot/8"] + body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/new-render-bug-report.md b/.github/ISSUE_TEMPLATE/new-render-bug-report.md deleted file mode 100644 index b93b98b444..0000000000 --- a/.github/ISSUE_TEMPLATE/new-render-bug-report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: New Render Bug Report -about: Create a report about the bugs you have found in the new render -title: '' -labels: new render -assignees: claragvinola - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**Steps to Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots or screen recordings** -If applicable, add screenshots or screen recording to help illustrate your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 58ed9335f4..e18d04e87d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -15,6 +15,5 @@ - [ ] Add or modify existing integration tests in case of bugs or new features, if applicable. - [ ] Refactor any modified SCSS files following the refactor guide. - [ ] Check CI passes successfully. -- [ ] Update the `CHANGES.md` file, referencing the related GitHub issue, if applicable. diff --git a/.opencode/skills/backport-commit/SKILL.md b/.opencode/skills/backport-commit/SKILL.md index c8092402db..79494a3c5d 100644 --- a/.opencode/skills/backport-commit/SKILL.md +++ b/.opencode/skills/backport-commit/SKILL.md @@ -70,12 +70,7 @@ module's `AGENTS.md` for the exact commands). If the formatter auto-fixes indentation, verify the logic is still semantically correct. All checks must pass before moving on. -### 6. Port the changelog entry (if any) - -If the original commit added or modified a `CHANGES.md` entry, port that entry -too — adapting wording and version references for the target branch. - -### 7. Commit +### 6. Commit Ask the `commiter` sub-agent to create a commit. Stage all relevant files (exclude unrelated untracked files) and provide the original commit message as diff --git a/.opencode/skills/issue-title/SKILL.md b/.opencode/skills/issue-title/SKILL.md new file mode 100644 index 0000000000..6cd8c14fc5 --- /dev/null +++ b/.opencode/skills/issue-title/SKILL.md @@ -0,0 +1,123 @@ +--- +name: issue-title +description: Derive a clear, well-formatted title for a GitHub issue from its description body, using descriptive present-tense for bugs and imperative mood for features, always including the "where" (location in the UI/module). +--- + +# Skill: issue-title + +Derive a concise, descriptive title for a GitHub issue based on its body +content. Use **descriptive present tense for bugs** (e.g. "Plugin API +crashes when setting text fills") and **imperative mood for features** (e.g. +"Add customizable dash and gap controls"). No emoji or type prefixes +(`feat:`, `bug:`, `feature:`, etc.). + +Can be used both when **creating a new issue** and when **updating an +existing one** that has a vague or outdated title. + +## When to Use + +- Creating a new issue and need a well-formatted title from the draft body +- An existing issue has a vague, outdated, or auto-generated title (e.g. + `[PENPOT FEEDBACK]: ...`, `feature: ...`) +- The current title doesn't reflect the actual content of the description +- The title is missing the "where" (which part of the UI/module is affected) + +## Prerequisites + +- `gh` CLI authenticated (`gh auth status`) + +## Workflow + +### 1. Get the issue body + +For an **existing issue**, fetch it: + +```bash +gh issue view --repo penpot/penpot --json title,body +``` + +For a **new issue**, read the draft body from wherever it was provided +(Taiga link, user report, discussion, etc.). + +### 2. Read the body and derive a title + +Extract the core problem or request from the description. Distinguish between +bug reports and feature requests: + +**Bug titles (descriptive, present tense):** +Describe the symptom as it appears to the user. Format: +`[Where] [present-tense verb] when [condition]` + +- *"Plugin API crashes when setting text fills"* +- *"Canvas renders glitches when zooming quickly"* +- *"French Canada locale falls back to French (fr) translations"* +- *"Text layer content is not deleted when WebGL render is enabled"* + +Do **not** start bug titles with "Fix" or any imperative verb. The title +should state what's broken, not command a fix. + +**Feature / Enhancement titles (imperative mood):** +Command what should be built. Format: +`[Imperative verb] [what] in/on [where]` + +- *"Add customizable dash and gap length controls to dashed strokes in the sidebar"* +- *"Show user, timestamp, and hash in the workspace history panel like git commits"* +- *"Validate shape on add-object to catch malformed inputs early"* + +**Universal rules (both types):** +- **Include the "where"** — specify the UI location or module (e.g. + "in the sidebar", "in the workspace history panel", "on the stroke + options") +- **No prefixes** — strip `bug:`, `feature:`, `feat:`, `:bug:`, `:sparkles:`, + `[PENPOT FEEDBACK]`, etc. +- **No emoji** — plain text only +- **Be specific** — prefer concrete detail over generality. If the + description mentions two related problems, capture both. + +**Examples:** + +| Original / draft title | Type | New title | +|---|---|---| +| `[PENPOT FEEDBACK]: WebGL` | Bug | `Canvas renders glitches when zooming quickly — text appears distorted and nodes have background-colored rectangles` | +| `bug: flatten-nested-tokens-json uses $type instead of $value as the DTCG token/group discriminator` | Bug | `Token import fails when group-level type inheritance is used — parser misidentifies groups as tokens` | +| `feature: Dashed stroke customization` | Feature | `Add customizable dash and gap length controls to dashed strokes in the sidebar` | +| `feature: Add more detail to history of actions` | Feature | `Show user, timestamp, and hash in the workspace history panel like git commits` | + +### 3. Apply the title + +**If updating an existing issue:** + +```bash +gh issue edit --repo penpot/penpot --title "" +``` + +**If creating a new issue:** + +```bash +gh issue create --repo penpot/penpot --title "" --body "" +``` + +### 4. Confirm + +For updates, the command returns the issue URL. Verify by optionally fetching +again: + +```bash +gh issue view --repo penpot/penpot --json title +``` + +## Key Principles + +- **Bug titles describe the symptom** — present tense, 3rd person: + "crashes", "fails", "shows", "is cut off", "does not load". Do not + start with "Fix" or "Bug:". +- **Feature titles use imperative mood** — command form: "Add", "Show", + "Use", "Validate", "Support", "Toggle". +- **Always include the "where"** — a title like "Crashes when zooming" + is too vague; "Canvas crashes when zooming quickly" is clear. +- **No prefixes, no emoji** — strip all type labels and decorative + characters from the title. +- **Derive from the body, not the current title** — the body contains + the real detail; the current title may be auto-generated or stale. +- **Two problems → cover both** — if the description has two distinct + but related issues, capture both in the title joined by "and". diff --git a/.serena/memories/workflow/creating-commits.md b/.serena/memories/workflow/creating-commits.md index 1ff2916b8e..b58221ae48 100644 --- a/.serena/memories/workflow/creating-commits.md +++ b/.serena/memories/workflow/creating-commits.md @@ -21,20 +21,3 @@ Co-authored-by: ## Commit Type Emojis `:bug:` bug fix · `:sparkles:` enhancement · `:tada:` new feature · `:recycle:` refactor · `:lipstick:` cosmetic · `:ambulance:` critical fix · `:books:` docs · `:construction:` WIP · `:boom:` breaking · `:wrench:` config · `:zap:` perf · `:whale:` docker · `:paperclip:` other · `:arrow_up:` dep upgrade · `:arrow_down:` dep downgrade · `:fire:` removal · `:globe_with_meridians:` translations · `:rocket:` epic/highlight - - -## Changelogs - -**IMPORTANT:** do not modify the changelog unless it explicitly asked. - -For user-facing or notable changes, update the relevant changelog under the unreleased section: -- Main app/modules (`backend`, `frontend`, `common`, `render-wasm`, `exporter`, `mcp`): root `CHANGES.md`. -- Plugin subproject changes: `plugins/CHANGELOG.md`. - -Entry format uses the matching category (`:sparkles:`, `:bug:`, etc.) and references the GitHub issue: - -``` -- Short description of change [#NNNN](https://github.com/penpot/penpot/issues/NNNN) -``` - -Plugin API changelog prefixes: type/signature -> `**plugin-types:**`; runtime behavior -> `**plugin-runtime:**` in `plugins/CHANGELOG.md`. diff --git a/AGENTS.md b/AGENTS.md index dac88e8261..842cd15022 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,17 +32,6 @@ precision while maintaining a strong focus on maintainability and performance. 5. When searching code, prefer `ripgrep` (`rg`) over `grep` — it respects `.gitignore` by default. -## Changelogs - -The project has two changelogs: - -- **Main project changelog**: `CHANGES.md` (root of the repository). Tracks changes for the core Penpot application (backend, frontend, common, render-wasm, exporter, mcp). -- **Plugins changelog**: `plugins/CHANGELOG.md`. Tracks changes for the plugins subproject only. - -When making changes, add a changelog entry to the appropriate file under the -`## (Unreleased)` section in the correct category -(`:sparkles: New features & Enhancements` or `:bug: Bugs fixed`). - ## GitHub Operations To obtain the list of repository members/collaborators: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 532413194d..1251e2c80f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -237,9 +237,8 @@ setting this up. ## Changelog -When your change is user-facing or otherwise notable, add an entry to -[CHANGES.md](CHANGES.md) following the same commit-type conventions. Reference -the relevant GitHub issue or Taiga user story. +The changelog is updated automatically as part of the release process. Contributors +should **not** modify `CHANGES.md` manually in their pull requests. ## Code of Conduct diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc index 13fa601d69..043559e257 100644 --- a/common/src/app/common/math.cljc +++ b/common/src/app/common/math.cljc @@ -183,7 +183,7 @@ :clj (Math/log10 x))) (defn clamp [num from to] - (if (< num from) + (if (or (nan? num) (< num from)) from (if (> num to) to num))) diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index ea6cbd4d36..df61a14e5f 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -738,7 +738,7 @@ [h s v] (clr/hex->hsv value)] (merge data {:hex (or value "000000") - :alpha (or opacity 1) + :alpha (if (d/nan? opacity) 1 (or opacity 1)) :r r :g g :b b :h h :s s :v v}))) @@ -815,7 +815,6 @@ (rx/filter (ptk/type? ::update-colorpicker-add-stop) stream) (rx/filter (ptk/type? ::update-colorpicker-add-auto) stream) (rx/filter (ptk/type? ::remove-gradient-stop) stream)) - (rx/debounce 40) (rx/map (constantly (colorpicker-onchange-runner on-change))) (rx/take-until stopper)))) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs b/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs index 11a73a449d..14c4e1b820 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs @@ -42,9 +42,11 @@ (when on-change (let [{:keys [left right top bottom]} (-> ev dom/get-target dom/get-bounding-rect) {:keys [x y]} (-> ev dom/get-client-position) + h-size (- right left) + v-size (- bottom top) unit-value (if is-vertical - (mth/clamp (/ (- bottom y) (- bottom top)) 0 1) - (mth/clamp (/ (- x left) (- right left)) 0 1)) + (if (pos? v-size) (mth/clamp (/ (- bottom y) v-size) 0 1) 0) + (if (pos? h-size) (mth/clamp (/ (- x left) h-size) 0 1) 0)) value (+ min-value (* unit-value (- max-value min-value)))] (on-change value))))] diff --git a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs index d27cd2d018..92d2d43040 100644 --- a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.viewport.pixel-overlay (:require-macros [app.main.style :as stl]) (:require + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.math :as mth] [app.config :as cfg] @@ -89,10 +90,10 @@ (when (and (>= x 0) (< x img-width) (>= y 0) (< y img-height)) (let [offset (* (+ (* y img-width) x) 4) rgba (unchecked-get image-data "data") - r (obj/get rgba (+ 0 offset)) - g (obj/get rgba (+ 1 offset)) - b (obj/get rgba (+ 2 offset)) - a (obj/get rgba (+ 3 offset)) + r (d/check-num (obj/get rgba (+ 0 offset)) 255) + g (d/check-num (obj/get rgba (+ 1 offset)) 255) + b (d/check-num (obj/get rgba (+ 2 offset)) 255) + a (d/check-num (obj/get rgba (+ 3 offset)) 255) color [r g b a]] ;; Store latest color synchronously so the click handler always reads ;; the correct pixel even before the rAF fires (fixes race condition) @@ -293,13 +294,13 @@ ;; Only pick color when cursor is within canvas bounds to avoid garbage pixels (when (and (>= canvas-x 0) (< canvas-x img-width) (>= canvas-y 0) (< canvas-y img-height)) (let [;; image-data pixels start from the bottom-left corner; invert y accordingly - inverted-y (- img-height canvas-y) + inverted-y (- img-height canvas-y 1) offset (* (+ (* inverted-y img-width) canvas-x) 4) rgba (.-data image-data) - r (obj/get rgba (+ 0 offset)) - g (obj/get rgba (+ 1 offset)) - b (obj/get rgba (+ 2 offset)) - a (obj/get rgba (+ 3 offset)) + r (d/check-num (obj/get rgba (+ 0 offset)) 255) + g (d/check-num (obj/get rgba (+ 1 offset)) 255) + b (d/check-num (obj/get rgba (+ 2 offset)) 255) + a (d/check-num (obj/get rgba (+ 3 offset)) 255) color [r g b a]] ;; Store latest color synchronously so the click handler always reads ;; the correct pixel even before the rAF fires (fixes race condition)