diff --git a/.serena/memories/critical-info.md b/.serena/memories/critical-info.md index 9033a9e74a..0854fe28ee 100644 --- a/.serena/memories/critical-info.md +++ b/.serena/memories/critical-info.md @@ -14,6 +14,9 @@ You are working on the GitHub project `penpot/penpot`, a monorepo. - Before `git commit` → `mem:workflow/creating-commits` (subject format, body, `AI-assisted-by: model-name` trailer) - Before `gh issue create` → `mem:workflow/creating-issues` (title derivation, body template, labels, Issue Type) - Before `gh pr create` / `gh pr edit` → `mem:workflow/creating-prs` (title format, body structure, "Note:" line) +- Before a repo-wide pnpm version update → `mem:workflow/updating-pnpm` (workspace + layout, `corepack use` sweep order, the stamp-missing-field and + ignored-builds gotchas, verification steps) - **Never `git push`, force-push, or modify `git origin`** (or any other remote). The user pushes from their own shell; if a push is required, say so and wait. Never amend a commit that the user has already pushed unless explicitly asked. - **Never edit `CHANGES.md` by hand.** The changelog is generated from GitHub milestones during the release process; update it only via the `update-changelog` skill flow or on explicit user request. - You have access to the GitHub CLI `gh` or corresponding MCP tools. @@ -71,6 +74,10 @@ module. You can read it from `mem:/core` - `scripts/error-reports.mjs` — Query error reports via RPC API with token authentication. Supports list/get operations with filtering and pagination. See `mem:scripts/error-reports`. +- `scripts/clean-node-modules` — Remove stale `node_modules` from all pnpm + workspaces (root, modules, member packages). Keeps the shared pnpm store + at `/.pnpm-store` unless `--store`; ignores `external/` and + `.opencode/`. Usage and reinstall steps: `mem:workflow/updating-pnpm`. # Dependency graph diff --git a/.serena/memories/workflow/updating-pnpm.md b/.serena/memories/workflow/updating-pnpm.md new file mode 100644 index 0000000000..9f4ba133df --- /dev/null +++ b/.serena/memories/workflow/updating-pnpm.md @@ -0,0 +1,89 @@ +# Updating pnpm Across All Workspaces + +Canonical procedure. Run it from the repo root with the log redirected to a +file (never pipe tool output through filters). + +## Layout facts + +- The repo has 11 pnpm workspaces, each with its own `pnpm-workspace.yaml` + and `pnpm-lock.yaml`: the repo root plus `backend`, `common`, `docs`, + `exporter`, `frontend`, `library`, `mcp`, `media-processor`, `plugins`, + and `render-wasm`. +- Every package inside a module workspace (for example all `plugins/apps/*` + and `plugins/libs/*` packages) is a plain member of that module's + workspace. Members must not carry their own `pnpm-workspace.yaml` or + `pnpm-lock.yaml`; their dependencies resolve through the parent + workspace's lockfile. +- One shared pnpm store for the whole repo: `/.pnpm-store`. Every + workspace yaml sets it explicitly: `storeDir: .pnpm-store` at the root, + `storeDir: ../.pnpm-store` in each module. pnpm resolves the value + against the workspace root, so all workspaces land on the same store. + Do not remove these lines: nested workspaces do not inherit settings, + and without them each workspace may resolve a different store. +- The store survives `node_modules` cleans. It is content-addressed and + integrity-verified, so it cannot go stale; staleness lives in + node_modules. Only `scripts/clean-node-modules --store` removes it. +- Every `package.json` (about 35 of them) must carry a `packageManager` field + with the identical `pnpm@+sha512.` value. Do not let them drift. +- CI pins no pnpm version; workflows rely on corepack reading + `packageManager`. Fixing the fields fixes CI. + +## Procedure + +1. Resolve the target tag first and note the version. Example: + `npm view pnpm dist-tags --json` for `next-12` (latest 12.x). The tag + moves over time; always re-check. +2. List every directory with a `package.json`, excluding `node_modules` + (`fd -H -t f package.json -E node_modules`). This list is the work set; + do not maintain a hand-written list. +3. Run `corepack use pnpm@` in workspace roots first, then members. + `corepack use` stamps `packageManager` in the nearest package.json and + runs an install. Member runs repeat the workspace install; after the root + run they are quick no-ops. +4. If a run fails, fix the cause (see gotchas) and re-run that directory. + +## Gotchas + +- `corepack use` only updates an existing `packageManager` field. If a + package.json lacks the field, corepack walks up to the nearest ancestor + that has one and stamps that file instead; the member stays unstamped. + After the sweep, assert every package.json carries the field. For a + missing one, insert the identical `pnpm@+sha512.` string, + then re-run `corepack use pnpm@` in that directory. +- A workspace may fail with `ERR_PNPM_IGNORED_BUILDS`, and pnpm then writes + a placeholder scaffold into its `pnpm-workspace.yaml`: + `allowBuilds: esbuild: set this to true or false` plus + `ignoredBuiltDependencies`. Repo convention is `allowBuilds: esbuild: true`. + Replace the placeholder and drop the `ignoredBuiltDependencies` entry, + then re-run. +- `plugins/apps/composable-test-suite` once had its own + `pnpm-workspace.yaml` and acted as a nested workspace root. That state is + gone on purpose: pnpm picks the nearest `pnpm-workspace.yaml` walking up, + so a nested one silently forks install and lockfile behavior. Do not + reintroduce it. +- Expect metadata-only lockfile diffs when only the pnpm version moves: + the pnpm self-reference entries, plus a new `packageManagerDependencies` + section in lockfiles last written by older pnpm. Large diffs mean + re-resolution; inspect them before accepting. + +## Verification + +- Every `packageManager` field is byte-identical (same version and hash). +- `pnpm --version` in each workspace prints the target version. +- `pnpm install --frozen-lockfile` succeeds in each of the 11 workspaces. +- `git diff` on lockfiles matches the expectations above. + +## Cleaning stale node_modules + +- `scripts/clean-node-modules` removes every workspace `node_modules`: the + repo root, all module workspaces, and all member packages. Use it when + installs misbehave after dependency changes: clean, reinstall, done. +- Flags: `-n/--dry-run` lists without deleting; `--store` also removes the + shared pnpm store at `/.pnpm-store` (the next install re-downloads + what it held). `external/` (vendored dependency trees with their own + lifecycles) and `.opencode/` are always ignored. +- The script never touches the pnpm store by default, so the reinstall + after cleaning reuses cached packages (zero downloads). +- After cleaning, run `pnpm install` in each workspace root to restore the + development environment; `frontend` postinstall also reinstalls and + builds `plugins-runtime`. diff --git a/backend/package.json b/backend/package.json index c6baf43f73..f29469a28e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@11.20.0+sha512.9a6f330a95b66446ea088faf1521405a8a01f07fde7124cc9958dfed52d4bb436737e65b08f85f37b46fcba375092558ac51262b816844b22f63406ed166bfee", + "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", "repository": { "type": "git", "url": "https://github.com/penpot/penpot" diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index a0b16465e9..dfe9e50563 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -1,3 +1,104 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: 12.3.4 + version: 12.3.4 + +packages: + + '@pnpm/exe.darwin-arm64@12.3.4': + resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.3.4': + resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.linux-arm64-musl@12.3.4': + resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.3.4': + resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.3.4': + resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.3.4': + resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.3.4': + resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.3.4': + resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} + cpu: [x64] + os: [win32] + + pnpm@12.3.4: + resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} + engines: {node: '>=18.*'} + hasBin: true + +snapshots: + + '@pnpm/exe.darwin-arm64@12.3.4': + optional: true + + '@pnpm/exe.darwin-x64@12.3.4': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.3.4': + optional: true + + '@pnpm/exe.linux-arm64@12.3.4': + optional: true + + '@pnpm/exe.linux-x64-musl@12.3.4': + optional: true + + '@pnpm/exe.linux-x64@12.3.4': + optional: true + + '@pnpm/exe.win32-arm64@12.3.4': + optional: true + + '@pnpm/exe.win32-x64@12.3.4': + optional: true + + pnpm@12.3.4: + optionalDependencies: + '@pnpm/exe.darwin-arm64': 12.3.4 + '@pnpm/exe.darwin-x64': 12.3.4 + '@pnpm/exe.linux-arm64': 12.3.4 + '@pnpm/exe.linux-arm64-musl': 12.3.4 + '@pnpm/exe.linux-x64': 12.3.4 + '@pnpm/exe.linux-x64-musl': 12.3.4 + '@pnpm/exe.win32-arm64': 12.3.4 + '@pnpm/exe.win32-x64': 12.3.4 + +--- lockfileVersion: '9.0' settings: diff --git a/backend/pnpm-workspace.yaml b/backend/pnpm-workspace.yaml index b3fbd9192b..3ebe73f82d 100644 --- a/backend/pnpm-workspace.yaml +++ b/backend/pnpm-workspace.yaml @@ -1,2 +1,4 @@ +storeDir: ../.pnpm-store + minimumReleaseAgeExclude: - brace-expansion@5.0.8 || 5.0.9 diff --git a/backend/src/app/tasks/telemetry.clj b/backend/src/app/tasks/telemetry.clj index 82696144db..7064c8f40c 100644 --- a/backend/src/app/tasks/telemetry.clj +++ b/backend/src/app/tasks/telemetry.clj @@ -17,7 +17,6 @@ [app.http.client :as http] [app.main :as-alias main] [app.setup :as-alias setup] - [app.util.blob :as blob] [app.util.json :as json] [integrant.core :as ig] [promesa.exec :as px])) @@ -248,20 +247,16 @@ :props (or (some-> props db/decode-transit-pgobject) {}) :context (or (some-> context db/decode-transit-pgobject) {})})) -(defn- encode-batch - "Encode a sequence of event maps into a fressian+zstd base64 string - suitable for JSON transport." - ^String [events] - (blob/encode-str events {:version 4})) - (defn send-event-batch "Send a single batch of events to the telemetry endpoint. Returns - true on success." + true on success. The events are sent as a plain vector of event + maps; the JSON encoder handles UUID and temporal types natively and + the receiver coerces them back to proper types." [{:keys [::setup/props] :as cfg} batch] (let [payload {:type :telemetry-events :version (:full cf/version) :instance-id (:instance-id props) - :events (encode-batch batch)} + :events (vec batch)} request {:method :post :uri (cf/get :telemetry-uri) :headers {"content-type" "application/json"} diff --git a/backend/test/backend_tests/tasks_telemetry_test.clj b/backend/test/backend_tests/tasks_telemetry_test.clj index e12af0553b..a44f6efe74 100644 --- a/backend/test/backend_tests/tasks_telemetry_test.clj +++ b/backend/test/backend_tests/tasks_telemetry_test.clj @@ -12,7 +12,6 @@ [app.db :as db] [app.loggers.audit :as audit] [app.tasks.telemetry :as telemetry] - [app.util.blob :as blob] [app.util.json :as json] [backend-tests.helpers :as th] [clojure.test :as t] @@ -59,11 +58,6 @@ :cnt long)) -(defn- decode-event-batch - "Decode the base64+fressian+zstd event-batch sent to the mock." - [b64-str] - (blob/decode-str b64-str)) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; STATS / REPORT STRUCTURE TESTS (existing behaviour, extended) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -245,21 +239,19 @@ (t/is (not (contains? ev :ip-addr))))))))) (t/deftest test-batch-encoding-is-decodable - ;; Verify that encode-batch produces a blob that round-trips back - ;; through blob/decode to the original data. + ;; Events are sent as a plain vector of raw event maps (no blob + ;; encoding): every batch must JSON round-trip unchanged, because + ;; the receiver coerces types from the plain JSON representation. (let [events [{:name "navigate" :type "action" :source "telemetry" :tracked-at (ct/now)} {:name "create-file" :type "action" :source "telemetry" :tracked-at (ct/now)}] - ;; Call the private fn through the ns-mapped var - encode (ns-resolve 'app.tasks.telemetry 'encode-batch) - encoded (encode events) - decoded (decode-event-batch encoded)] - (t/is (string? encoded)) - (t/is (seq decoded)) - (t/is (= (count events) (count decoded))) - (t/is (= "navigate" (:name (first decoded)))) - (t/is (= "create-file" (:name (second decoded)))))) + encoded (json/encode-str {:events (vec events)}) + decoded (json/decode encoded)] + (t/is (vector? (:events decoded))) + (t/is (= (count events) (count (:events decoded)))) + (t/is (= "navigate" (:name (first (:events decoded))))) + (t/is (= "create-file" (:name (second (:events decoded))))))) (t/deftest test-multiple-batches-when-many-events ;; Lower batch-size to 1 so that 3 events produce 3 separate @@ -787,9 +779,13 @@ (t/is (= "telemetry-events" (name (:type body)))) (t/is (string? (:version body))) (t/is (some? (:instance-id body))) - ;; :events is a base64-encoded blob - (t/is (string? (:events body))) - (t/is (pos? (count (:events body)))))))))) + ;; :events is a plain vector of raw event maps + (t/is (vector? (:events body))) + (t/is (pos? (count (:events body)))) + (doseq [ev (:events body)] + (t/is (string? (:name ev))) + (t/is (string? (:source ev))) + (t/is (string? (:tracked-at ev)))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TASK BRANCH COVERAGE diff --git a/common/package.json b/common/package.json index acc9432642..4269da7ba6 100644 --- a/common/package.json +++ b/common/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC Sucursal en España SL", "private": true, - "packageManager": "pnpm@12.0.0+sha512.9e2e3dc3911995868dc94b8175c217c27e95408fa03b4a22749778f2b34f773b77cdd3b39ede8171b22fcd53be6a35342e9fac9948a68ef58df6488ce89a7e67", + "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457", "type": "module", "repository": { "type": "git", diff --git a/common/pnpm-lock.yaml b/common/pnpm-lock.yaml index 77f173a685..ef70c46056 100644 --- a/common/pnpm-lock.yaml +++ b/common/pnpm-lock.yaml @@ -7,96 +7,96 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.0.0 - version: 12.0.0 + specifier: 12.3.4 + version: 12.3.4 packages: - '@pnpm/exe.darwin-arm64@12.0.0': - resolution: {integrity: sha512-sqeoPfVMIfQhbwzDrKraXY2ynyuWClFqzvfImzAS/yczEru1m5SGvQ9kgFPDvQzJZ9AetedgJeDZC6qYvH8/tQ==} + '@pnpm/exe.darwin-arm64@12.3.4': + resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.0.0': - resolution: {integrity: sha512-Quc3J6c9cGTy+LDgz1cLVgCNOU9IERuyAlDoEj0DCilKqvo50Jx1GV8k74iwn4J9fFSKkm8JrwNvtTDj3uWnUA==} + '@pnpm/exe.darwin-x64@12.3.4': + resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.0.0': - resolution: {integrity: sha512-EVWd3OTmgsMFhXx69b5JxIzoabG9Ma7m4OeTaf0ZKBzMnfYi8u21NDQo92ToMrdYL5dYDDCHsyYIjXzk+d0HhA==} + '@pnpm/exe.linux-arm64-musl@12.3.4': + resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.0.0': - resolution: {integrity: sha512-cXHHW8M4rAPsYNkKZO9WVcpLLK55i9EaIsZPfIqUuY2eopd5LqnFyBge54HCh1GC0yCX8ySn0hYIi+4OyAEoDg==} + '@pnpm/exe.linux-arm64@12.3.4': + resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.0.0': - resolution: {integrity: sha512-UcXwMdFjly0mpddkGigHKTxe27IMv2fUK4IWW/MHmJ3yMguxXmkwNlEI4aE+G1HO2TLo20uNEUWD4ymLe/DaCQ==} + '@pnpm/exe.linux-x64-musl@12.3.4': + resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.0.0': - resolution: {integrity: sha512-6Rsl+zEWMOmus7v7/9J3OE8EMvHyNAfxYmDfmhQG4J0985OuT3G3Ho9NSGHjkBn4aU4bgklWifRhe1HX8dUSyw==} + '@pnpm/exe.linux-x64@12.3.4': + resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.0.0': - resolution: {integrity: sha512-O5F76A4oVFrpDGdFxEszRIThOSBfjHdH5c006gR+7UTCfiXrukr1XfqPungUI1DXcSR5gb9jBsPQqQZOAoOOxw==} + '@pnpm/exe.win32-arm64@12.3.4': + resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.0.0': - resolution: {integrity: sha512-5dKFajIEWJ1ai+KHXFJvskY6vchbunmLwSUV2ywbLymcmJjfY5XJVpgzPCyIoVCMVG0zHorr66+hM8h8b3aRfQ==} + '@pnpm/exe.win32-x64@12.3.4': + resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} cpu: [x64] os: [win32] - pnpm@12.0.0: - resolution: {integrity: sha512-ni49w5EZlYaNyUuBdcIXwn6VQI+gO0oidJd48rNPdzt3zdOznt6BcbIvzVO+ajU0Lp+smUimjvWN9kiM6Jp+Zw==} + pnpm@12.3.4: + resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.0.0': + '@pnpm/exe.darwin-arm64@12.3.4': optional: true - '@pnpm/exe.darwin-x64@12.0.0': + '@pnpm/exe.darwin-x64@12.3.4': optional: true - '@pnpm/exe.linux-arm64-musl@12.0.0': + '@pnpm/exe.linux-arm64-musl@12.3.4': optional: true - '@pnpm/exe.linux-arm64@12.0.0': + '@pnpm/exe.linux-arm64@12.3.4': optional: true - '@pnpm/exe.linux-x64-musl@12.0.0': + '@pnpm/exe.linux-x64-musl@12.3.4': optional: true - '@pnpm/exe.linux-x64@12.0.0': + '@pnpm/exe.linux-x64@12.3.4': optional: true - '@pnpm/exe.win32-arm64@12.0.0': + '@pnpm/exe.win32-arm64@12.3.4': optional: true - '@pnpm/exe.win32-x64@12.0.0': + '@pnpm/exe.win32-x64@12.3.4': optional: true - pnpm@12.0.0: + pnpm@12.3.4: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.0.0 - '@pnpm/exe.darwin-x64': 12.0.0 - '@pnpm/exe.linux-arm64': 12.0.0 - '@pnpm/exe.linux-arm64-musl': 12.0.0 - '@pnpm/exe.linux-x64': 12.0.0 - '@pnpm/exe.linux-x64-musl': 12.0.0 - '@pnpm/exe.win32-arm64': 12.0.0 - '@pnpm/exe.win32-x64': 12.0.0 + '@pnpm/exe.darwin-arm64': 12.3.4 + '@pnpm/exe.darwin-x64': 12.3.4 + '@pnpm/exe.linux-arm64': 12.3.4 + '@pnpm/exe.linux-arm64-musl': 12.3.4 + '@pnpm/exe.linux-x64': 12.3.4 + '@pnpm/exe.linux-x64-musl': 12.3.4 + '@pnpm/exe.win32-arm64': 12.3.4 + '@pnpm/exe.win32-x64': 12.3.4 --- lockfileVersion: '9.0' diff --git a/common/pnpm-workspace.yaml b/common/pnpm-workspace.yaml index a1c031fc33..7b3a5d3edf 100644 --- a/common/pnpm-workspace.yaml +++ b/common/pnpm-workspace.yaml @@ -1 +1,3 @@ +storeDir: ../.pnpm-store + minimumReleaseAge: 0 diff --git a/docs/img/objects/line-arrow-tools.webp b/docs/img/objects/line-arrow-tools.webp new file mode 100644 index 0000000000..4615e65cb8 Binary files /dev/null and b/docs/img/objects/line-arrow-tools.webp differ diff --git a/docs/package.json b/docs/package.json index 7d498ee015..208cd18089 100644 --- a/docs/package.json +++ b/docs/package.json @@ -39,5 +39,5 @@ "markdown-it-anchor": "^9.2.1", "markdown-it-plantuml": "^1.4.1" }, - "packageManager": "pnpm@12.0.0+sha512.9e2e3dc3911995868dc94b8175c217c27e95408fa03b4a22749778f2b34f773b77cdd3b39ede8171b22fcd53be6a35342e9fac9948a68ef58df6488ce89a7e67" + "packageManager": "pnpm@12.3.4+sha512.961aa41fb077da3a04a441d9f8e15ebc0c96da8ef710b2eb67bf9ee7cb0610eabd48f1fd85f51cffe73846785fa0f87c56a3a872a1d893f8446741b5cce45457" } diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 5938203566..3527b910e8 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -7,96 +7,96 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.0.0 - version: 12.0.0 + specifier: 12.3.4 + version: 12.3.4 packages: - '@pnpm/exe.darwin-arm64@12.0.0': - resolution: {integrity: sha512-sqeoPfVMIfQhbwzDrKraXY2ynyuWClFqzvfImzAS/yczEru1m5SGvQ9kgFPDvQzJZ9AetedgJeDZC6qYvH8/tQ==} + '@pnpm/exe.darwin-arm64@12.3.4': + resolution: {integrity: sha512-PAyUol8T1+/+ViOiXAt51ECA+QnfXCqz6foL4bW+LsoX0NcVd5XVEM2mRQu+LV4oc7uRz9zf9U0P+XFfuQeDAw==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.0.0': - resolution: {integrity: sha512-Quc3J6c9cGTy+LDgz1cLVgCNOU9IERuyAlDoEj0DCilKqvo50Jx1GV8k74iwn4J9fFSKkm8JrwNvtTDj3uWnUA==} + '@pnpm/exe.darwin-x64@12.3.4': + resolution: {integrity: sha512-fxP9JCk0Cdye+ePuj+GJJLMUMTqHGWRdb1dtv4How876uQ2ehxvenpgiYAir/ceO9PsYUZkFTtyZdx+rRu5QOA==} cpu: [x64] os: [darwin] - '@pnpm/exe.linux-arm64-musl@12.0.0': - resolution: {integrity: sha512-EVWd3OTmgsMFhXx69b5JxIzoabG9Ma7m4OeTaf0ZKBzMnfYi8u21NDQo92ToMrdYL5dYDDCHsyYIjXzk+d0HhA==} + '@pnpm/exe.linux-arm64-musl@12.3.4': + resolution: {integrity: sha512-FBOt0/7ye6O6q4AllVV5QMviB6qE6fqkeczV/+MDWQsmo+QJrlfsh6X7CpH/tClVpBZEyIbjpUoT8bNhCYBxEg==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.0.0': - resolution: {integrity: sha512-cXHHW8M4rAPsYNkKZO9WVcpLLK55i9EaIsZPfIqUuY2eopd5LqnFyBge54HCh1GC0yCX8ySn0hYIi+4OyAEoDg==} + '@pnpm/exe.linux-arm64@12.3.4': + resolution: {integrity: sha512-t71AVA7LRqiKTyZ5xMYaZc2n5DfdpMbfokZuiIOXHBOM03ECnF0t4iYwaBDqJgVjlKYUOwaF/bRQajGNA4cJ4w==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.0.0': - resolution: {integrity: sha512-UcXwMdFjly0mpddkGigHKTxe27IMv2fUK4IWW/MHmJ3yMguxXmkwNlEI4aE+G1HO2TLo20uNEUWD4ymLe/DaCQ==} + '@pnpm/exe.linux-x64-musl@12.3.4': + resolution: {integrity: sha512-RPmk7Jb/aYaFvL2iyDN/AtMY+hUEsue732WmXpcuQ9tBpMnGyA5py7Z3+e+qmQaJ0zY/4ni9jJiyPBQHujmv6w==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.0.0': - resolution: {integrity: sha512-6Rsl+zEWMOmus7v7/9J3OE8EMvHyNAfxYmDfmhQG4J0985OuT3G3Ho9NSGHjkBn4aU4bgklWifRhe1HX8dUSyw==} + '@pnpm/exe.linux-x64@12.3.4': + resolution: {integrity: sha512-2ZqOlSPkfwX1h5cR+FPiWf8+F+2hZT/3TvhUK5sigHqwaQCIiq8R7CGxhndKs63JtcLi2a1Qpo+wX/EoyfjyJQ==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.0.0': - resolution: {integrity: sha512-O5F76A4oVFrpDGdFxEszRIThOSBfjHdH5c006gR+7UTCfiXrukr1XfqPungUI1DXcSR5gb9jBsPQqQZOAoOOxw==} + '@pnpm/exe.win32-arm64@12.3.4': + resolution: {integrity: sha512-ANyrHqyqco6SXBysUTRF74itDyyraea7IbFsKFdNXTjcFnfycTDx37EwuhdpPYFNSIh2JhUG4fByclsRfiHX7w==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.0.0': - resolution: {integrity: sha512-5dKFajIEWJ1ai+KHXFJvskY6vchbunmLwSUV2ywbLymcmJjfY5XJVpgzPCyIoVCMVG0zHorr66+hM8h8b3aRfQ==} + '@pnpm/exe.win32-x64@12.3.4': + resolution: {integrity: sha512-WH/KqBPY/hq2Tb7SgQltEZytimcjgKRaCRL/aM9CI0c67iKc5TVmHUhIiL3Ux9FB4bWn36i6XewUcScQI+zG8w==} cpu: [x64] os: [win32] - pnpm@12.0.0: - resolution: {integrity: sha512-ni49w5EZlYaNyUuBdcIXwn6VQI+gO0oidJd48rNPdzt3zdOznt6BcbIvzVO+ajU0Lp+smUimjvWN9kiM6Jp+Zw==} + pnpm@12.3.4: + resolution: {integrity: sha512-lhqkH7B32joEpEHZ+OFevAyW2o73ELLrZ7+e58sGEOq9SPH9hfUc/+c4RnhfoPh8VqOocqHYk/hEZ0G1zORUVw==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.darwin-arm64@12.0.0': + '@pnpm/exe.darwin-arm64@12.3.4': optional: true - '@pnpm/exe.darwin-x64@12.0.0': + '@pnpm/exe.darwin-x64@12.3.4': optional: true - '@pnpm/exe.linux-arm64-musl@12.0.0': + '@pnpm/exe.linux-arm64-musl@12.3.4': optional: true - '@pnpm/exe.linux-arm64@12.0.0': + '@pnpm/exe.linux-arm64@12.3.4': optional: true - '@pnpm/exe.linux-x64-musl@12.0.0': + '@pnpm/exe.linux-x64-musl@12.3.4': optional: true - '@pnpm/exe.linux-x64@12.0.0': + '@pnpm/exe.linux-x64@12.3.4': optional: true - '@pnpm/exe.win32-arm64@12.0.0': + '@pnpm/exe.win32-arm64@12.3.4': optional: true - '@pnpm/exe.win32-x64@12.0.0': + '@pnpm/exe.win32-x64@12.3.4': optional: true - pnpm@12.0.0: + pnpm@12.3.4: optionalDependencies: - '@pnpm/exe.darwin-arm64': 12.0.0 - '@pnpm/exe.darwin-x64': 12.0.0 - '@pnpm/exe.linux-arm64': 12.0.0 - '@pnpm/exe.linux-arm64-musl': 12.0.0 - '@pnpm/exe.linux-x64': 12.0.0 - '@pnpm/exe.linux-x64-musl': 12.0.0 - '@pnpm/exe.win32-arm64': 12.0.0 - '@pnpm/exe.win32-x64': 12.0.0 + '@pnpm/exe.darwin-arm64': 12.3.4 + '@pnpm/exe.darwin-x64': 12.3.4 + '@pnpm/exe.linux-arm64': 12.3.4 + '@pnpm/exe.linux-arm64-musl': 12.3.4 + '@pnpm/exe.linux-x64': 12.3.4 + '@pnpm/exe.linux-x64-musl': 12.3.4 + '@pnpm/exe.win32-arm64': 12.3.4 + '@pnpm/exe.win32-x64': 12.3.4 --- lockfileVersion: '9.0' diff --git a/docs/pnpm-workspace.yaml b/docs/pnpm-workspace.yaml index bd2aa47620..b9b659dcc5 100644 --- a/docs/pnpm-workspace.yaml +++ b/docs/pnpm-workspace.yaml @@ -1,3 +1,5 @@ +storeDir: ../.pnpm-store + minimumReleaseAgeExclude: - undici@7.28.0 || 7.29.0 - js-yaml@3.15.0 || 4.3.0 diff --git a/docs/user-guide/account-teams/projects-files.njk b/docs/user-guide/account-teams/projects-files.njk index 02f1afd38c..fa3029e7c9 100644 --- a/docs/user-guide/account-teams/projects-files.njk +++ b/docs/user-guide/account-teams/projects-files.njk @@ -56,6 +56,10 @@ desc: Learn how to organize your work in Penpot. Create, manage and organize pro

When creating a file, you'll be asked to give it a name. The file will open in the workspace where you can start designing immediately.

+

Grid and list view

+

In the dashboard header you can switch how files are shown: a thumbnail Grid view or a compact List view. The choice is kept in the browser and applies both to the team files view and inside a project.

+

List view shows the file name, whether the file is a shared library, the last modification time, and the same options menu as the file cards.

+

Edit a file

To rename a file, right-click on the file card in the dashboard and select Rename, or click on the three-dot menu on the file card. Enter the new name and confirm the change. You can also access file settings and other options from the file's context menu.

diff --git a/docs/user-guide/designing/color-stroke.njk b/docs/user-guide/designing/color-stroke.njk index 012b45b264..bd1cab3000 100644 --- a/docs/user-guide/designing/color-stroke.njk +++ b/docs/user-guide/designing/color-stroke.njk @@ -122,8 +122,14 @@ desc: Style your designs with Penpot's options! Learn about color fills, gradien Multiple strokes +

Stroke to path

+

A path is the underlying geometry: a sequence of points and segments (straight lines or Bezier curves). A stroke is the visible line drawn along that path, with properties such as width, color, opacity, dashes, caps, and joins.

+

Stroke to path turns the visible outline of a stroke into a separate, editable path. You can then reshape or style that outline independently of the original path. The new shape is usually closed. That matters for SVG, boolean operations, and editing the outline as geometry.

+

To convert a stroke, select a layer that has one, open the layer menu, and choose Stroke to path. Penpot creates a new path from each stroke (named with " (stroke)") and removes the strokes from the original layer.

+

Stroke to path is currently available when WebGL rendering is enabled.

+

Stroke Caps

-

Ever needed an arrow to point something? You can style the ends of any open paths selecting different styles for each end of an open path.

+

Ever needed an arrow to point to something? You can style the ends of any open paths selecting different styles for each end of an open path. You can also start from the Arrow tool, which already applies a triangle cap.